-
Valerio Mariani authored
Major clean-up and refactoring to prepare for OnDA 1.0. Currently only the refactored modules are left in the branch. The other missing modules will be reintroduced later
Valerio Mariani authoredMajor clean-up and refactoring to prepare for OnDA 1.0. Currently only the refactored modules are left in the branch. The other missing modules will be reintroduced later
geometry_utils.py 7.61 KiB
# This file is part of cfelpyutils.
#
# cfelpyutils is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cfelpyutils is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cfelpyutils. If not, see <http://www.gnu.org/licenses/>.
"""
Utilities for CrystFEL-style geometry files.
This module contains utilities for the processing of CrystFEL-style geometry
files.
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import collections
import numpy
PixelMaps = collections.namedtuple('PixelMaps', ['x', 'y', 'r'])
ImageShape = collections.namedtuple('ImageShape', ['ss', 'fs'])
def compute_pixel_maps(geometry):
"""Create pixel maps from a CrystFEL geometry object.
Compute pixel maps from a CrystFEL-style geometry object (A dictionary
returned by the load_crystfel_geometry function from the crystfel_utils
module). The pixel maps can be used to create a representation of
the physical layout of the geometry, keeping the origin of the reference
system at the beam interaction point.
Args:
geometry (dict): A CrystFEL geometry object (A dictionary returned by
the load_crystfel_geometry function from the crystfel_utils
module).
Returns:
tuple: a tuple containing three float32 numpy arrays ('slab'-like pixel
maps) with respectively x, y coordinates of the data pixels and
distance of each pixel 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 contain the 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.
for pan in geometry['panels']:
# Determine the pixel coordinates for the current panel.
i, j = numpy.meshgrid(
numpy.arange(
geometry['panels'][pan]['max_ss'] -
geometry['panels'][pan]['min_ss'] +
1
),
numpy.arange(
geometry['panels'][pan]['max_fs'] -
geometry['panels'][pan]['min_fs'] +
1
),
indexing='ij'
)
# Compute the x,y vectors, using 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']
)
cmplx = i * d_y + j * d_x + r_0
# Fill maps that will be returned with the computed
# values (x and y maps).
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.real
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.imag
# Compute the values for the radius pixel maps that will be returned.
r_map = numpy.sqrt(numpy.square(x_map) + numpy.square(y_map))
# Return the pixel maps as a tuple.
return PixelMaps(x_map, y_map, r_map)
def apply_pixel_maps(data_as_slab, pixel_maps, output_array=None):
"""Apply geometry in pixel map format to the input data.
Applies geometry, described by pixel maps, to input data in 'slab' format.
Turns a 2d array of pixel values into an array containing a representation
of the physical layout of the geometry, keeping the origin of the reference
system at the beam interaction point.
Args:
data_as_slab (ndarray): the pixel values on which to apply the
geometry, in 'slab' format.
pixel_maps (tuple): pixel maps, as returned by the
compute_pixel_maps function in this module.
output_array (Optional[numpy.ndarray]): array to hold the output.
If the array is not provided, one will be generated automatically.
Defaults to None (No array provided).
Returns:
ndarray: Array with the same dtype as the input data containing a
representation of the physical layout of the geometry (i.e.: the
geometry information applied to the input data).
"""
# If no array was provided, generate one.
if output_array is None:
output_array = numpy.zeros(
shape=data_as_slab.shape,
dtype=data_as_slab.dtype
)
# Apply the pixel map geometry to the data.
output_array[pixel_maps.y, pixel_maps.x] = data_as_slab.ravel()
# Return the output array.
return output_array
def compute_minimum_image_size(pixel_maps):
"""
Compute the minimum size of an image that can represent the geometry.
Compute the minimum size of an image that can contain a
representation of the geometry described by the pixel maps, assuming
that the image is center at the center of the reference system.
Args:
pixel_maps (tuple): pixel maps, as returned by the
compute_pixel_maps function in this module.
Returns:
tuple: numpy shape object describing the minimum image size.
"""
# Recover the x and y pixel maps.
x_map, y_map = pixel_maps.x, pixel_maps.x.y
# 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 ImageShape(y_largest, x_largest)
def adjust_pixel_maps_for_pyqtgraph(pixel_maps):
"""
Adjust pixel maps for visualization of the data in a pyqtgraph widget.
Adjust the pixel maps for use in a Pyqtgraph's ImageView widget.
Essentially, the origin of the reference system is moved to the
top-left of the image.
Args:
pixel_maps (tuple): pixel maps, as returned by the
compute_pixel_maps function in this module.
Returns:
tuple: a three-element tuple containing two float32 numpy arrays
('slab'-like pixel maps) with respectively x, y coordinates of the
data pixels as the first two elements, and None as the third.
"""
# Compute the minimum image shape needed to represent the coordinates
min_ss, min_fs = compute_minimum_image_size(pixel_maps)
# convert y x values to i j values
i = numpy.array(pixel_maps.y, dtype=numpy.int) + min_ss // 2 - 1
j = numpy.array(pixel_maps.x, dtype=numpy.int) + min_fs // 2 - 1
y_map = i.flatten()
x_map = j.flatten()
return PixelMaps(x_map, y_map, None)