Module pyms.utils.numpy_utils

Utility functions for working with the numpy library.

Expand source code
"""Utility functions for working with the numpy library."""
import numpy as np
import torch
import copy


def ensure_array(input):
    """Force a (potentially scalar) input to be an array."""
    if hasattr(input, "__len__") and not isinstance(input, str):
        return input
    else:
        return np.asarray([input])


def r_space_array(pixels, gridsize):
    """
    Return the appropriately scaled 2D real space coordinates.

    Parameters
    -----------
    pixels : (2,) array_like
        Pixels in each dimension of a 2D array
    gridsize : (2,) array_like
        Dimensions of the array in real space units
    """
    rspace = [np.fft.fftfreq(pixels[i], d=1 / gridsize[i]) for i in [0, 1]]
    return [
        np.broadcast_to(r, pixels) for r in [rspace[0][:, None], rspace[1][None, :]]
    ]


def q_space_array(pixels, gridsize):
    """
    Return the appropriately scaled 2D reciprocal space coordinates.

    Parameters
    -----------
    pixels : (2,) array_like
        Pixels in each dimension of a 2D array
    gridsize : (2,) array_like
        Dimensions of the array in real space units
    """
    qspace = [np.fft.fftfreq(pixels[i], d=gridsize[i] / pixels[i]) for i in [0, 1]]
    return [
        np.broadcast_to(q, pixels) for q in [qspace[0][:, None], qspace[1][None, :]]
    ]


def crop_window_to_flattened_indices(indices, shape):
    """Map  y and x indices describing a cropping window to a flattened 1d array."""
    return (
        indices[-1][None, :] % shape[-1]
        + (indices[-2][:, None] % shape[-2]) * shape[-1]
    ).ravel()


def crop_to_bandwidth_limit(
    array, limit=2 / 3, norm="conserve_L2", qspace_in=True, qspace_out=True
):
    """
    Crop an array to its bandwidth limit (ie remove superfluous array entries).

    assumes that input array is in Fourier space with zeroth Fourier component
    in upper-left corner
    """
    # New shape of final dimensions
    newshape = tuple([round(array.shape[-2 + i] * limit) for i in range(2)])

    return fourier_interpolate_2d(
        array, newshape, norm=norm, qspace_in=qspace_in, qspace_out=qspace_out
    )


def bandwidth_limit_array(arrayin, limit=2 / 3, qspace_in=True, qspace_out=True):
    """
    Band-width limit an array in Fourier space.

    Band width limiting of the propagator and transmission functions is necessary
    in multislice to prevent "aliasing", wrapping round of high-angle scattering
    due the periodic boundary conditions implicit in the multislice algorithm.
    See sec. 6.8 of "Kirkland's Advanced Computing in Electron Microscopy" for
    more detail.

    Parameters
    ----------
    arrayin : array_like (...,Ny,Nx)
        Array to be bandwidth limited.
    limit : float
        Bandwidth limit as a fraction of the maximum reciprocal space frequency
        of the array.
    qspace_in : bool, optional
        Set to True if the input array is in reciprocal space (default),
        False if not
    qspace_out : bool, optional
        Set to True for reciprocal space output (default), False for real-space
        output.
    Returns
    -------
    array : array_like (...,Ny,Nx)
        The bandwidth limit of the array
    """
    # Transform array to real space if necessary
    if qspace_in:
        array = copy.deepcopy(arrayin)
    else:
        array = np.fft.fft2(arrayin)

    # Case where band-width limiting has been turned off
    if limit is not None:
        if isinstance(array, np.ndarray):
            pixelsize = array.shape[:2]
            array[
                (
                    np.square(np.fft.fftfreq(pixelsize[0]))[:, np.newaxis]
                    + np.square(np.fft.fftfreq(pixelsize[1]))[np.newaxis, :]
                )
                * (2 / limit) ** 2
                >= 1
            ] = 0
        else:
            pixelsize = array.size()[:2]
            array[
                (
                    torch.from_numpy(np.fft.fftfreq(pixelsize[0]) ** 2).view(
                        pixelsize[0], 1
                    )
                    + torch.from_numpy(np.fft.fftfreq(pixelsize[1]) ** 2).view(
                        1, pixelsize[1]
                    )
                )
                * (2 / limit) ** 2
                >= 1
            ] = 0

    if qspace_out:
        return array
    else:
        return np.fft.ifft2(array)


