Esempio n. 1
0
def rotated_ellipse(width_major, width_minor, major_axis_angle=0, samples=128):
    """Generate a binary mask for an ellipse, centered at the origin.

    The major axis will notionally extend to the limits of the array, but this
    will not be the case for rotated cases.

    Parameters
    ----------
    width_major : `float`
        width of the ellipse in its major axis
    width_minor : `float`
        width of the ellipse in its minor axis
    major_axis_angle : `float`
        angle of the major axis w.r.t. the x axis, degrees
    samples : `int`
        number of samples

    Returns
    -------
    `numpy.ndarray`
        An ndarray of shape (samples,samples) of value 0 outside the ellipse,
        and value 1 inside the ellipse

    Notes
    -----
    The formula applied is:
         ((x-h)cos(A)+(y-k)sin(A))^2      ((x-h)sin(A)+(y-k)cos(A))^2
        ______________________________ + ______________________________ 1
                     a^2                               b^2
    where x and y are the x and y dimensions, A is the rotation angle of the
    major axis, h and k are the centers of the the ellipse, and a and b are
    the major and minor axis widths.  In this implementation, h=k=0 and the
    formula simplifies to:
            (x*cos(A)+y*sin(A))^2             (x*sin(A)+y*cos(A))^2
        ______________________________ + ______________________________ 1
                     a^2                               b^2

    see SO:
    https://math.stackexchange.com/questions/426150/what-is-the-general-equation-of-the-ellipse-that-is-not-in-the-origin-and-rotate

    Raises
    ------
    ValueError
        Description

    """
    if width_minor > width_major:
        raise ValueError(
            'By definition, major axis must be larger than minor.')

    arr = m.ones((samples, samples))
    lim = width_major
    x, y = m.linspace(-lim, lim, samples), m.linspace(-lim, lim, samples)
    xv, yv = m.meshgrid(x, y)
    A = m.radians(-major_axis_angle)
    a, b = width_major, width_minor
    major_axis_term = ((xv * m.cos(A) + yv * m.sin(A))**2) / a**2
    minor_axis_term = ((xv * m.sin(A) - yv * m.cos(A))**2) / b**2
    arr[major_axis_term + minor_axis_term > 1] = 0
    return arr
Esempio n. 2
0
    def __init__(self, width, sample_spacing=None, samples=0):
        """Create a Pinhole instance.

        Parameters
        ----------
        width : `float`
            the width of the pinhole
        sample_spacing : `float`
            spacing of samples in the synthetic image
        samples : `int`
            number of samples per dimension in the synthetic image

        Notes
        -----
        Default of 0 samples allows quick creation for convolutions without
        generating the image; use samples > 0 for an actual image.

        """
        self.width = width

        # produce coordinate arrays
        if samples > 0:
            ext = samples / 2 * sample_spacing
            x, y = m.linspace(-ext, ext,
                              samples), m.linspace(-ext, ext, samples)
            xv, yv = m.meshgrid(x, y)
            w = width / 2
            # paint a circle on a black background
            arr = m.zeros((samples, samples))
            arr[m.sqrt(xv**2 + yv**2) < w] = 1
        else:
            arr, x, y = None, None, None

        super().__init__(data=arr, unit_x=x, unit_y=y, has_analytic_ft=True)
Esempio n. 3
0
def resample_2d_complex(array, sample_pts, query_pts):
    '''Resamples a 2D complex array.

    Works by interpolating the magnitude and phase independently and merging the results into a complex value.

    Parameters
    ----------
    array : `numpy.ndarray`
        complex 2D array
    sample_pts : `tuple`
        pair of `numpy.ndarray` objects that contain the x and y sample locations,
        each array should be 1D
    query_pts : `tuple`
        points to interpolate onto, also 1D for each array

    Returns
    -------
    `numpy.ndarray`
        array resampled onto query_pts via bivariate spline

    '''
    xq, yq = m.meshgrid(*query_pts)
    mag = abs(array)
    phase = m.angle(array)

    magfunc = interpolate.RegularGridInterpolator(sample_pts, mag)
    phasefunc = interpolate.RegularGridInterpolator(sample_pts, phase)

    interp_mag = magfunc((yq, xq))
    interp_phase = phasefunc((yq, xq))

    return interp_mag * m.exp(1j * interp_phase)
