Skip to content
Snippets Groups Projects
Commit 7e27f832 authored by Michael Schneider's avatar Michael Schneider
Browse files

merge with DevelopmentRG

parent 4ae0f38e
No related branches found
No related tags found
2 merge requests!94azimuthal integrator using DSSC geometry object,!87Introduce package structure, generalized binning principle, ...
...@@ -11,7 +11,7 @@ from .dssc_processing import ( ...@@ -11,7 +11,7 @@ from .dssc_processing import (
process_dssc_data) process_dssc_data)
from .dssc import ( from .dssc import (
DSSCBinner, DSSCFormatter, DSSCAnalyzer) DSSCBinner, DSSCFormatter, DSSCAnalyzer)
from .azimuthal_integrator import AzimuthalIntegrator from .azimuthal_integrator import AzimuthalIntegrator, AzimuthalIntegratorDSSC
__all__ = ( __all__ = (
# Functions # Functions
...@@ -35,6 +35,7 @@ __all__ = ( ...@@ -35,6 +35,7 @@ __all__ = (
"DSSCFormatter", "DSSCFormatter",
"DSSCAnalyzer", "DSSCAnalyzer",
"AzimuthalIntegrator", "AzimuthalIntegrator",
"AzimuthalIntegratorDSSC",
# Variables # Variables
) )
......
import logging import logging
import warnings
import numpy as np import numpy as np
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class AzimuthalIntegrator(object): class AzimuthalIntegrator(object):
def __init__(self, imageshape, center, polar_range, dr=2, aspect=204/236): def __init__(self, imageshape, center, polar_range, aspect=204/236, **kwargs):
''' '''
Create a reusable integrator for repeated azimuthal integration of Create a reusable integrator for repeated azimuthal integration of
similar images. Calculates array indices for a given parameter set that similar images. Calculates array indices for a given parameter set that
allows fast recalculation. allows fast recalculation.
Copyright (c) 2019, Michael Schneider
Copyright (c) 2020, SCS-team
license: BSD 3-Clause License (see LICENSE_BSD for more info)
Parameters Parameters
========== ==========
imageshape : tuple of ints imageshape : tuple of ints
The shape of the images to be integrated over. The shape of the images to be integrated over.
center : tuple of ints center : tuple of ints
center coordinates in pixels center coordinates in pixels
polar_range : tuple of ints polar_range : tuple of ints
start and stop polar angle (in degrees) to restrict integration to start and stop polar angle (in degrees) to restrict integration to
wedges wedges
dr : int, default 2 dr : int, optional
radial width of the integration slices. Takes non-square DSSC radial width of the integration slices. Takes non-square DSSC
pixels into account. pixels into account.
nrings : int, optional
Number of integration rings. Can be given as an alternative to dr
aspect: float, default 204/236 for DSSC aspect: float, default 204/236 for DSSC
aspect ratio of the pixel pitch aspect ratio of the pixel pitch
Returns Returns
======= =======
ai : azimuthal_integrator instance ai : azimuthal_integrator instance
...@@ -43,45 +42,142 @@ class AzimuthalIntegrator(object): ...@@ -43,45 +42,142 @@ class AzimuthalIntegrator(object):
> ai.distance > ai.distance
> ai.polar_mask > ai.polar_mask
''' '''
self.shape = imageshape self.xcoord = None
self.ycoord = None
self._calc_dist_array(imageshape, center, aspect)
self._calc_polar_mask(polar_range)
self._calc_indices(**kwargs)
def _calc_dist_array(self, shape, center, aspect):
'''Calculate pixel coordinates for the given shape.'''
self.center = center
self.shape = shape
self.aspect = aspect
cx, cy = center cx, cy = center
log.info(f'azimuthal center: {center}') log.info(f'azimuthal center: {center}')
sx, sy = imageshape sx, sy = shape
xcoord, ycoord = np.ogrid[:sx, :sy] xcoord, ycoord = np.ogrid[:sx, :sy]
xcoord -= cx self.xcoord = xcoord - cx
ycoord -= cy self.ycoord = ycoord - cy
# distance from center, hexagonal pixel shape taken into account # distance from center, hexagonal pixel shape taken into account
dist_array = np.hypot(xcoord * aspect, ycoord) self.dist_array = np.hypot(self.xcoord * aspect, self.ycoord)
# array of polar angles def _calc_indices(self, **kwargs):
if np.abs(polar_range[1]-polar_range[0]) > 180: '''Calculates the list of indices for the flattened image array.'''
raise ValueError('Integration angle too wide, should be within 180' maxdist = self.dist_array.max()
' degrees') mindist = self.dist_array.min()
dr = kwargs.get('dr', None)
if np.abs(polar_range[1]-polar_range[0]) < 1e-6: nrings = kwargs.get('nrings', None)
raise ValueError('Integration angle too narrow') if (dr is None) and (nrings is None):
raise AssertionError('Either <dr> or <nrings> needs to be given.')
tmin, tmax = np.deg2rad(np.sort(polar_range)) % np.pi if (dr is None):
polar_array = np.arctan2(xcoord, ycoord) dr = maxdist / nrings
polar_array = np.mod(polar_array, np.pi) if (dr is not None) and (nrings is not None):
self.polar_mask = (polar_array > tmin) * (polar_array < tmax) warnings.warn('Both <dr> and <nrings> given. <dr> takes precedence.')
self.maxdist = max(sx - cx, sy - cy) idx = np.indices(dimensions=self.shape)
self.index_array = np.ravel_multi_index(idx, self.shape)
ix, iy = np.indices(dimensions=(sx, sy))
self.index_array = np.ravel_multi_index((ix, iy), (sx, sy))
self.distance = np.array([]) self.distance = np.array([])
self.flat_indices = [] self.flat_indices = []
for dist in range(dr, self.maxdist, dr): for dist in np.arange(mindist, maxdist + dr, dr):
ring_mask = self.polar_mask * (dist_array >= (dist - dr)) * \ ring_mask = (self.polar_mask
(dist_array < dist) * (self.dist_array >= (dist - dr))
* (self.dist_array < dist))
self.flat_indices.append(self.index_array[ring_mask]) self.flat_indices.append(self.index_array[ring_mask])
self.distance = np.append(self.distance, dist) self.distance = np.append(self.distance, dist)
def _calc_polar_mask(self, polar_range):
self.polar_range = polar_range
prange = np.abs(polar_range[1] - polar_range[0])
if prange > 180:
raise ValueError('Integration angle too wide, should be within 180'
' degrees')
if prange < 1e-6:
raise ValueError('Integration angle too narrow')
if prange == 180:
self.polar_mask = np.ones(self.shape, dtype=bool)
else:
tmin, tmax = np.deg2rad(np.sort(polar_range)) % np.pi
polar_array = np.arctan2(self.xcoord, self.ycoord)
polar_array = np.mod(polar_array, np.pi)
self.polar_mask = (polar_array > tmin) * (polar_array < tmax)
def calc_q(self, distance, wavelength):
'''Calculate momentum transfer coordinate.
Parameters
==========
distance : float
Sample - detector distance in meter
wavelength : float
wavelength of scattered light in meter
Returns
=======
deltaq : np.ndarray
Momentum transfer coordinate in 1/m
'''
return 4 * np.pi * np.sin(np.arctan(self.distance / distance) / 2) / wavelength
def __call__(self, image): def __call__(self, image):
assert self.shape == image.shape, 'image shape does not match' assert self.shape == image.shape, 'image shape does not match'
image_flat = image.flatten() image_flat = np.ravel(image)
return np.array([np.nansum(image_flat[indices]) \ return np.array([np.nansum(image_flat[indices]) \
for indices in self.flat_indices]) for indices in self.flat_indices])
class AzimuthalIntegratorDSSC(AzimuthalIntegrator):
def __init__(self, geom, polar_range, dxdy=(0, 0), **kwargs):
'''
Create a reusable integrator for repeated azimuthal integration of
similar images. Calculates array indices for a given parameter set that
allows fast recalculation. Directly uses a extra_geom.detectors.DSSC_1MGeometry
instance for correct pixel positions
Parameters
==========
geom : extra_geom.detectors.DSSC_1MGeometry
loaded geometry instance
polar_range : tuple of ints
start and stop polar angle (in degrees) to restrict integration to
wedges
dr : int, optional
radial width of the integration slices. Takes non-square DSSC
pixels into account.
nrings : int, optional
Number of integration rings. Can be given as an alternative to dr
dxdy : tuple of floats, default (0, 0)
global coordinate shift to adjust center outside of geom object (meter)
Returns
=======
ai : azimuthal_integrator instance
Instance can directly be called with image data:
> az_intensity = ai(image)
radial distances and the polar mask are accessible as attributes:
> ai.distance
> ai.polar_mask
'''
self.xcoord = None
self.ycoord = None
self._calc_dist_array(geom, dxdy)
self._calc_polar_mask(polar_range)
self._calc_indices(**kwargs)
def _calc_dist_array(self, geom, dxdy):
self.dxdy = dxdy
pos = geom.get_pixel_positions()
self.shape = pos.shape[:-1]
self.xcoord = pos[..., 0] + dxdy[0]
self.ycoord = pos[..., 1] + dxdy[1]
self.dist_array = np.hypot(self.xcoord, self.ycoord)
...@@ -126,7 +126,7 @@ def get_xgm_formatted(run, xgm_name, xgm_coord_stride=1): ...@@ -126,7 +126,7 @@ def get_xgm_formatted(run, xgm_name, xgm_coord_stride=1):
return xgm return xgm
def quickmask_DSSC_ASIC(geom, poslist): def quickmask_DSSC_ASIC(poslist):
''' '''
Returns a mask for the given DSSC geometry with ASICs given in poslist Returns a mask for the given DSSC geometry with ASICs given in poslist
blanked. poslist is a list of (module, row, column) tuples. Each module blanked. poslist is a list of (module, row, column) tuples. Each module
...@@ -140,7 +140,7 @@ def quickmask_DSSC_ASIC(geom, poslist): ...@@ -140,7 +140,7 @@ def quickmask_DSSC_ASIC(geom, poslist):
for (module, row, col) in poslist: for (module, row, col) in poslist:
mask[module, 64 * row:64 * (row + 1), 64 * col:64 * (col + 1)] = \ mask[module, 64 * row:64 * (row + 1), 64 * col:64 * (col + 1)] = \
np.nan np.nan
return geom.position_modules_fast(mask)[0] return mask
def load_mask(fname, dssc_mask): def load_mask(fname, dssc_mask):
......
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