Example #1
0
def _unequal_spacing_conv_core(psf1, psf2):
    '''Interpolates psf2 before using fft-based convolution

    Args:
        psf1 (prysm.PSF): PSF.  This one defines the sampling of the output.

        psf2 (prysm.PSF): PSF.  This one will have its frequency response
            truncated.

    Returns:
        PSF: a new `PSF` that is the convolution of psf1 and psf2.

    '''
    # map psf1 into the fourier domain
    ft1 = fft2(fftshift(psf1.data))
    unit1x = forward_ft_unit(psf1.sample_spacing, psf1.samples_x)
    unit1y = forward_ft_unit(psf1.sample_spacing, psf1.samples_y)
    # map psf2 into the fourier domain
    ft2 = fft2(fftshift(psf2.data))
    unit2x = forward_ft_unit(psf2.sample_spacing, psf2.samples_x)
    unit2y = forward_ft_unit(psf2.sample_spacing, psf2.samples_y)
    ft3 = ifftshift(resample_2d_complex(fftshift(ft2), (unit2y, unit2x), (unit1y, unit1x)))
    psf3 = PSF(data=abs(ifftshift(ifft2(ft1 * ft3))),
               sample_spacing=psf1.sample_spacing)
    return psf3._renorm()
Example #2
0
def mtf():
    x, y = forward_ft_unit(1 / 1e3, 128), forward_ft_unit(1 / 1e3, 128)
    xx, yy = np.meshgrid(x, y)
    dat = np.sin(xx)
    return otf.MTF(
        data=dat,
        unit_x=x)  # do not pass unit_y, simultaneous test for unit_y=None
Example #3
0
    def conv(self, psf2):
        '''Convolves this PSF with another

        Args:
            psf2 (`PSF`): PSf to convolve with this one.

        Returns:
            PSF:  A new `PSF` that is the convolution of these two PSFs.

        Notes:
            output PSF has equal sampling to whichever PSF has a lower nyquist
                frequency.

        '''
        try:
            psf_ft = fftshift(fft2(self.data))
            psf_unit_x = forward_ft_unit(self.sample_spacing, self.samples_x)
            psf_unit_y = forward_ft_unit(self.sample_spacing, self.samples_y)
            psf2_ft = psf2.analytic_ft(psf_unit_x, psf_unit_y)
            psf3 = PSF(data=abs(ifft2(psf_ft * psf2_ft)),
                       sample_spacing=self.sample_spacing)
            return psf3._renorm()
        except AttributeError:  # no analytic FT on the PSF/subclass
            print('No analytic FT, falling back to numerical approach.')
            return convpsf(self, psf2)