Esempio n. 4
0
    def __init__(self, fno, wavelength, extent=None, samples=None):
        """Create a new AiryDisk.

        Parameters
        ----------
        fno : `float`
            F/# associated with the PSF
        wavelength : `float`
            wavelength of light, in microns
        extent : `float`
            cartesian window half-width, e.g. 10 will make an RoI 20x20 microns wide
        samples : `int`
            number of samples across full width

        """
        if samples is not None:
            x = m.linspace(-extent, extent, samples)
            y = m.linspace(-extent, extent, samples)
            xx, yy = m.meshgrid(x, y)
            rho, phi = cart_to_polar(xx, yy)
            data = airydisk(rho, fno, wavelength)
        else:
            x, y, data = None, None, None
        self.fno = fno
        self.wavelength = wavelength
        super().__init__(data, x, y)
        self.has_analytic_ft = True
Esempio n. 5
0
def render_synthetic_surface(size, samples, rms=None, mask='circle', psd_fcn=abc_psd, **psd_fcn_kwargs):
    """Render a synthetic surface with a given RMS value given a PSD function.

    Parameters
    ----------
    size : `float`
        diameter of the output surface, mm
    samples : `int`
        number of samples across the output surface
    rms : `float`
        desired RMS value of the output, if rms=None, no normalization is done
    mask : `str`, optional
        mask defining the clear aperture
    psd_fcn : `callable`
        function used to generate the PSD
    **psd_fcn_kwargs:
        keyword arguments passed to psd_fcn in addition to nu
        if psd_fcn == abc_psd, kwargs are a, b, c
        elif psd_Fcn == ab_psd kwargs are a, b

        kwargs will be user-defined for user PSD functions

    Returns
    -------
    x : `numpy.ndarray`
        x coordinates, mm
    y: `numpy.ndarray`
        y coordinates, mm
    z : `numpy.ndarray`
        height data, nm

    """
    # compute the grid and PSD
    sample_spacing = size / (samples - 1)
    nu_x = nu_y = forward_ft_unit(sample_spacing, samples)
    center = samples // 2  # some bullshit here to gloss over zeros for ab_psd
    nu_x[center] = nu_x[center+1] / 10
    nu_y[center] = nu_y[center+1] / 10
    nu_xx, nu_yy = m.meshgrid(nu_x, nu_y)

    nu_r, _ = cart_to_polar(nu_xx, nu_yy)
    psd = psd_fcn(nu_r, **psd_fcn_kwargs)

    # synthesize a surface from the PSD
    x, y, z = synthesize_surface_from_psd(psd, nu_x, nu_y)

    # mask
    mask = mcache(mask, samples)
    z[mask == 0] = m.nan

    # possibly scale RMS
    if rms is not None:
        z_rms = globals()['rms'](z)  # rms function is shadowed by rms kwarg
        scale_factor = rms / z_rms
        z *= scale_factor

    return x, y, z
Esempio n. 6
0
    def __init__(self,
                 spokes,
                 sinusoidal=True,
                 background='black',
                 sample_spacing=2,
                 samples=256):
        """Produces a Siemen's Star.

        Parameters
        ----------
        spokes : `int`
            number of spokes in the star.
        sinusoidal : `bool`
            if True, generates a sinusoidal Siemen' star, else, generates a bar/block siemen's star
        background : 'string', {'black', 'white'}
            background color
        sample_spacing : `float`
            spacing of samples, in microns
        samples : `int`
            number of samples per dimension in the synthetic image

        Raises
        ------
        ValueError
            background other than black or white

        """
        relative_width = 0.9
        self.spokes = spokes

        # generate a coordinate grid
        x = m.linspace(-1, 1, samples)
        y = m.linspace(-1, 1, samples)
        xx, yy = m.meshgrid(x, y)
        rv, pv = cart_to_polar(xx, yy)
        ext = sample_spacing * samples / 2
        ux, uy = m.arange(-ext, ext,
                          sample_spacing), m.arange(-ext, ext, sample_spacing)

        # generate the siemen's star as a (rho,phi) polynomial
        arr = m.cos(spokes / 2 * pv)

        if not sinusoidal:  # make binary
            arr[arr < 0] = -1
            arr[arr > 0] = 1

        # scale to (0,1) and clip into a disk
        arr = (arr + 1) / 2
        if background.lower() in ('b', 'black'):
            arr[rv > relative_width] = 0
        elif background.lower() in ('w', 'white'):
            arr[rv > relative_width] = 1
        else:
            raise ValueError('invalid background color')

        super().__init__(data=arr, unit_x=ux, unit_y=uy, has_analytic_ft=False)
