mirror of
https://github.com/edoverg/MPID.git
synced 2026-10-14 23:26:38 +00:00
149 lines
5.3 KiB
Python
149 lines
5.3 KiB
Python
import autograd.numpy as npa
|
|
from numpy.typing import ArrayLike as ArrayLikeType
|
|
import matplotlib.pyplot as plt
|
|
|
|
filter_radius_um = 1
|
|
def _centered(arr: npa.ndarray, newshape: ArrayLikeType) -> npa.ndarray:
|
|
"""Formats the output of an FFT to center the zero-frequency component.
|
|
|
|
A helper function borrowed from SciPy:
|
|
https://github.com/scipy/scipy/blob/v1.4.1/scipy/signal/signaltools.py#L263-L270
|
|
|
|
Args:
|
|
arr: output array from an FFT operation.
|
|
newshape: 1d array with two elements (integers) specifying the dimensions
|
|
of the array to be returned.
|
|
|
|
Returns:
|
|
The input array with the zero-frequency component as the central element.
|
|
"""
|
|
newshape = npa.asarray(newshape)
|
|
currshape = npa.array(arr.shape)
|
|
startind = (currshape - newshape) // 2
|
|
endind = startind + newshape
|
|
myslice = [slice(startind[k], endind[k]) for k in range(len(endind))]
|
|
|
|
return arr[tuple(myslice)]
|
|
|
|
def _edge_pad(arr: npa.ndarray, pad: npa.ndarray) -> npa.ndarray:
|
|
"""Border-pads the edges of an array.
|
|
|
|
Used to preprocess the design weights prior to convolution with the filter.
|
|
Border padding an image will set the value of each padded pixel equal to
|
|
the value of the nearest pixel in the image. Used to implement feature-
|
|
preserving convolution filters that prevent unwanted edge effects.
|
|
|
|
Args:
|
|
arr: 2d array whose borders contain the values to use for padding
|
|
pad: 2x2 array of integers indicating the size
|
|
of the borders to pad the array with.
|
|
|
|
Returns:
|
|
A 2d array with border padding.
|
|
"""
|
|
# fill sides
|
|
left = npa.tile(arr[0, :], (pad[0][0], 1))
|
|
right = npa.tile(arr[-1, :], (pad[0][1], 1))
|
|
top = npa.tile(arr[:, 0], (pad[1][0], 1)).transpose()
|
|
bottom = npa.tile(arr[:, -1], (pad[1][1], 1)).transpose()
|
|
|
|
# fill corners
|
|
top_left = npa.tile(arr[0, 0], (pad[0][0], pad[1][0]))
|
|
top_right = npa.tile(arr[-1, 0], (pad[0][1], pad[1][0]))
|
|
bottom_left = npa.tile(arr[0, -1], (pad[0][0], pad[1][1]))
|
|
bottom_right = npa.tile(arr[-1, -1], (pad[0][1], pad[1][1]))
|
|
|
|
if pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
|
return npa.concatenate(
|
|
(
|
|
npa.concatenate((top_left, top, top_right)),
|
|
npa.concatenate((left, arr, right)),
|
|
npa.concatenate((bottom_left, bottom, bottom_right)),
|
|
),
|
|
axis=1,
|
|
)
|
|
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
|
return npa.concatenate((top, arr, bottom), axis=1)
|
|
elif pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
|
return npa.concatenate((left, arr, right), axis=0)
|
|
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
|
return arr
|
|
else:
|
|
raise ValueError("At least one of the padding numbers is invalid.")
|
|
|
|
def _quarter_to_full_kernel(arr: npa.ndarray, pad_to: npa.ndarray) -> npa.ndarray:
|
|
"""Constructs the full kernel from its nonnegative quadrant.
|
|
|
|
Args:
|
|
arr: 2d input array representing the nonnegative quadrant of a
|
|
filter kernel with C4v symmetry.
|
|
pad_to: 1d array with two elements (integers) specifying the size
|
|
of the zero padding.
|
|
|
|
Returns:
|
|
The complete kernel.
|
|
"""
|
|
pad_size = pad_to - 2 * npa.array(arr.shape) + 1
|
|
|
|
top = npa.zeros((pad_size[0], arr.shape[1]))
|
|
bottom = npa.zeros((pad_size[0], arr.shape[1] - 1))
|
|
middle = npa.zeros((pad_to[0], pad_size[1]))
|
|
|
|
top_left = arr[:, :]
|
|
top_right = npa.flipud(arr[1:, :])
|
|
bottom_left = npa.fliplr(arr[:, 1:])
|
|
bottom_right = npa.flipud(
|
|
npa.fliplr(arr[1:, 1:])
|
|
) # equivalent to flip, but flip is incompatible with autograd
|
|
|
|
return npa.concatenate(
|
|
(
|
|
npa.concatenate((top_left, top, top_right)),
|
|
middle,
|
|
npa.concatenate((bottom_left, bottom, bottom_right)),
|
|
),
|
|
axis=1,
|
|
)
|
|
|
|
def conic_filter(phase_mask,radius,space_grid):
|
|
'''Applies a conic filter to the input phase mask. The filter is defined as a
|
|
conic region of a specified radius, where the values are averaged.
|
|
|
|
Args:
|
|
phase_mask: the input phase mask to be filtered (2D)
|
|
radius: the radius of the conic filter in micrometers
|
|
space_grid: an object containing the size and resolution of the phase mask
|
|
|
|
Returns:
|
|
The filtered phase mask (2D)
|
|
'''
|
|
Nx = space_grid.get_Nx()
|
|
Ny = space_grid.get_Ny()
|
|
|
|
#positive quadrant of the filter
|
|
xv = npa.arange(0, Nx//2+1) * (space_grid.get_resolution())
|
|
yv = npa.arange(0, Ny//2+1) * (space_grid.get_resolution())
|
|
#note that the filter's center is at (0,0)
|
|
Xv, Yv = npa.meshgrid(xv, yv)
|
|
|
|
h = npa.where(Xv**2 + Yv**2 < radius**2, (1 - npa.sqrt(abs(Xv**2 + Yv**2)) / radius),0)
|
|
|
|
h = _quarter_to_full_kernel(h, 3 * npa.array([Nx, Ny]))
|
|
|
|
h = h / npa.sum(h)
|
|
#make a plot of the filter kernel
|
|
H = npa.fft.fft2(h)
|
|
|
|
phase_mask_2d = phase_mask.reshape(Nx,Ny)
|
|
phase_mask_padded = _edge_pad(phase_mask_2d, ((Nx, Nx), (Ny, Ny)))
|
|
|
|
phase_mask_fft = npa.fft.fft2(phase_mask_padded)
|
|
|
|
Y = phase_mask_fft * H
|
|
|
|
y = _centered(
|
|
npa.real(npa.fft.ifft2(Y)),
|
|
(Nx, Ny)
|
|
)
|
|
|
|
return y.flatten() |