Skip to content
Snippets Groups Projects
geometry_utils.py 7.61 KiB
Newer Older
#    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)