Esempio n. 7
0
def fit_plane(x, y, z):
    xx, yy = m.meshgrid(x, y)
    pts = m.isfinite(z)
    xx_, yy_ = xx[pts].flatten(), yy[pts].flatten()
    flat = m.ones(xx_.shape)

    coefs = m.lstsq(m.stack([xx_, yy_, flat]).T, z[pts].flatten(),
                    rcond=None)[0]
    plane_fit = coefs[0] * xx + coefs[1] * yy + coefs[2]
    return plane_fit
Esempio n. 8
0
def fit_sphere(z):
    x, y = m.linspace(-1, 1, z.shape[1]), m.linspace(-1, 1, z.shape[0])
    xx, yy = m.meshgrid(x, y)
    pts = m.isfinite(z)
    xx_, yy_ = xx[pts].flatten(), yy[pts].flatten()
    rho, phi = cart_to_polar(xx_, yy_)
    focus = defocus(rho, phi)

    coefs = m.lstsq(m.stack([focus, m.ones(focus.shape)]).T, z[pts].flatten(), rcond=None)[0]
    rho, phi = cart_to_polar(xx, yy)
    sphere = defocus(rho, phi) * coefs[0]
    return sphere
Esempio n. 9
0
    def encircled_energy(self, radius):
        """Compute the encircled energy of the PSF.

        Parameters
        ----------
        radius : `float` or iterable
            radius or radii to evaluate encircled energy at

        Returns
        -------
        encircled energy
            if radius is a float, returns a float, else returns a list.

        Notes
        -----
        implementation of "Simplified Method for Calculating Encircled Energy,"
        Baliga, J. V. and Cohn, B. D., doi: 10.1117/12.944334

        """
        from .otf import MTF

        if hasattr(radius, '__iter__'):
            # user wants multiple points
            # um to mm, cy/mm assumed in Fourier plane
            radius_is_array = True
        else:
            radius_is_array = False

        # compute MTF from the PSF
        if self._mtf is None:
            self._mtf = MTF.from_psf(self)
            nx, ny = m.meshgrid(self._mtf.unit_x, self._mtf.unit_y)
            self._nu_p = m.sqrt(nx**2 + ny**2)
            # this is meaninglessly small and will avoid division by 0
            self._nu_p[self._nu_p == 0] = 1e-99
            self._dnx, self._dny = ny[1, 0] - ny[0, 0], nx[0, 1] - nx[0, 0]

        if radius_is_array:
            out = []
            for r in radius:
                if r not in self._ee:
                    self._ee[r] = _encircled_energy_core(
                        self._mtf.data, r / 1e3, self._nu_p, self._dnx,
                        self._dny)
                out.append(self._ee[r])
            return m.asarray(out)
        else:
            if radius not in self._ee:
                self._ee[radius] = _encircled_energy_core(
                    self._mtf.data, radius / 1e3, self._nu_p, self._dnx,
                    self._dny)
            return self._ee[radius]
Esempio n. 10
0
def fit_plane(x, y, z):
    pts = m.isfinite(z)
    if len(z.shape) > 1:
        x, y = m.meshgrid(x, y)
        xx, yy = x[pts].flatten(), y[pts].flatten()
    else:
        xx, yy = x, y

    flat = m.ones(xx.shape)

    coefs = m.lstsq(m.stack([xx, yy, flat]).T, z[pts].flatten(), rcond=None)[0]
    plane_fit = coefs[0] * x + coefs[1] * y + coefs[2]
    return plane_fit
