Beispiel #1
0
    def __init__(self, width, sample_spacing=0.025, samples=0):
        '''Produce a Pinhole.

        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, m.zeros(2), m.zeros(2)

        super().__init__(data=arr, unit_x=x, unit_y=y, has_analytic_ft=True)
Beispiel #2
0
    def __init__(self,
                 width,
                 orientation='Vertical',
                 sample_spacing=0.075,
                 samples=0):
        '''Create a new Slit instance.

        Parameters
        ----------
        width : `float`
            the width of the slit in microns
        orientation : `string`, {'Horizontal', 'Vertical', 'Crossed', 'Both'}
            the orientation of the slit; Crossed and Both produce the same results
        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.

        '''
        w = width / 2

        if samples > 0:
            ext = samples / 2 * sample_spacing
            x, y = m.linspace(-ext, ext,
                              samples), m.linspace(-ext, ext, samples)
            arr = m.zeros((samples, samples))
        else:
            arr, x, y = None, m.zeros(2), m.zeros(2)

        # paint in the slit
        if orientation.lower() in ('v', 'vert', 'vertical'):
            if samples > 0:
                arr[:, abs(x) < w] = 1
            self.orientation = 'Vertical'
            self.width_x = width
            self.width_y = 0
        elif orientation.lower() in ('h', 'horiz', 'horizontal'):
            if samples > 0:
                arr[abs(y) < w, :] = 1
            self.width_x = 0
            self.width_y = width
            self.orientation = 'Horizontal'
        elif orientation.lower() in ('b', 'both', 'c', 'crossed'):
            if samples > 0:
                arr[abs(y) < w, :] = 1
                arr[:, abs(x) < w] = 1
            self.orientation = 'Crossed'
            self.width_x, self.width_y = width, width

        super().__init__(arr, x, y, has_analytic_ft=True)
Beispiel #3
0
    def __init__(self,
                 width_x,
                 width_y=None,
                 sample_spacing=0.1,
                 samples_x=384,
                 samples_y=None):
        """Create a new OLPF object.

        Parameters
        ----------
        width_x : `float`
            blur width in the x direction, microns
        width_y : `float`
            blur width in the y direction, microns
        sample_spacing : `float`, optional
            center to center spacing of samples
        samples_x : `int`, optional
            number of samples along x axis
        samples_y : `int`, optional
            number of samples along y axis; duplicates x if None

        """
        # compute relevant spacings
        if width_y is None:
            width_y = width_x
        if samples_y is None:
            samples_y = samples_x

        self.width_x = width_x
        self.width_y = width_y

        if samples_x is None:  # do no math
            data, ux, uy = None, m.zeros(2), m.zeros(2)
        else:
            space_x = width_x / 2
            space_y = width_y / 2
            shift_x = int(space_x // sample_spacing)
            shift_y = int(space_y // sample_spacing)
            center_x = samples_x // 2
            center_y = samples_y // 2

            data = m.zeros((samples_x, samples_y))

            data[center_y - shift_y, center_x - shift_x] = 1
            data[center_y - shift_y, center_x + shift_x] = 1
            data[center_y + shift_y, center_x - shift_x] = 1
            data[center_y + shift_y, center_x + shift_x] = 1
            ux = m.linspace(-space_x, space_x, samples_x)
            uy = m.linspace(-space_y, space_y, samples_y)
        super().__init__(data=data, unit_x=ux, unit_y=uy, has_analytic_ft=True)
Beispiel #4
0
def truecircle(samples=128, radius=1):
    """Create a "true" circular mask with anti-aliasing.

    Parameters
    ----------
    samples : `int`, optional
        number of samples in the square output array
    radius : `float`, optional
        radius of the shape in the square output array.  radius=1 will fill the
        x

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

    Notes
    -----
    Based on a more general algorithm by Jim Fienup

    """
    if radius is 0:
        return m.zeros((samples, samples), dtype=config.precision)
    else:
        rho, phi = make_rho_phi_grid(samples, samples)
        one_pixel = 2 / samples
        radius_plus = radius + (one_pixel / 2)
        intermediate = (radius_plus - rho) * (samples / 2)
        return m.minimum(m.maximum(intermediate, 0), 1)
Beispiel #5
0
    def __init__(self,
                 width_x,
                 width_y=None,
                 sample_spacing=0,
                 samples_x=None,
                 samples_y=None):
        """Create a new `PixelAperture` object.

        Parameters
        ----------
        width_x : `float`
            width of the aperture in the x dimension, in microns.
        width_y : `float`, optional
            siez of the aperture in the y dimension, in microns
        sample_spacing : `float`, optional
            spacing of samples, in microns
        samples_x : `int`, optional
            number of samples in the x dimension
        samples_y : `int`, optional
            number of samples in the y dimension

        """
        if width_y is None:
            width_y = width_x
        if samples_y is None:
            samples_y = samples_x

        self.width_x = width_x
        self.width_y = width_y

        if samples_x is None:  # do no math
            data, ux, uy = None, m.zeros(2), m.zeros(2)
        else:  # build PixelAperture model
            center_x = samples_x // 2
            center_y = samples_y // 2
            half_width = width_x / 2
            half_height = width_y / 2
            steps_x = int(half_width // sample_spacing)
            steps_y = int(half_height // sample_spacing)

            data = m.zeros((samples_x, samples_y))
            data[center_y - steps_y:center_y + steps_y,
                 center_x - steps_x:center_x + steps_x] = 1
            extx, exty = samples_x // 2 * sample_spacing, samples_y // 2 * sample_spacing
            ux, uy = m.linspace(-extx, extx,
                                samples_x), m.linspace(-exty, exty, samples_y)
        super().__init__(data=data, unit_x=ux, unit_y=uy, has_analytic_ft=True)
Beispiel #6
0
def pad2d(array, Q=2, value=0, mode='constant'):
    """Symmetrically pads a 2D array with a value.

    Parameters
    ----------
    array : `numpy.ndarray`
        source array
    Q : `float`, optional
        oversampling factor; ratio of input to output array widths
    value : `float`, optioanl
        value with which to pad the array
    mode : `str`, optional
        mode, passed directly to np.pad

    Returns
    -------
    `numpy.ndarray`
        padded array

    Notes
    -----
    padding will be symmetric.

    """
    if Q is 1:
        return array
    else:
        if mode == 'constant':
            pad_shape, out_x, out_y = _padshape(array, Q)
            y, x = array.shape
            if value is 0:
                out = m.zeros((out_y, out_x), dtype=array.dtype)
            else:
                out = m.zeros((out_y, out_x), dtype=array.dtype) + value
            yy, xx = pad_shape
            out[yy[0]:yy[0] + y, xx[0]:xx[0] + x] = array
            return out
        else:
            pad_shape, *_ = _padshape(array, Q)

            if mode == 'constant':
                kwargs = {'constant_values': value, 'mode': mode}
            else:
                kwargs = {'mode': mode}
            return m.pad(array, pad_shape, **kwargs)
Beispiel #7
0
def pad2d(array, Q=2, value=0):
    """Symmetrically pads a 2D array with a value.

    Parameters
    ----------
    array : `numpy.ndarray`
        source array
    Q : `float` or `int`
        oversampling factor; ratio of input to output array widths
    value : `float` or `int`
        value with which to pad the array

    Returns
    -------
    `numpy.ndarray`
        padded array

    Notes
    -----
    padding will be symmetric.

    """
    if Q is 1:
        return array
    else:
        y, x = array.shape
        out_x = int(x * Q)
        out_y = int(y * Q)
        factor_x = (out_x - x) / 2
        factor_y = (out_y - y) / 2
        pad_shape = ((int(m.floor(factor_y)), int(m.ceil(factor_y))),
                     (int(m.floor(factor_x)), int(m.ceil(factor_x))))
        if value is 0:
            out = m.zeros((out_y, out_x), dtype=array.dtype)
        else:
            out = m.zeros((out_y, out_x), dtype=array.dtype) + value
        yy, xx = pad_shape
        out[yy[0]:yy[0] + y, xx[0]:xx[0] + x] = array
        return out
Beispiel #8
0
    def build(self):
        # construct an equation for the phase of the pupil
        # build a coordinate system over which to evaluate this function
        self._gengrid()
        self.phase = m.zeros((self.samples, self.samples),
                             dtype=config.precision)
        for term, coef in enumerate(self.coefs):
            # short circuit for speed
            if coef == 0:
                continue
            self.phase += coef * zernwrapper(
                term=term, rho=self.rho, phi=self.phi)

        self._correct_phase_units()
        self._phase_to_wavefunction()
        return self.phase, self.fcn
Beispiel #9
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
Beispiel #10
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()
Beispiel #11
0
    def mask(self, shape_or_mask, diameter=None):
        """Mask the signal.

        The mask will be inscribed in the axis with fewer pixels.  I.e., for
        a interferogram with 1280x1000 pixels, the mask will be 1000x1000 at
        largest.

        Parameters
        ----------
        shape_or_mask : `str` or `numpy.ndarray`
            valid shape from prysm.geometry
        diameter : `float`
            diameter of the mask, in self.spatial_units
        mask : `numpy.ndarray`
            user-provided mask

        Returns
        -------
        self
            modified Interferogram instance.

        """
        if isinstance(shape_or_mask, str):
            if diameter is None:
                diameter = self.diameter
            mask = mcache(shape_or_mask, min(self.shape), radius=diameter / min(self.diameter_x, self.diameter_y))
            base = m.zeros(self.shape, dtype=config.precision)
            difference = abs(self.shape[0] - self.shape[1])
            l, u = int(m.floor(difference / 2)), int(m.ceil(difference / 2))
            if u is 0:  # guard against nocrop scenario
                _slice = slice(None)
            else:
                _slice = slice(l, -u)
            if self.shape[0] < self.shape[1]:
                base[:, _slice] = mask
            else:
                base[_slice, :] = mask

            mask = base
        else:
            mask = shape_or_mask

        hitpts = mask == 0
        self.phase[hitpts] = m.nan
        return self
Beispiel #12
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)
Beispiel #13
0
    def build(self):
        """Construct a numerical model of a `Pupil`.

        The method should be overloaded by all subclasses to impart their unique
        mathematical models to the simulation.

        Returns
        -------
        `Pupil`
            this pupil instance

        """
        # build up the pupil
        self._gengrid()

        # fill in the phase of the pupil
        self.phase = m.zeros((self.samples, self.samples),
                             dtype=config.precision)

        return self
Beispiel #14
0
    def build(self):
        '''Uses the wavefront coefficients stored in this class instance to
            build a wavefront model.

        Args:
            none

        Returns:
            (numpy.ndarray, numpy.ndarray) arrays containing the phase, and the
                wavefunction for the pupil.

        '''
        # build a coordinate system over which to evaluate this function
        self.phase = m.zeros((self.samples, self.samples), dtype=config.precision)
        for term, coef in enumerate(self.coefs):
            # short circuit for speed
            if coef == 0:
                continue
            self.phase += coef * zcache.get_zernike(term, self.normalize, self.samples)

        self._phase_to_wavefunction()
        return self.phase, self.fcn
Beispiel #15
0
    def build(self):
        """Use the wavefront coefficients stored in this class instance to build a wavefront model.

        Returns
        -------
        self.phase : `numpy.ndarray`
            arrays containing the phase associated with the pupil
        self.fcn : `numpy.ndarray`
            array containing the wavefunction of the pupil plane

        """
        # build a coordinate system over which to evaluate this function
        self.phase = m.zeros((self.samples, self.samples), dtype=config.precision)
        for term, coef in enumerate(self.coefs):
            # short circuit for speed
            if coef == 0:
                continue
            else:
                idx = self._map[term]
                self.phase += coef * self._cache(idx, self.normalize, self.samples)  # NOQA

        return self
Beispiel #16
0
def circle(samples=128, radius=1):
    """Create a circular mask.

    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.zeros((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
Beispiel #17
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
    radius : `float`, optional
        radius of the shape in the square output array.  radius=1 will fill the
        x

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

    """
    if radius is 0:
        return m.zeros((samples, samples), dtype=config.precision)
    else:
        rho, phi = make_rho_phi_grid(samples, samples)
        mask = m.ones(rho.shape, dtype=config.precision)
        mask[rho < radius] = 0
        return mask
Beispiel #18
0
    def __init__(self, *args, **kwargs):
        """Initialize a new Zernike instance."""
        if args is not None:
            if len(args) is 0:
                self.coefs = m.zeros(len(self._map), dtype=config.precision)
            else:
                self.coefs = m.asarray([*args[0]], dtype=config.precision)

        self.normalize = False
        pass_args = {}

        self.base = config.zernike_base
        try:
            bb = kwargs['base']
            if bb > 1:
                raise ValueError('It violates convention to use a base greater than 1.')
            elif bb < 0:
                raise ValueError('It is nonsensical to use a negative base.')
            self.base = bb
        except KeyError:
            # user did not specify base
            pass

        if kwargs is not None:
            for key, value in kwargs.items():
                if key[0].lower() == 'z':
                    idx = int(key[1:])  # strip 'Z' from index
                    self.coefs[idx - self.base] = value
                elif key in ('norm'):
                    self.normalize = value
                elif key.lower() == 'base':
                    self.base = value
                else:
                    pass_args[key] = value

        super().__init__(**pass_args)
Beispiel #19
0
    def truncate_topn(self, n):
        """Truncate the pupil to only the top n terms.

        Parameters
        ----------
        n : `int`
            number of parameters to keep

        Returns
        -------
        `self`
            modified FringeZernike instance.

        """
        topn = self.top_n(n)
        new_coefs = m.zeros(len(self.coefs), dtype=config.precision)
        for coef in topn:
            mag, index, *_ = coef
            new_coefs[index-self.base] = mag

        self.coefs = new_coefs
        self.build()
        self.mask(self._mask, self.mask_target)
        return self
Beispiel #20
0
 def mkary():  # default for defaultdict
     return m.zeros(2)
Beispiel #21
0
    def plot2d_rgbgrid(self,
                       axlim=25,
                       interp_method='lanczos',
                       pix_grid=None,
                       fig=None,
                       ax=None):
        """Create a 2D color plot of the PSF and R,G,B components.

        Parameters
        ----------
        axlim : `float`
            limits of axis, symmetric. xlim=(-axlim,axlim), ylim=(-axlim, axlim)
        interp_method : `str`
            method used to interpolate the image between samples of the PSF
        pix_grid : float
            if not None, overlays gridlines with spacing equal to pix_grid.
            Intended to show the collection into camera pixels while still in
            the oversampled domain
        fig : `matplotlib.figure.Figure`, optional
            Figure containing the plot
        ax : `matplotlib.axes.Axis`, optional:
            Axis containing the plot

        fig : `matplotlib.figure.Figure`, optional
            Figure containing the plot
        ax : `matplotlib.axes.Axis`, optional:
            Axis containing the plot

        Notes
        -----
        Need to refine internal workings at some point.

        """
        # make the arrays for the RGB images
        dat = m.empty((self.samples_y, self.samples_x, 3))
        datr = m.zeros((self.samples_y, self.samples_x, 3))
        datg = m.zeros((self.samples_y, self.samples_x, 3))
        datb = m.zeros((self.samples_y, self.samples_x, 3))
        dat[:, :, 0] = self.R
        dat[:, :, 1] = self.G
        dat[:, :, 2] = self.B
        datr[:, :, 0] = self.R
        datg[:, :, 1] = self.G
        datb[:, :, 2] = self.B

        left, right = self.unit[0], self.unit[-1]
        ax_width = 2 * axlim

        # generate a figure and axes to plot in
        fig, ax = share_fig_ax(fig, ax)
        axr, axg, axb = make_rgb_axes(ax)

        ax.imshow(dat,
                  extent=[left, right, left, right],
                  interpolation=interp_method,
                  origin='lower')

        axr.imshow(datr,
                   extent=[left, right, left, right],
                   interpolation=interp_method,
                   origin='lower')
        axg.imshow(datg,
                   extent=[left, right, left, right],
                   interpolation=interp_method,
                   origin='lower')
        axb.imshow(datb,
                   extent=[left, right, left, right],
                   interpolation=interp_method,
                   origin='lower')

        for axs in (ax, axr, axg, axb):
            ax.set(xlim=(-axlim, axlim), ylim=(-axlim, axlim))
            if pix_grid is not None:
                # if pixel grid is desired, add it
                mult = m.m.floor(axlim / pix_grid)
                gmin, gmax = -mult * pix_grid, mult * pix_grid
                pts = m.arange(gmin, gmax, pix_grid)
                ax.set_yticks(pts, minor=True)
                ax.set_xticks(pts, minor=True)
                ax.yaxis.grid(True, which='minor')
                ax.xaxis.grid(True, which='minor')
        ax.set(xlabel=r'Image Plane X [$\mu m$]',
               ylabel=r'Image Plane Y [$\mu m$]')
        axr.text(-axlim + 0.1 * ax_width,
                 axlim - 0.2 * ax_width,
                 'R',
                 color='white')
        axg.text(-axlim + 0.1 * ax_width,
                 axlim - 0.2 * ax_width,
                 'G',
                 color='white')
        axb.text(-axlim + 0.1 * ax_width,
                 axlim - 0.2 * ax_width,
                 'B',
                 color='white')
        return fig, ax