Source code for abel.transform
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import numpy as np
import abel
import time
import warnings
[docs]def transform(
IM, direction='inverse', method='three_point', center='none',
verbose=True, symmetry_axis=None,
use_quadrants=(True, True, True, True), recast_as_float64=True,
transform_options=dict(), center_options=dict()):
"""This is the main function of PyAbel, providing both forward
and inverse abel transforms for full images. In addition,
this function can perform image centering and symmetrization.
Parameters
----------
IM : a NxM numpy array
This is the image to be transformed
direction : str
The type of Abel transform to be performed.
``forward``
A 'forward' Abel transform takes a (2D) slice of a 3D image
and returns the 2D projection.
``inverse``
An 'inverse' Abel transform takes a 2D projection
and reconstructs a 2D slice of the 3D image.
The default is ``inverse``.
method : str
specifies which numerical approximation to the Abel transform
should be employed (see below). The options are
``hansenlaw``
the recursive algorithm described by Hansen and Law
``basex``
the Gaussian "basis set expansion" method
of Dribinski et al.
``direct``
a naive implementation of the analytical
formula by Roman Yurchuk.
``three_point``
the three-point transform of Dasch and co-workers
center : tuple or str
If a tuple (float, float) is provided, this specifies
the image center in (y,x) (row, column) format.
A value `None` can be supplied
if no centering is desired in one dimension,
for example 'center=(None, 250)'.
If a string is provided, an automatic centering algorithm is used
``image_center``
center is assumed to be the center of the image.
``slice``
the center is found my comparing slices in the horizontal and vertical directions
``com``
the center is calculated as the center of mass
``gaussian``
the center is found using a fit to a Gaussian function. This
only makes sense if your data looks like a Gaussian.
``none``
(Default)
No centering is performed. An image with an odd
number of columns must be provided.
verbose : boolean
True/False to determine if non-critical output should be printed.
symmetry_axis : int or tuple
Symmetrize the image about the numpy axis
0 (vertical), 1 (horizontal), (0,1) (both axes)
use_quadrants : tuple of 4 booleans
select quadrants to be used in the analysis: (Q0,Q1,Q2,Q3).
Quadrants are numbered counter-clockwide from upper right.
See note below for description of quadrants.
Default is ``(True, True, True, True)``, which uses all quadrants.
recast_as_float64 : boolean
True/False that determines if the input image should be recast to ``float64``.
Many images are imported in other formats (such as ``uint8`` or ``uint16``)
and this does not always play well with the transorm algorithms. This should
probably always be set to True. (Default is True.)
transform_options : tuple
Additional arguments passed to the individual transform functions.
See the documentation for the individual transform method for options.
center_options : tuple
Additional arguments to be passed to the centering function.
.. note:: Quadrant averaging
The quadrants can be averaged using the
``use_quadrants`` keyword in order to provide better data quality.
The quadrants are numbered starting from
Q0 in the upper right and proceeding counter-clockwise: ::
+--------+--------+
| Q1 * | * Q0 |
| * | * |
| * | * | AQ1 | AQ0
+--------o--------+ --(inverse Abel transform)--> ----o----
| * | * | AQ2 | AQ3
| * | * |
| Q2 * | * Q3 | AQi == inverse Abel transform
+--------+--------+ of quadrant Qi
Three cases are possible:
1) symmetry_axis = 0 (vertical): ::
Combine: Q01 = Q0 + Q1, Q23 = Q2 + Q3
inverse image AQ01 | AQ01
-----o-----
AQ23 | AQ23
2) symmetry_axis = 1 (horizontal): ::
Combine: Q12 = Q1 + Q2, Q03 = Q0 + Q3
inverse image AQ12 | AQ03
-----o----- (top and bottom equivalent)
AQ12 | AQ03
3) symmetry_axis = (0, 1) (both): ::
Combine: Q = Q0 + Q1 + Q2 + Q3
inverse image AQ | AQ
---o--- (all quadrants equivalent)
AQ | AQ
Returns
-------
results : dict
The transform function returns a dictionary of results
depending on the options selected
``results['transform']``
(always returned) is the 2D forward/reverse Abel transform
``results['radial_intensity']``
is not currently implemented
``results['residual']``
is not currently implemented
Notes
-----
As mentioned above, PyAbel offers several different approximations
to the the exact abel transform.
All the the methods should produce similar results, but
depending on the level and type of noise found in the image,
certain methods may perform better than others. Please see the "Transform Methods"
section of the documentation for complete information.
``hansenlaw``
This "recursive algorithm" produces reliable results
and is quite fast (~0.1 sec for a 1001x1001 image).
It makes no assumptions about the data
(apart from cylindrical symmetry). It tends to require that the data
is finely sampled for good convergence.
E. W. Hansen and P.-L. Law "Recursive methods for computing
the Abel transform and its inverse"
J. Opt. Soc. A*2, 510-520 (1985)
http://dx.doi.org/10.1364/JOSAA.2.000510
``basex`` *
The "basis set exapansion" algorithm describes the data in terms
of gaussian functions, which themselves can be abel transformed
analytically. Because the gaussian functions are approximately the size
of each pixel, this method also does not make any assumption about
the shape of the data. This method is one of the de-facto standards in
photoelectron/photoion imaging.
Dribinski et al, 2002 (Rev. Sci. Instrum. 73, 2634)
http://dx.doi.org/10.1063/1.1482156
``direct``
This method attempts a direct integration of the Abel
transform integral. It makes no assumptions about the data
(apart from cylindrical symmetry),
but it typically requires fine sampling to converge.
Such methods are typically inefficient,
but thanks to this Cython implementation (by Roman Yurchuk),
this 'direct' method is competitive with the other methods.
``three_point`` *
The "Three Point" Abel transform method
exploits the observation that the value of the Abel inverted data
at any radial position r is primarily determined from changes
in the projection data in the neighborhood of r.
This method is also very efficient
once it has generated the basis sets.
Dasch, 1992 (Applied Optics, Vol 31, No 8, March 1992, Pg 1146-1152).
``*``
The methods marked with a * indicate methods that generate basis sets.
The first time they are run for a new image size,
it takes seconds to minutes to generate the basis set.
However, this basis set is saved to disk can can be reloaded,
meaning that future transforms are performed
much more quickly.
"""
abel_transform = {\
"basex" : abel.basex.basex_transform,
"direct" : abel.direct.direct_transform,
"hansenlaw" : abel.hansenlaw.hansenlaw_transform,
"three_point" : abel.three_point.three_point_transform,
}
verboseprint = print if verbose else lambda *a, **k: None
if IM.ndim == 1 or np.shape(IM)[0] <= 2:
raise ValueError('Data must be 2-dimensional. \
To transform a single row \
use the individual transform function.')
if not np.any(use_quadrants):
raise ValueError('No image quadrants selected to use')
rows, cols = np.shape(IM)
if not isinstance(symmetry_axis, (list, tuple)):
# if the user supplies an int, make it into a 1-element list:
symmetry_axis = [symmetry_axis]
if recast_as_float64:
IM = IM.astype('float64')
# centering:
if center == 'none': # no centering
if cols % 2 != 1:
raise ValueError('Image must have an odd number of columns. \
Use a centering method.')
else:
IM = abel.center_image(IM, center)
#########################
verboseprint('Calculating {0} Abel transform using {1} method -'.format(
direction, method), 'image size: {:d}x{:d}'.format(rows, cols))
t0 = time.time()
# split image into quadrants
Q0, Q1, Q2, Q3 = abel.tools.symmetry.get_image_quadrants(
IM, reorient=True, symmetry_axis=symmetry_axis)
def selected_transform(Z):
return abel_transform[method](Z, direction=direction,
**transform_options)
AQ0 = AQ1 = AQ2 = AQ3 = None
# Inverse Abel transform for quadrant 1 (all include Q1)
AQ1 = selected_transform(Q1)
if 0 in symmetry_axis:
AQ2 = selected_transform(Q2)
if 1 in symmetry_axis:
AQ0 = selected_transform(Q0)
if None in symmetry_axis:
AQ0 = selected_transform(Q0)
AQ2 = selected_transform(Q2)
AQ3 = selected_transform(Q3)
# reassemble image
results = {}
results['transform'] = abel.tools.symmetry.put_image_quadrants(
(AQ0, AQ1, AQ2, AQ3), odd_size=cols % 2,
symmetry_axis=symmetry_axis)
verboseprint("{:.2f} seconds".format(time.time()-t0))
return results
def main():
import matplotlib.pyplot as plt
IM0 = abel.tools.analytical.sample_image(n=201, name="dribinski")
IM1 = transform(IM0, direction='forward', center='com',
method='hansenlaw')['transform']
IM2 = transform(IM1, direction='inverse', method='basex')['transform']
fig, axs = plt.subplots(2, 3, figsize=(10, 6))
axs[0, 0].imshow(IM0)
axs[0, 1].imshow(IM1)
axs[0, 2].imshow(IM2)
axs[1, 0].plot(*abel.tools.vmi.angular_integration(IM0))
axs[1, 1].plot(*abel.tools.vmi.angular_integration(IM1))
axs[1, 2].plot(*abel.tools.vmi.angular_integration(IM2))
plt.show()
if __name__ == "__main__":
main()