Esempio n. 11
0
    def __init__(self,
                 angle=4,
                 contrast=0.9,
                 crossed=False,
                 sample_spacing=2,
                 samples=256):
        """Create a new TitledSquare instance.

        Parameters
        ----------
        angle : `float`
            angle in degrees to tilt w.r.t. the y axis
        contrast : `float`
            difference between minimum and maximum values in the image
        crossed : `bool`, optional
            whether to make a single edge (crossed=False) or pair of crossed edges (crossed=True)
            aka a "BMW target"
        sample_spacing : `float`
            spacing of samples
        samples : `int`
            number of samples

        """
        diff = (1 - contrast) / 2
        arr = m.full((samples, samples), 1 - diff)
        x = m.linspace(-0.5, 0.5, samples)
        y = m.linspace(-0.5, 0.5, samples)
        xx, yy = m.meshgrid(x, y)
        sf = samples * sample_spacing

        angle = m.radians(angle)
        xp = xx * m.cos(angle) - yy * m.sin(angle)
        # yp = xx * m.sin(angle) + yy * m.cos(angle)  # do not need this
        mask = xp > 0  # single edge
        if crossed:
            mask = xp > 0  # set of 4 edges
            upperright = mask & m.rot90(mask)
            lowerleft = m.rot90(upperright, 2)
            mask = upperright | lowerleft

        arr[mask] = diff
        self.contrast = contrast
        self.black = diff
        self.white = 1 - diff
        super().__init__(data=arr,
                         unit_x=x * sf,
                         unit_y=y * sf,
                         has_analytic_ft=False)
Esempio n. 12
0
    def __init__(self, psfs, weights=None):
        '''Create a new `MultispectralPSF` instance.

        Parameters
        ----------
        psfs : iterable
            iterable of PSFs
        weights : iterable
            iterable of weights associated with each PSF

        '''
        if weights is None:
            weights = [1] * len(psfs)

        # find the most densely sampled PSF
        min_spacing = 1e99
        ref_idx = None
        ref_unit_x = None
        ref_unit_y = None
        ref_samples_x = None
        ref_samples_y = None
        for idx, psf in enumerate(psfs):
            if psf.sample_spacing < min_spacing:
                min_spacing = psf.sample_spacing
                ref_idx = idx
                ref_unit_x = psf.unit_x
                ref_unit_y = psf.unit_y
                ref_samples_x = psf.samples_x
                ref_samples_y = psf.samples_y

        merge_data = m.zeros((ref_samples_x, ref_samples_y, len(psfs)))
        for idx, psf in enumerate(psfs):
            # don't do anything to our reference PSF
            if idx is ref_idx:
                merge_data[:, :, idx] = psf.data * weights[idx]
            else:
                xv, yv = m.meshgrid(ref_unit_x, ref_unit_y)
                interpf = interpolate.RegularGridInterpolator(
                    (psf.unit_x, psf.unit_y), psf.data)
                merge_data[:, :, idx] = interpf(
                    (xv, yv), method='linear') * weights[idx]

        self.weights = weights
        super().__init__(merge_data.sum(axis=2), min_spacing)
        self._renorm()
Esempio n. 13
0
    def polychromatic(psfs, spectral_weights=None, interp_method='linear'):
        """Create a new PSF instance from an ensemble of monochromatic PSFs given spectral weights.

        The new PSF is the polychromatic PSF, assuming the wavelengths are
        sufficiently different that they do not interfere and the mode of
        imaging is incoherent.

        """
        if spectral_weights is None:
            spectral_weights = [1] * len(psfs)

        # find the most densely sampled PSF
        min_spacing = 1e99
        ref_idx = None
        ref_unit_x = None
        ref_unit_y = None
        ref_samples_x = None
        ref_samples_y = None
        for idx, psf in enumerate(psfs):
            if psf.sample_spacing < min_spacing:
                min_spacing = psf.sample_spacing
                ref_idx = idx
                ref_unit_x = psf.unit_x
                ref_unit_y = psf.unit_y
                ref_samples_x = psf.samples_x
                ref_samples_y = psf.samples_y

        merge_data = m.zeros((ref_samples_x, ref_samples_y, len(psfs)))
        for idx, psf in enumerate(psfs):
            # don't do anything to the reference PSF besides spectral scaling
            if idx is ref_idx:
                merge_data[:, :, idx] = psf.data * spectral_weights[idx]
            else:
                xv, yv = m.meshgrid(ref_unit_x, ref_unit_y)
                interpf = interpolate.RegularGridInterpolator(
                    (psf.unit_y, psf.unit_x), psf.data)
                merge_data[:, :, idx] = interpf(
                    (yv, xv), method=interp_method) * spectral_weights[idx]

        psf = PSF(data=merge_data.sum(axis=2),
                  unit_x=ref_unit_x,
                  unit_y=ref_unit_y)
        psf.spectral_weights = spectral_weights
        psf._renorm()
        return psf