Example #4
0
def test_otf_calc_correct():
    x, y = forward_ft_unit(1 / 1e3, 128), forward_ft_unit(1 / 1e3, 128)
    xx, yy = np.meshgrid(x, y)
    dat = np.sin(xx)
    otf_ = otf.otf_from_psf(dat, x[1] - x[0])
    center = tuple(s // 2 for s in otf_.shape)
    assert otf_.data[center] == 1 + 0j
Example #5
0
    def from_psf(psf):
        ''' Generates an MTF from a PSF.

        Args:
            psf (:class:`PSF`): PSF to compute an MTF from.

        Returns:
            :class:`MTF`: A new MTF instance.

        '''
        dat = abs(fftshift(fft2(psf.data)))
        unit_x = forward_ft_unit(psf.sample_spacing, psf.samples_x)
        unit_y = forward_ft_unit(psf.sample_spacing, psf.samples_y)
        return MTF(dat / dat[psf.center_x, psf.center_y], unit_x, unit_y)
Example #6
0
    def show_fourier(self, interp_method='lanczos', fig=None, ax=None):
        ''' Displays the fourier transform of the image.

        Args:
            interp_method (`string`): method used to interpolate the data
                for display.

            fig (`matplotlib.figure`): figure to plot in.

            ax (`matplotlib.axis`): axis to plot in.

        Returns:
            `tuple` containing:

                `matplotlib.figure`: figure containing the plot.

                `matplotlib.axis`: axis containing the plot.

        '''
        dat = abs(fftshift(fft2(pad2d(self.data))))
        dat /= dat.max()
        unit_x = forward_ft_unit(self.sample_spacing, self.samples_x)
        unit_y = forward_ft_unit(self.sample_spacing, self.samples_y)
        xmin, xmax = unit_x[0], unit_x[-1]
        ymin, ymax = unit_y[0], unit_y[-1]

        fig, ax = share_fig_ax(fig, ax)
        im = ax.imshow(dat**0.1,
                       extent=[xmin, xmax, ymin, ymax],
                       cmap='Greys_r',
                       interpolation=interp_method,
                       origin='lower')
        fig.colorbar(im)
        ax.set(xlabel='Spatial Frequency X [cy/mm]',
               ylabel='Spatial Frequency Y [cy/mm]',
               title='Normalized FT of image, to 0.1 power')
        return fig, ax
Example #7
0
def mtf():
    x, y = forward_ft_unit(1/1e3, 128), forward_ft_unit(1/1e3, 128)
    xx, yy = np.meshgrid(x, y)
    dat = np.sin(xx)
    return otf.MTF(data=dat, x=x, y=y)
Example #8
0
    def __init__(self,
                 ifn,
                 Nact=50,
                 sep=10,
                 shift=(0, 0),
                 rot=(0, 0, 0),
                 upsample=1,
                 spline_order=3,
                 mask=None):
        """Create a new DM model.

        This model is based on convolution of a 'poke lattice' with the influence
        function.  It has the following idiosyncracies:

            1.  The poke lattice is always "FFT centered" on the array, i.e.
                centered on the sample which would contain the DC frequency bin
                after an FFT.
            2.  The rotation is applied in the same sampling as ifn
            3.  Shifts and resizing are applied using a Fourier method and not
                subject to quantization

        Parameters
        ----------
        ifn : numpy.ndarray
            influence function; assumes the same for all actuators and must
            be the same shape as (x,y).  Assumed centered on N//2th sample of x, y.
            Assumed to be well-conditioned for use in convolution, i.e.
            compact compared to the array holding it
        Nact : int or tuple of int, length 2
            (X, Y) actuator counts
        sep : int or tuple of int, length 2
            (X, Y) actuator separation, samples of influence function
        shift : tuple of float, length 2
            (X, Y) shift of the actuator grid to (x, y), units of x influence
            function sampling.  E.g., influence function on 0.1 mm grid, shift=1
            = 0.1 mm shift.  Positive numbers describe (rightward, downward)
            shifts in image coordinates (origin lower left).
        rot : tuple of int, length <= 3
            (Z, Y, X) rotations; see coordinates.make_rotation_matrix
        upsample : float
            upsampling factor used in determining output resolution, if it is different
            to the resolution of ifn.
        mask : numpy.ndarray
            boolean ndarray of shape Nact used to suppress/delete/exclude
            actuators; 1=keep, 0=suppress

        """
        if isinstance(Nact, int):
            Nact = (Nact, Nact)
        if isinstance(sep, int):
            sep = (sep, sep)

        x, y = make_xy_grid(ifn.shape, dx=1)

        # stash inputs and some computed values on self
        self.ifn = ifn
        self.Ifn = fft.fft2(ifn)
        self.Nact = Nact
        self.sep = sep
        self.shift = shift
        self.obliquity = truenp.cos(truenp.radians(truenp.linalg.norm(rot)))
        self.rot = rot
        self.upsample = upsample

        # prepare the poke array and supplimentary integer arrays needed to
        # copy it into the working array
        out = prepare_actuator_lattice(ifn.shape,
                                       Nact,
                                       sep,
                                       mask,
                                       dtype=x.dtype)
        self.mask = out['mask']
        self.actuators = out['actuators']
        self.actuators_work = np.zeros_like(self.actuators)
        self.poke_arr = out['poke_arr']
        self.ixx = out['ixx']
        self.iyy = out['iyy']

        # rotation data
        self.rotmat = make_rotation_matrix(rot)
        XY = apply_rotation_matrix(self.rotmat, x, y)
        XY2 = xyXY_to_pixels((x, y), XY)
        self.XY = XY
        self.XY2 = XY2
        self.needs_rot = True
        if np.allclose(rot, [0, 0, 0]):
            self.needs_rot = False

        # shift data
        if shift[0] != 0 or shift[1] != 0:
            # caps = Fourier variable (x -> X, y -> Y)
            # make 2pi/px phase ramps in 1D (much faster)
            # then broadcast them to 2D when they're used as transfer functions
            # in a Fourier convolution
            Y, X = [forward_ft_unit(1, s, shift=False) for s in x.shape]
            Xramp = np.exp(X * (-2j * np.pi * shift[0]))
            Yramp = np.exp(Y * (-2j * np.pi * shift[1]))
            shpx = x.shape
            shpy = tuple(reversed(x.shape))
            Xramp = np.broadcast_to(Xramp, shpx)
            Yramp = np.broadcast_to(Yramp, shpy).T
            self.Xramp = Xramp
            self.Yramp = Yramp
            self.tf = [self.Ifn * self.Xramp * self.Yramp]
        else:
            self.tf = [self.Ifn]