def Fourier_interpolation_masks(npiyin, npixin, npiyout, npixout):
    """Calculate a mask of array entries to be included in Fourier interpolation."""
    # Construct input and output fft grids
    qyin, qxin, qyout, qxout = [
        (np.fft.fftfreq(x, 1 / x)).astype(np.int)
        for x in [npiyin, npixin, npiyout, npixout]
    ]

    # Get maximum and minimum common reciprocal space coordinates
    minqy, maxqy = [
        max(np.amin(qyin), np.amin(qyout)),
        min(np.amax(qyin), np.amax(qyout)),
    ]
    minqx, maxqx = [
        max(np.amin(qxin), np.amin(qxout)),
        min(np.amax(qxin), np.amax(qxout)),
    ]

    # Make 2d grids
    qqxout, qqyout = np.meshgrid(qxout, qyout)
    qqxin, qqyin = np.meshgrid(qxin, qyin)

    # Make a masks of common Fourier components for input and output arrays
    maskin = np.logical_and(
        np.logical_and(qqxin <= maxqx, qqxin >= minqx),
        np.logical_and(qqyin <= maxqy, qqyin >= minqy),
    )

    maskout = np.logical_and(
        np.logical_and(qqxout <= maxqx, qqxout >= minqx),
        np.logical_and(qqyout <= maxqy, qqyout >= minqy),
    )

    return maskin, maskout


def renormalize(array, oldmin=None, oldmax=None, newmax=1.0, newmin=0.0):
    """Rescales the array such that its maximum is newmax and its minimum is newmin."""
    if oldmin is not None:
        min_ = oldmin
    else:
        min_ = array.min()

    if oldmax is not None:
        max_ = oldmax
    else:
        max_ = array.max()

    return (
        np.clip((array - min_) / (max_ - min_), 0.0, 1.0) * (newmax - newmin) + newmin
    )


def convolve(array1, array2, axes=None):
    """
    Fourier convolution of two arrays over specified axes.

    array2 is broadcast to match array1 so axes refers to the dimensions of
    array1
    """
    # input and output shape
    s = array1.shape
    # Broadcast array2 to match array1
    a2 = np.broadcast_to(array2, s)
    # Axes of transformation
    a = axes
    if a is not None:
        s = [s[i] for i in a]
    if np.iscomplexobj(array1) or np.iscomplexobj(a2):
        return np.fft.ifftn(np.fft.fftn(array1, s, a) * np.fft.fftn(a2, s, a), s, a)
    else:
        return np.fft.irfftn(np.fft.rfftn(array1, s, a) * np.fft.rfftn(a2, s, a), s, a)


def colorize(z, saturation=0.8, minlightness=0.0, maxlightness=0.5):
    """
    Map a complex number to the hsl scale and output in RGB format.

    Parameters
    ----------
    z : complex, array_like
        Complex array to be plotted using hsl
    Saturation : float, optional
        (Uniform) saturation value of the hsl colormap
    minlightness, maxlightness : float, optional
        The amplitude of the complex array z will be mapped to the lightness of
        the output hsl map. These keyword arguments allow control over the range
        of lightness values in the map
    """
    from colorsys import hls_to_rgb

    # Get phase an amplitude of complex array
    r = np.abs(z)
    arg = np.angle(z)

    # Calculate hue, lightness and saturation
    h = arg / (2 * np.pi)
    ell = renormalize(r, newmin=minlightness, newmax=maxlightness)
    s = saturation

    # Convert HLS format to RGB format
    c = np.vectorize(hls_to_rgb)(h, ell, s)  # --> tuple
    # Convert to numpy array
    c = np.array(c)  # -->
    # Array has shape (3,n,m), but we need (n,m,3) for output, range needs to be
    # from 0 to 256
    c = (c.swapaxes(0, 2) * 256).astype(np.uint8)
    return c


def fourier_interpolate_2d(
    ain, shapeout, norm="conserve_val", qspace_in=False, qspace_out=False
):
    """
    Perfom fourier interpolation on array ain so that its shape matches shapeout.

    Arguments
    ---------
    ain : (...,Ny,Nx) array_like
        Input numpy array
    shapeout : int (2,) , array_like
        Desired shape of output array
    norm : str, optional  {'conserve_val','conserve_norm','conserve_L1'}
        Normalization of output. If 'conserve_val' then array values are preserved
        if 'conserve_norm' L2 norm is conserved under interpolation and if
        'conserve_L1' L1 norm is conserved under interpolation
    qspace_in : bool, optional
        Set to True if the input array is in reciprocal space, False if not (default).
        Be careful with setting this to True for a non-complex array.
    qspace_out : bool, optional
        Set to True for reciprocal space output, False for real-space output (default).
    """
    # Import required FFT functions
    from numpy.fft import fft2

    # Make input complex
    aout = np.zeros(np.shape(ain)[:-2] + tuple(shapeout), dtype=np.complex)

    # Get input dimensions
    npiyin, npixin = np.shape(ain)[-2:]
    npiyout, npixout = shapeout

    # Get Fourier interpolation masks
    maskin, maskout = Fourier_interpolation_masks(npiyin, npixin, npiyout, npixout)

    if qspace_in:
        a = np.asarray(ain, dtype=np.complex)
    else:
        a = fft2(np.asarray(ain, dtype=np.complex))

    # Now transfer over Fourier coefficients from input to output array
    aout[..., maskout] = a[..., maskin]

    # Fourier transform result with appropriate normalization
    if norm == "conserve_val":
        aout *= np.prod(shapeout) / np.prod(np.shape(ain)[-2:])
    elif norm == "conserve_norm":
        aout *= np.sqrt(np.prod(shapeout) / np.prod(np.shape(ain)[-2:]))

    if not qspace_out:
        aout = np.fft.ifftn(aout, axes=[-2, -1])

    # Return correct array data type
    if not np.iscomplexobj(ain):
        return np.real(aout)
    else:
        return aout