Esempio n. 14
0
    def analytic_ft(self, unit_x, unit_y):
        """Analytic fourier transform of a pixel aperture.

        Parameters
        ----------
        unit_x : `numpy.ndarray`
            sample points in x axis
        unit_y : `numpy.ndarray`
            sample points in y axis

        Returns
        -------
        `numpy.ndarray`
            2D numpy array containing the analytic fourier transform

        """
        xq, yq = m.meshgrid(unit_x, unit_y)
        return abs(pixelaperture_analytic_otf(self.width_x, self.width_y, xq, yq))
Esempio n. 15
0
def fit(data, num_terms=16, rms_norm=False, round_at=6):
    ''' Fits a number of Zernike coefficients to provided data by minimizing
        the root sum square between each coefficient and the given data.  The
        data should be uniformly sampled in an x,y grid.

    Args:

        data (`numpy.ndarray`): data to fit to.

        num_terms (`int`): number of terms to fit, fits terms 0~num_terms.

        rms_norm (`bool`): if true, normalize coefficients to unit RMS value.

        round_at (`int`): decimal place to round values at.

    Returns:
        numpy.ndarray: an array of coefficients matching the input data.

    '''
    if num_terms > len(zernmap):
        raise ValueError(f'number of terms must be less than {len(zernmap)}')

    # precompute the valid indexes in the original data
    pts = m.isfinite(data)

    # set up an x/y rho/phi grid to evaluate Zernikes on
    x, y = m.linspace(-1, 1, data.shape[1]), m.linspace(-1, 1, data.shape[0])
    xx, yy = m.meshgrid(x, y)
    rho = m.sqrt(xx**2 + yy**2)[pts].flatten()
    phi = m.arctan2(xx, yy)[pts].flatten()

    # compute each Zernike term
    zernikes = []
    for i in range(num_terms):
        func = z.zernikes[zernmap[i]]
        base_zern = func(rho, phi)
        if rms_norm:
            base_zern *= func.norm
        zernikes.append(base_zern)
    zerns = m.asarray(zernikes).T

    # use least squares to compute the coefficients
    coefs = m.lstsq(zerns, data[pts].flatten(), rcond=None)[0]
    return coefs.round(round_at)
Esempio n. 16
0
    def __init__(self,
                 angle=4,
                 background='white',
                 sample_spacing=2,
                 samples=256):
        """Create a new TitledSquare instance.

        Parameters
        ----------
        angle : `float`
            angle in degrees to tilt w.r.t. the x axis
        background : `string`
            white or black; the square will be the opposite color of the background
        sample_spacing : `float`
            spacing of samples
        samples : `int`
            number of samples

        """
        radius = 0.3
        if background.lower() == 'white':
            arr = m.ones((samples, samples))
            fill_with = 0
        else:
            arr = m.zeros((samples, samples))
            fill_with = 1

        # TODO: optimize by working with index numbers directly and avoid
        # creation of X,Y arrays for performance.
        x = m.linspace(-0.5, 0.5, samples)
        y = m.linspace(-0.5, 0.5, samples)
        xx, yy = m.meshgrid(x, y)
        sf = samples * sample_spacing

        # TODO: convert inline operation to use of rotation matrix
        angle = m.radians(angle)
        xp = xx * m.cos(angle) - yy * m.sin(angle)
        yp = xx * m.sin(angle) + yy * m.cos(angle)
        mask = (abs(xp) < radius) * (abs(yp) < radius)
        arr[mask] = fill_with
        super().__init__(data=arr,
                         unit_x=x * sf,
                         unit_y=y * sf,
                         has_analytic_ft=False)
Esempio n. 17
0
    def analytic_ft(self, unit_x, unit_y):
        """Analytic fourier transform of a pixel aperture.

        Parameters
        ----------
        unit_x : `numpy.ndarray`
            sample points in x axis
        unit_y : `numpy.ndarray`
            sample points in y axis

        Returns
        -------
        `numpy.ndarray`
            2D numpy array containing the analytic fourier transform

        """
        xq, yq = m.meshgrid(unit_x, unit_y)
        return (m.sinc(xq * self.width_x) * m.sinc(yq * self.width_y)).astype(
            config.precision)
Esempio n. 18
0
    def analytic_ft(self, unit_x, unit_y):
        """Analytic fourier transform of an airy disk.

        Parameters
        ----------
        unit_x : `numpy.ndarray`
            sample points in x axis
        unit_y : `numpy.ndarray`
            sample points in y axis

        Returns
        -------
        `numpy.ndarray`
            2D numpy array containing the analytic fourier transform

        """
        from .otf import diffraction_limited_mtf
        r, p = cart_to_polar(*m.meshgrid(unit_x, unit_y))
        return diffraction_limited_mtf(self.fno, self.wavelength,
                                       r * 1e3)  # um to mm
Esempio n. 19
0
    def __init__(self, r_psf, g_psf, b_psf):
        '''Create a new `RGBPSF` instance.

        Parameters
        ----------
        r_psf : `PSF`
            PSF for the red channel
        g_psf : `PSF`
            PSF for the green channel
        b_psf : `PSF`
            PSF for the blue channel

        '''
        if m.array_equal(r_psf.unit_x, g_psf.unit_x) and \
           m.array_equal(g_psf.unit_x, b_psf.unit_x) and \
           m.array_equal(r_psf.unit_y, g_psf.unit_y) and \
           m.array_equal(g_psf.unit_y, b_psf.unit_y):
            # do not need to interpolate the arrays
            self.R = r_psf.data
            self.G = g_psf.data
            self.B = b_psf.data
        else:
            # need to interpolate the arrays.  Blue tends to be most densely
            # sampled, use it to define our grid
            self.B = b_psf.data

            xv, yv = m.meshgrid(b_psf.unit_x, b_psf.unit_y)
            interpf_r = interpolate.RegularGridInterpolator(
                (r_psf.unit_y, r_psf.unit_x), r_psf.data)
            interpf_g = interpolate.RegularGridInterpolator(
                (g_psf.unit_y, g_psf.unit_x), g_psf.data)
            self.R = interpf_r((yv, xv), method='linear')
            self.G = interpf_g((yv, xv), method='linear')

        self.sample_spacing = b_psf.sample_spacing
        self.samples_x = b_psf.samples_x
        self.samples_y = b_psf.samples_y
        self.unit_x = b_psf.unit_x
        self.unit_y = b_psf.unit_y
        self.center_x = b_psf.center_x
        self.center_y = b_psf.center_y
Esempio n. 20
0
def uniform_cart_to_polar(x, y, data):
    """Interpolate data uniformly sampled in cartesian coordinates to polar coordinates.

    Parameters
    ----------
    x : `numpy.ndarray`
        sorted 1D array of x sample pts
    y : `numpy.ndarray`
        sorted 1D array of y sample pts
    data : `numpy.ndarray`
        data sampled over the (x,y) coordinates

    Returns
    -------
    rho : `numpy.ndarray`
        samples for interpolated values
    phi : `numpy.ndarray`
        samples for interpolated values
    f(rho,phi) : `numpy.ndarray`
        data uniformly sampled in (rho,phi)

    """
    # create a set of polar coordinates to interpolate onto
    xmin, xmax = min(x), max(x)
    ymin, ymax = min(y), max(y)

    _max = max(abs(m.asarray([xmin, xmax, ymin, ymax])))

    rho = m.linspace(0, _max, len(x))
    phi = m.linspace(0, 2 * m.pi, len(y))
    rv, pv = m.meshgrid(rho, phi)

    # map points to x, y and make a grid for the original samples
    xv, yv = polar_to_cart(rv, pv)

    # interpolate the function onto the new points
    f = interpolate.RegularGridInterpolator((y, x),
                                            data,
                                            bounds_error=False,
                                            fill_value=0)
    return rho, phi, f((yv, xv), method='linear')