def oned_shift(N, shift, pixel_units=True):
    """
    Construct a one-dimensional shift array.

    Parameters
    ----------
    N     -- Number of pixels in the shift array
    shift -- Amount of shift to be achieved (default units of pixels)

    Keyword arguments
    ----------
    pixel_units -- Pass True if shift is to be units of pixels, False for
                   fraction of the array
    """
    # Create the Fourier space pixel coordinates of the shift array
    shiftarray = (np.arange(N) + N // 2) % N - N // 2

    # Conversion necessary if the shift is in units of pixels, and not fractions
    # of the array
    if pixel_units:
        shiftarray = shiftarray / N

    # The shift array is given mathematically as e^(-2pi i k Delta x) and this
    # is what is returned.
    return np.exp(-2 * np.pi * 1j * shiftarray * shift)


def fourier_shift(arrayin, shift, qspacein=False, qspaceout=False, pixel_units=True):
    """
    Shifts a 2d array using the Fourier shift theorem.

    Parameters
    ----------
    arrayin -- Array to be Fourier shifted
    shift   -- Shift in units of pixels (pass pixel_units = False for shift
               to be in units of fraction of the array size)

    Keyword arguments
    ----------
    qspacein    -- Pass True if arrayin is in Fourier space
    qspaceout   -- Pass True for Fourier space output, False (default) for
                   real space output
    pixel_units -- Pass True if shift is to be units of pixels, False for
                   fraction of the array
    """
    # Construct shift array
    shifty, shiftx = [
        oned_shift(arrayin.shape[-2 + i], shift[i], pixel_units) for i in range(2)
    ]

    # Now Fourier transform array and apply shift
    real = not np.iscomplexobj(arrayin)

    if real:
        array = np.asarray(arrayin, dtype=np.complex)
    else:
        array = arrayin

    if not qspacein:
        array = np.fft.fft2(array)

    array = shiftx[np.newaxis, :] * shifty[:, np.newaxis] * array

    if not qspaceout:
        array = np.fft.ifft2(array)

    if real:
        return np.real(array)
    else:
        return array


def add_noise(arrayin, Total_counts):
    """
    Add Poisson counting noise to simulated data.

    Parameters
    ----------
    arrayin : array_like
        Array giving the fraction of Total_counts that is expected at each pixel
        in the array.
    Total_counts : float
        Total number of electron counts expected over the array.
    """
    return np.random.poisson(arrayin * Total_counts)


def crop(arrayin, shapeout):
    """
    Crop the last two dimensions of arrayin to grid size shapeout.

    For entries of shapeout which are larger than the shape of the input array,
    perform zero-padding.

    Parameters
    ----------
    arrayin : (...,Ny,Nx) array_like
        Array to be cropped or zero-padded.
    shapeout : (2,) array_like
        Desired output shape of the final two dimensions of arrayin
    """
    # Number of dimensions in input array
    ndim = arrayin.ndim

    # Number of dimensions not covered by shapeout (ie not to be cropped)
    nUntouched = ndim - 2

    # Shape of output array
    shapeout_ = arrayin.shape[:nUntouched] + tuple(shapeout)

    arrayout = np.zeros(shapeout_, dtype=arrayin.dtype)

    y, x = arrayin.shape[-2:]
    y_, x_ = shapeout[-2:]

    def indices(y, y_):
        if y > y_:
            # Crop in y dimension
            y1, y2 = [(y - y_) // 2, (y + y_) // 2]
            y1_, y2_ = [0, y_]
        else:
            # Zero pad in y dimension
            y1, y2 = [0, y]
            y1_, y2_ = [(y_ - y) // 2, (y + y_) // 2]
        return y1, y2, y1_, y2_

    y1, y2, y1_, y2_ = indices(y, y_)
    x1, x2, x1_, x2_ = indices(x, x_)

    arrayout[..., y1_:y2_, x1_:x2_] = arrayin[..., y1:y2, x1:x2]
    return arrayout


def Gaussian(sigma, gridshape, rsize, theta=0):
    r"""
    Calculate a normalized 2D Gaussian function.

    Notes
    -----
    Functional form
    .. math:: 1 / \sqrt { 2 \pi \sigma }  e^{ - ( x^2 + y^2 ) / 2 / \sigma^2 }

    Parameters
    ----------
    sigma : float or (2,) array_like
        The standard deviation of the Gaussian function, if an array is provided
        then the first two entries will give the y and x standard deviation of
        the Gaussian.
    gridshape : (2,) array_like
        Number of pixels in the grid.
    rsize : (2,) array_like
        Size of the grid in units of length
    theta : float, optional
        Angle of the two dimensional Gaussian function.
    """
    if isinstance(sigma, (list, tuple, np.ndarray)):
        sigmay, sigmax = sigma[:2]
    else:
        sigmax = sigma
        sigmay = sigma
    grid = r_space_array(gridshape, rsize)
    a = np.cos(theta) ** 2 / (2 * sigmax ** 2) + np.sin(theta) ** 2 / (2 * sigmay ** 2)
    b = -np.sin(2 * theta) / (4 * sigmax ** 2) + np.sin(2 * theta) / (4 * sigmay ** 2)
    c = np.sin(theta) ** 2 / (2 * sigmax ** 2) + np.cos(theta) ** 2 / (2 * sigmay ** 2)
    gaussian = np.exp(
        -(a * grid[1] ** 2 + 2 * b * grid[0] * grid[1] + c * grid[0] ** 2)
    )
    return gaussian / np.sum(gaussian)

Functions

def Fourier_interpolation_masks(npiyin, npixin, npiyout, npixout)

Calculate a mask of array entries to be included in Fourier interpolation.

Expand source code
def Fourier_interpolation_masks(npiyin, npixin, npiyout, npixout):
    """Calculate a mask of array entries to be included in Fourier interpolation."""
    # Construct input and output fft grids
    qyin, qxin, qyout, qxout = [
        (np.fft.fftfreq(x, 1 / x)).astype(np.int)
        for x in [npiyin, npixin, npiyout, npixout]
    ]

    # Get maximum and minimum common reciprocal space coordinates
    minqy, maxqy = [
        max(np.amin(qyin), np.amin(qyout)),
        min(np.amax(qyin), np.amax(qyout)),
    ]
    minqx, maxqx = [
        max(np.amin(qxin), np.amin(qxout)),
        min(np.amax(qxin), np.amax(qxout)),
    ]

    # Make 2d grids
    qqxout, qqyout = np.meshgrid(qxout, qyout)
    qqxin, qqyin = np.meshgrid(qxin, qyin)

    # Make a masks of common Fourier components for input and output arrays
    maskin = np.logical_and(
        np.logical_and(qqxin <= maxqx, qqxin >= minqx),
        np.logical_and(qqyin <= maxqy, qqyin >= minqy),
    )

    maskout = np.logical_and(
        np.logical_and(qqxout <= maxqx, qqxout >= minqx),
        np.logical_and(qqyout <= maxqy, qqyout >= minqy),
    )

    return maskin, maskout
def Gaussian(sigma, gridshape, rsize, theta=0)

Calculate a normalized 2D Gaussian function.

Notes

Functional form [ ] Parameters


sigma : float or (2,) array_like
The standard deviation of the Gaussian function, if an array is provided then the first two entries will give the y and x standard deviation of the Gaussian.
gridshape : (2,) array_like
Number of pixels in the grid.
rsize : (2,) array_like
Size of the grid in units of length
theta : float, optional
Angle of the two dimensional Gaussian function.
Expand source code
def Gaussian(sigma, gridshape, rsize, theta=0):
    r"""
    Calculate a normalized 2D Gaussian function.

    Notes
    -----
    Functional form
    .. math:: 1 / \sqrt { 2 \pi \sigma }  e^{ - ( x^2 + y^2 ) / 2 / \sigma^2 }

    Parameters
    ----------
    sigma : float or (2,) array_like
        The standard deviation of the Gaussian function, if an array is provided
        then the first two entries will give the y and x standard deviation of
        the Gaussian.
    gridshape : (2,) array_like
        Number of pixels in the grid.
    rsize : (2,) array_like
        Size of the grid in units of length
    theta : float, optional
        Angle of the two dimensional Gaussian function.
    """
    if isinstance(sigma, (list, tuple, np.ndarray)):
        sigmay, sigmax = sigma[:2]
    else:
        sigmax = sigma
        sigmay = sigma
    grid = r_space_array(gridshape, rsize)
    a = np.cos(theta) ** 2 / (2 * sigmax ** 2) + np.sin(theta) ** 2 / (2 * sigmay ** 2)
    b = -np.sin(2 * theta) / (4 * sigmax ** 2) + np.sin(2 * theta) / (4 * sigmay ** 2)
    c = np.sin(theta) ** 2 / (2 * sigmax ** 2) + np.cos(theta) ** 2 / (2 * sigmay ** 2)
    gaussian = np.exp(
        -(a * grid[1] ** 2 + 2 * b * grid[0] * grid[1] + c * grid[0] ** 2)
    )
    return gaussian / np.sum(gaussian)
def add_noise(arrayin, Total_counts)

Add Poisson counting noise to simulated data.

Parameters

arrayin : array_like
Array giving the fraction of Total_counts that is expected at each pixel in the array.
Total_counts : float
Total number of electron counts expected over the array.
Expand source code
def add_noise(arrayin, Total_counts):
    """
    Add Poisson counting noise to simulated data.

    Parameters
    ----------
    arrayin : array_like
        Array giving the fraction of Total_counts that is expected at each pixel
        in the array.
    Total_counts : float
        Total number of electron counts expected over the array.
    """
    return np.random.poisson(arrayin * Total_counts)
def bandwidth_limit_array(arrayin, limit=0.6666666666666666, qspace_in=True, qspace_out=True)

Band-width limit an array in Fourier space.

Band width limiting of the propagator and transmission functions is necessary in multislice to prevent "aliasing", wrapping round of high-angle scattering due the periodic boundary conditions implicit in the multislice algorithm. See sec. 6.8 of "Kirkland's Advanced Computing in Electron Microscopy" for more detail.

Parameters

arrayin : array_like (…,Ny,Nx)
Array to be bandwidth limited.
limit : float
Bandwidth limit as a fraction of the maximum reciprocal space frequency of the array.
qspace_in : bool, optional
Set to True if the input array is in reciprocal space (default), False if not
qspace_out : bool, optional
Set to True for reciprocal space output (default), False for real-space output.

Returns

array : array_like (…,Ny,Nx)
The bandwidth limit of the array
Expand source code
def bandwidth_limit_array(arrayin, limit=2 / 3, qspace_in=True, qspace_out=True):
    """
    Band-width limit an array in Fourier space.

    Band width limiting of the propagator and transmission functions is necessary
    in multislice to prevent "aliasing", wrapping round of high-angle scattering
    due the periodic boundary conditions implicit in the multislice algorithm.
    See sec. 6.8 of "Kirkland's Advanced Computing in Electron Microscopy" for
    more detail.

    Parameters
    ----------
    arrayin : array_like (...,Ny,Nx)
        Array to be bandwidth limited.
    limit : float
        Bandwidth limit as a fraction of the maximum reciprocal space frequency
        of the array.
    qspace_in : bool, optional
        Set to True if the input array is in reciprocal space (default),
        False if not
    qspace_out : bool, optional
        Set to True for reciprocal space output (default), False for real-space
        output.
    Returns
    -------
    array : array_like (...,Ny,Nx)
        The bandwidth limit of the array
    """
    # Transform array to real space if necessary
    if qspace_in:
        array = copy.deepcopy(arrayin)
    else:
        array = np.fft.fft2(arrayin)

    # Case where band-width limiting has been turned off
    if limit is not None:
        if isinstance(array, np.ndarray):
            pixelsize = array.shape[:2]
            array[
                (
                    np.square(np.fft.fftfreq(pixelsize[0]))[:, np.newaxis]
                    + np.square(np.fft.fftfreq(pixelsize[1]))[np.newaxis, :]
                )
                * (2 / limit) ** 2
                >= 1
            ] = 0
        else:
            pixelsize = array.size()[:2]
            array[
                (
                    torch.from_numpy(np.fft.fftfreq(pixelsize[0]) ** 2).view(
                        pixelsize[0], 1
                    )
                    + torch.from_numpy(np.fft.fftfreq(pixelsize[1]) ** 2).view(
                        1, pixelsize[1]
                    )
                )
                * (2 / limit) ** 2
                >= 1
            ] = 0

    if qspace_out:
        return array
    else:
        return np.fft.ifft2(array)
def colorize(z, saturation=0.8, minlightness=0.0, maxlightness=0.5)

Map a complex number to the hsl scale and output in RGB format.

Parameters

z : complex, array_like
Complex array to be plotted using hsl
Saturation : float, optional
(Uniform) saturation value of the hsl colormap
minlightness, maxlightness : float, optional
The amplitude of the complex array z will be mapped to the lightness of the output hsl map. These keyword arguments allow control over the range of lightness values in the map
Expand source code
def colorize(z, saturation=0.8, minlightness=0.0, maxlightness=0.5):
    """
    Map a complex number to the hsl scale and output in RGB format.

    Parameters
    ----------
    z : complex, array_like
        Complex array to be plotted using hsl
    Saturation : float, optional
        (Uniform) saturation value of the hsl colormap
    minlightness, maxlightness : float, optional
        The amplitude of the complex array z will be mapped to the lightness of
        the output hsl map. These keyword arguments allow control over the range
        of lightness values in the map
    """
    from colorsys import hls_to_rgb

    # Get phase an amplitude of complex array
    r = np.abs(z)
    arg = np.angle(z)

    # Calculate hue, lightness and saturation
    h = arg / (2 * np.pi)
    ell = renormalize(r, newmin=minlightness, newmax=maxlightness)
    s = saturation

    # Convert HLS format to RGB format
    c = np.vectorize(hls_to_rgb)(h, ell, s)  # --> tuple
    # Convert to numpy array
    c = np.array(c)  # -->
    # Array has shape (3,n,m), but we need (n,m,3) for output, range needs to be
    # from 0 to 256
    c = (c.swapaxes(0, 2) * 256).astype(np.uint8)
    return c
def convolve(array1, array2, axes=None)

Fourier convolution of two arrays over specified axes.

array2 is broadcast to match array1 so axes refers to the dimensions of array1

Expand source code
def convolve(array1, array2, axes=None):
    """
    Fourier convolution of two arrays over specified axes.

    array2 is broadcast to match array1 so axes refers to the dimensions of
    array1
    """
    # input and output shape
    s = array1.shape
    # Broadcast array2 to match array1
    a2 = np.broadcast_to(array2, s)
    # Axes of transformation
    a = axes
    if a is not None:
        s = [s[i] for i in a]
    if np.iscomplexobj(array1) or np.iscomplexobj(a2):
        return np.fft.ifftn(np.fft.fftn(array1, s, a) * np.fft.fftn(a2, s, a), s, a)
    else:
        return np.fft.irfftn(np.fft.rfftn(array1, s, a) * np.fft.rfftn(a2, s, a), s, a)
def crop(arrayin, shapeout)

Crop the last two dimensions of arrayin to grid size shapeout.

For entries of shapeout which are larger than the shape of the input array, perform zero-padding.

Parameters

arrayin : (…,Ny,Nx) array_like
Array to be cropped or zero-padded.
shapeout : (2,) array_like
Desired output shape of the final two dimensions of arrayin
Expand source code
def crop(arrayin, shapeout):
    """
    Crop the last two dimensions of arrayin to grid size shapeout.

    For entries of shapeout which are larger than the shape of the input array,
    perform zero-padding.

    Parameters
    ----------
    arrayin : (...,Ny,Nx) array_like
        Array to be cropped or zero-padded.
    shapeout : (2,) array_like
        Desired output shape of the final two dimensions of arrayin
    """
    # Number of dimensions in input array
    ndim = arrayin.ndim

    # Number of dimensions not covered by shapeout (ie not to be cropped)
    nUntouched = ndim - 2

    # Shape of output array
    shapeout_ = arrayin.shape[:nUntouched] + tuple(shapeout)

    arrayout = np.zeros(shapeout_, dtype=arrayin.dtype)

    y, x = arrayin.shape[-2:]
    y_, x_ = shapeout[-2:]

    def indices(y, y_):
        if y > y_:
            # Crop in y dimension
            y1, y2 = [(y - y_) // 2, (y + y_) // 2]
            y1_, y2_ = [0, y_]
        else:
            # Zero pad in y dimension
            y1, y2 = [0, y]
            y1_, y2_ = [(y_ - y) // 2, (y + y_) // 2]
        return y1, y2, y1_, y2_

    y1, y2, y1_, y2_ = indices(y, y_)
    x1, x2, x1_, x2_ = indices(x, x_)

    arrayout[..., y1_:y2_, x1_:x2_] = arrayin[..., y1:y2, x1:x2]
    return arrayout
def crop_to_bandwidth_limit(array, limit=0.6666666666666666, norm='conserve_L2', qspace_in=True, qspace_out=True)

Crop an array to its bandwidth limit (ie remove superfluous array entries).

assumes that input array is in Fourier space with zeroth Fourier component in upper-left corner

Expand source code
def crop_to_bandwidth_limit(
    array, limit=2 / 3, norm="conserve_L2", qspace_in=True, qspace_out=True
):
    """
    Crop an array to its bandwidth limit (ie remove superfluous array entries).

    assumes that input array is in Fourier space with zeroth Fourier component
    in upper-left corner
    """
    # New shape of final dimensions
    newshape = tuple([round(array.shape[-2 + i] * limit) for i in range(2)])

    return fourier_interpolate_2d(
        array, newshape, norm=norm, qspace_in=qspace_in, qspace_out=qspace_out
    )
def crop_window_to_flattened_indices(indices, shape)

Map y and x indices describing a cropping window to a flattened 1d array.

Expand source code
def crop_window_to_flattened_indices(indices, shape):
    """Map  y and x indices describing a cropping window to a flattened 1d array."""
    return (
        indices[-1][None, :] % shape[-1]
        + (indices[-2][:, None] % shape[-2]) * shape[-1]
    ).ravel()
def ensure_array(input)

Force a (potentially scalar) input to be an array.

Expand source code
def ensure_array(input):
    """Force a (potentially scalar) input to be an array."""
    if hasattr(input, "__len__") and not isinstance(input, str):
        return input
    else:
        return np.asarray([input])
def fourier_interpolate_2d(ain, shapeout, norm='conserve_val', qspace_in=False, qspace_out=False)

Perfom fourier interpolation on array ain so that its shape matches shapeout.

Arguments

ain : (…,Ny,Nx) array_like
Input numpy array
shapeout : int (2,) , array_like
Desired shape of output array
norm : str, optional {'conserve_val','conserve_norm','conserve_L1'}
Normalization of output. If 'conserve_val' then array values are preserved if 'conserve_norm' L2 norm is conserved under interpolation and if 'conserve_L1' L1 norm is conserved under interpolation
qspace_in : bool, optional
Set to True if the input array is in reciprocal space, False if not (default). Be careful with setting this to True for a non-complex array.
qspace_out : bool, optional
Set to True for reciprocal space output, False for real-space output (default).
Expand source code
def fourier_interpolate_2d(
    ain, shapeout, norm="conserve_val", qspace_in=False, qspace_out=False
):
    """
    Perfom fourier interpolation on array ain so that its shape matches shapeout.

    Arguments
    ---------
    ain : (...,Ny,Nx) array_like
        Input numpy array
    shapeout : int (2,) , array_like
        Desired shape of output array
    norm : str, optional  {'conserve_val','conserve_norm','conserve_L1'}
        Normalization of output. If 'conserve_val' then array values are preserved
        if 'conserve_norm' L2 norm is conserved under interpolation and if
        'conserve_L1' L1 norm is conserved under interpolation
    qspace_in : bool, optional
        Set to True if the input array is in reciprocal space, False if not (default).
        Be careful with setting this to True for a non-complex array.
    qspace_out : bool, optional
        Set to True for reciprocal space output, False for real-space output (default).
    """
    # Import required FFT functions
    from numpy.fft import fft2

    # Make input complex
    aout = np.zeros(np.shape(ain)[:-2] + tuple(shapeout), dtype=np.complex)

    # Get input dimensions
    npiyin, npixin = np.shape(ain)[-2:]
    npiyout, npixout = shapeout

    # Get Fourier interpolation masks
    maskin, maskout = Fourier_interpolation_masks(npiyin, npixin, npiyout, npixout)

    if qspace_in:
        a = np.asarray(ain, dtype=np.complex)
    else:
        a = fft2(np.asarray(ain, dtype=np.complex))

    # Now transfer over Fourier coefficients from input to output array
    aout[..., maskout] = a[..., maskin]

    # Fourier transform result with appropriate normalization
    if norm == "conserve_val":
        aout *= np.prod(shapeout) / np.prod(np.shape(ain)[-2:])
    elif norm == "conserve_norm":
        aout *= np.sqrt(np.prod(shapeout) / np.prod(np.shape(ain)[-2:]))

    if not qspace_out:
        aout = np.fft.ifftn(aout, axes=[-2, -1])

    # Return correct array data type
    if not np.iscomplexobj(ain):
        return np.real(aout)
    else:
        return aout
def fourier_shift(arrayin, shift, qspacein=False, qspaceout=False, pixel_units=True)

Shifts a 2d array using the Fourier shift theorem.

Parameters

arrayin – Array to be Fourier shifted shift – Shift in units of pixels (pass pixel_units = False for shift to be in units of fraction of the array size)

Keyword Arguments

qspacein – Pass True if arrayin is in Fourier space qspaceout – Pass True for Fourier space output, False (default) for real space output pixel_units – Pass True if shift is to be units of pixels, False for fraction of the array

Expand source code
def fourier_shift(arrayin, shift, qspacein=False, qspaceout=False, pixel_units=True):
    """
    Shifts a 2d array using the Fourier shift theorem.

    Parameters
    ----------
    arrayin -- Array to be Fourier shifted
    shift   -- Shift in units of pixels (pass pixel_units = False for shift
               to be in units of fraction of the array size)

    Keyword arguments
    ----------
    qspacein    -- Pass True if arrayin is in Fourier space
    qspaceout   -- Pass True for Fourier space output, False (default) for
                   real space output
    pixel_units -- Pass True if shift is to be units of pixels, False for
                   fraction of the array
    """
    # Construct shift array
    shifty, shiftx = [
        oned_shift(arrayin.shape[-2 + i], shift[i], pixel_units) for i in range(2)
    ]

    # Now Fourier transform array and apply shift
    real = not np.iscomplexobj(arrayin)

    if real:
        array = np.asarray(arrayin, dtype=np.complex)
    else:
        array = arrayin

    if not qspacein:
        array = np.fft.fft2(array)

    array = shiftx[np.newaxis, :] * shifty[:, np.newaxis] * array

    if not qspaceout:
        array = np.fft.ifft2(array)

    if real:
        return np.real(array)
    else:
        return array
def oned_shift(N, shift, pixel_units=True)

Construct a one-dimensional shift array.

Parameters

N – Number of pixels in the shift array shift – Amount of shift to be achieved (default units of pixels)

Keyword Arguments

pixel_units – Pass True if shift is to be units of pixels, False for fraction of the array

Expand source code
def oned_shift(N, shift, pixel_units=True):
    """
    Construct a one-dimensional shift array.

    Parameters
    ----------
    N     -- Number of pixels in the shift array
    shift -- Amount of shift to be achieved (default units of pixels)

    Keyword arguments
    ----------
    pixel_units -- Pass True if shift is to be units of pixels, False for
                   fraction of the array
    """
    # Create the Fourier space pixel coordinates of the shift array
    shiftarray = (np.arange(N) + N // 2) % N - N // 2

    # Conversion necessary if the shift is in units of pixels, and not fractions
    # of the array
    if pixel_units:
        shiftarray = shiftarray / N

    # The shift array is given mathematically as e^(-2pi i k Delta x) and this
    # is what is returned.
    return np.exp(-2 * np.pi * 1j * shiftarray * shift)
def q_space_array(pixels, gridsize)

Return the appropriately scaled 2D reciprocal space coordinates.

Parameters

pixels : (2,) array_like
Pixels in each dimension of a 2D array
gridsize : (2,) array_like
Dimensions of the array in real space units
Expand source code
def q_space_array(pixels, gridsize):
    """
    Return the appropriately scaled 2D reciprocal space coordinates.

    Parameters
    -----------
    pixels : (2,) array_like
        Pixels in each dimension of a 2D array
    gridsize : (2,) array_like
        Dimensions of the array in real space units
    """
    qspace = [np.fft.fftfreq(pixels[i], d=gridsize[i] / pixels[i]) for i in [0, 1]]
    return [
        np.broadcast_to(q, pixels) for q in [qspace[0][:, None], qspace[1][None, :]]
    ]
def r_space_array(pixels, gridsize)

Return the appropriately scaled 2D real space coordinates.

Parameters

pixels : (2,) array_like
Pixels in each dimension of a 2D array
gridsize : (2,) array_like
Dimensions of the array in real space units
Expand source code
def r_space_array(pixels, gridsize):
    """
    Return the appropriately scaled 2D real space coordinates.

    Parameters
    -----------
    pixels : (2,) array_like
        Pixels in each dimension of a 2D array
    gridsize : (2,) array_like
        Dimensions of the array in real space units
    """
    rspace = [np.fft.fftfreq(pixels[i], d=1 / gridsize[i]) for i in [0, 1]]
    return [
        np.broadcast_to(r, pixels) for r in [rspace[0][:, None], rspace[1][None, :]]
    ]
def renormalize(array, oldmin=None, oldmax=None, newmax=1.0, newmin=0.0)

Rescales the array such that its maximum is newmax and its minimum is newmin.

Expand source code
def renormalize(array, oldmin=None, oldmax=None, newmax=1.0, newmin=0.0):
    """Rescales the array such that its maximum is newmax and its minimum is newmin."""
    if oldmin is not None:
        min_ = oldmin
    else:
        min_ = array.min()

    if oldmax is not None:
        max_ = oldmax
    else:
        max_ = array.max()

    return (
        np.clip((array - min_) / (max_ - min_), 0.0, 1.0) * (newmax - newmin) + newmin
    )