Esempio n. 21
0
def resample_2d(array, sample_pts, query_pts):
    """Resample 2D array to be sampled along queried points.

    Parameters
    ----------
    array : `numpy.ndarray`
        2D array
    sample_pts : `tuple`
        pair of `numpy.ndarray` objects that contain the x and y sample locations,
        each array should be 1D
    query_pts : `tuple`
        points to interpolate onto, also 1D for each array

    Returns
    -------
    `numpy.ndarray`
        array resampled onto query_pts via bivariate spline

    """
    xq, yq = m.meshgrid(*query_pts)
    interpf = interpolate.RectBivariateSpline(*sample_pts, array)
    return interpf.ev(yq, xq)
Esempio n. 22
0
def bandreject_filter(array, sample_spacing, wllow, wlhigh):
    sy, sx = array.shape

    # compute the bandpass in sample coordinates
    ux, uy = forward_ft_unit(sample_spacing,
                             sx), forward_ft_unit(sample_spacing, sy)
    fhigh, flow = 1 / wllow, 1 / wlhigh

    # make an ordinate array in frequency space and use it to make a mask
    uxx, uyy = m.meshgrid(ux, uy)
    highpass = ((uxx < -fhigh) | (uxx > fhigh)) | ((uyy < -fhigh) |
                                                   (uyy > fhigh))
    lowpass = ((uxx > -flow) & (uxx < flow)) & ((uyy > -flow) & (uyy < flow))
    mask = highpass | lowpass

    # adjust NaNs and FFT
    work = array.copy()
    work[~m.isfinite(work)] = 0
    fourier = m.fftshift(m.fft2(m.ifftshift(work)))
    fourier[mask] = 0
    out = m.fftshift(m.ifft2(m.ifftshift(fourier)))
    return out.real
Esempio n. 23
0
    def analytic_ft(self, unit_x, unit_y):
        """Analytic fourier transform of a slit.

        Parameters
        ----------
        unit_x : `numpy.ndarray`
            sample points in x frequency axis
        unit_y : `numpy.ndarray`
            sample points in y frequency axis

        Returns
        -------
        `numpy.ndarray`
            2D numpy array containing the analytic fourier transform

        """
        xq, yq = m.meshgrid(unit_x, unit_y)

        # factor of pi corrects for jinc being modulo pi
        # factor of 2 converts radius to diameter
        rho = m.sqrt(xq**2 + yq**2) * self.width * 2 * m.pi
        return m.jinc(rho).astype(config.precision)
Esempio n. 24
0
def window_2d_welch(x, y, alpha=8):
    """Return a 2D welch window for a given alpha.

    Parameters
    ----------
    x : `numpy.ndarray`
        x values, 1D array
    y : `numpy.ndarray`
        y values, 1D array
    alpha : `float`
        alpha (edge roll) parameter

    Returns
    -------
    `numpy.ndarray`
        window

    """
    xx, yy = m.meshgrid(x, y)
    r, _ = cart_to_polar(xx, yy)
    rmax = m.sqrt(x.max()**2 + y.max()**2)
    window = 1 - abs(r/rmax)**alpha
    return window
Esempio n. 25
0
    def analytic_ft(self, unit_x, unit_y):
        """Analytic fourier transform of a slit.

        Parameters
        ----------
        unit_x : `numpy.ndarray`
            sample points in x frequency axis
        unit_y : `numpy.ndarray`
            sample points in y frequency axis

        Returns
        -------
        `numpy.ndarray`
            2D numpy array containing the analytic fourier transform

        """
        xq, yq = m.meshgrid(unit_x, unit_y)
        if self.width_x > 0 and self.width_y > 0:
            return (m.sinc(xq * self.width_x) +
                    m.sinc(yq * self.width_y)).astype(config.precision)
        elif self.width_x > 0 and self.width_y is 0:
            return m.sinc(xq * self.width_x).astype(config.precision)
        else:
            return m.sinc(yq * self.width_y).astype(config.precision)
Esempio n. 26
0
def make_xy_grid(samples_x, samples_y=None):
    """Create an x, y grid from -1, 1 with n number of samples.

    Parameters
    ----------
    samples_x : `int`
        number of samples in x direction
    samples_y : `int`
        number of samples in y direction, if None, copied from sample_x

    Returns
    -------
    xx : `numpy.ndarray`
        x meshgrid
    yy : `numpy.ndarray`
        y meshgrid

    """
    if samples_y is None:
        samples_y = samples_x
    x = m.linspace(-1, 1, samples_x, dtype=config.precision)
    y = m.linspace(-1, 1, samples_y, dtype=config.precision)
    xx, yy = m.meshgrid(x, y)
    return xx, yy
Esempio n. 27
0
def generate_mask(vertices, num_samples=128):
    """Create a filled convex polygon mask based on the given vertices.

    Parameters
    ----------
    vertices : `iterable`
        ensemble of vertice (x,y) coordinates, in array units
    num_samples : `int`
        number of points in the output array along each dimension

    Returns
    -------
    `numpy.ndarray`
        polygon mask

    """
    vertices = m.asarray(vertices)
    unit = m.arange(num_samples)
    xxyy = m.stack(m.meshgrid(unit, unit), axis=2)

    # use delaunay to fill from the vertices and produce a mask
    triangles = Delaunay(vertices, qhull_options='Qj Qf')
    mask = ~(triangles.find_simplex(xxyy) < 0)
    return mask
Esempio n. 28
0
def inverted_circle(samples=128, radius=1):
    """ Create an inverted circular mask (obscuration).

    Parameters
    ----------
    samples : `int`, optional
        number of samples in the square output array

    Returns
    ------
    `numpy.ndarray`
        binary ndarray representation of the mask

    """
    if radius is 0:
        return m.ones((samples, samples))
    else:
        x = m.linspace(-1, 1, samples)
        y = x
        xx, yy = m.meshgrid(x, y)
        rho, phi = cart_to_polar(xx, yy)
        mask = m.ones(rho.shape)
        mask[rho < radius] = 0
        return mask
Esempio n. 29
0
def bandlimited_rms(ux, uy, psd, wllow=None, wlhigh=None, flow=None, fhigh=None):
    """Calculate the bandlimited RMS of a signal from its PSD.

    Parameters
    ----------
    ux : `numpy.ndarray`
        x spatial frequencies
    uy : `numpy.ndarray`
        y spatial frequencies
    psd : `numpy.ndarray`
        power spectral density
    wllow : `float`
        short spatial scale
    wlhigh : `float`
        long spatial scale
    flow : `float`
        low frequency
    fhigh : `float`
        high frequency

    Returns
    -------
    `float`
        band-limited RMS value.

    """
    if wllow is not None or wlhigh is not None:
        # spatial period given
        if wllow is None:
            flow = 0
        else:
            fhigh = 1 / wllow

        if wlhigh is None:
            fhigh = max(ux[-1], uy[-1])
        else:
            flow = 1 / wlhigh
    elif flow is not None or fhigh is not None:
        # spatial frequency given
        if flow is None:
            flow = 0
        if fhigh is None:
            fhigh = max(ux[-1], uy[-1])
    else:
        raise ValueError('must specify either period (wavelength) or frequency')

    ux2, uy2 = m.meshgrid(ux, uy)
    r, p = cart_to_polar(ux2, uy2)

    if flow is None:
        warnings.warn('no lower limit given, using 0 for low frequency')
        flow = 0

    if fhigh is None:
        warnings.warn('no upper limit given, using limit imposed by data.')
        fhigh = r.max()

    work = psd.copy()
    work[r < flow] = 0
    work[r > fhigh] = 0
    first = m.trapz(work, uy, axis=0)
    second = m.trapz(first, ux, axis=0)
    return m.sqrt(second)
Esempio n. 30
0
def window_2d_welch(x, y, alpha=8):
    xx, yy = m.meshgrid(x, y)
    r, _ = cart_to_polar(xx, yy)
    rmax = m.sqrt(x.max()**2 + y.max()**2)
    window = 1 - abs(r / rmax)**alpha
    return window