Beispiel #1
0
def _compute_output_grid(convolvable1, convolvable2):
    """Calculate the output grid to be used when two convolvables are on different grids."""
    # determine output spacing
    errorstr = 'when convolving two analytic objects, one must have a grid.'
    if m.isnan(convolvable1.sample_spacing):
        if m.isnan(convolvable2.sample_spacing):
            raise ValueError(errorstr)
        else:
            output_spacing = convolvable2.sample_spacing
    elif m.isnan(convolvable2.sample_spacing):
        if m.isnan(convolvable1.sample_spacing):
            raise ValueError(errorstr)
        else:
            output_spacing = convolvable1.sample_spacing
    else:
        output_spacing = min(convolvable1.sample_spacing,
                             convolvable2.sample_spacing)

    # determine region of output
    if convolvable1.unit_x is None:
        c1ux, c1uy = (0, 0), (0, 0)
    else:
        c1ux, c1uy = convolvable1.unit_x, convolvable1.unit_y

    if convolvable2.unit_x is None:  # this isn't totally DRY
        c2ux, c2uy = (0, 0), (0, 0)
    else:
        c2ux, c2uy = convolvable2.unit_x, convolvable2.unit_y

    output_x_left = min(c1ux[0], c2ux[0])
    output_x_right = max(c1ux[-1], c2ux[-1])
    output_y_left = min(c1uy[0], c2uy[0])
    output_y_right = max(c1uy[-1], c2uy[-1])

    # if region is not an integer multiple of sample spacings, enlarge to make this true
    x_rem = (output_x_right - output_x_left) % output_spacing
    y_rem = (output_y_right - output_y_left) % output_spacing
    if x_rem > 1e-3:
        adj = x_rem / 2
        output_x_left -= adj
        output_x_right += adj
    if y_rem > 1e-3:
        adj = y_rem / 2
        output_y_left -= adj
        output_y_right += adj

    # finally, compute the output window
    samples_x = int((output_x_right - output_x_left) // output_spacing)
    samples_y = int((output_y_right - output_y_left) // output_spacing)
    unit_out_x = m.linspace(output_x_left, output_x_right, samples_x)
    unit_out_y = m.linspace(output_y_left, output_y_right, samples_y)
    return unit_out_x, unit_out_y
Beispiel #2
0
def _encircled_energy_core(mtf_data, radius, nu_p, dx, dy):
    """Core computation of encircled energy, based on Baliga 1988.

    Parameters
    ----------
    mtf_data : `numpy.ndarray`
        unaliased MTF data
    radius : `float`
        radius of "detector"
    nu_p : `numpy.ndarray`
        radial spatial frequencies
    dx : `float`
        x frequency delta
    dy : `float`
        y frequency delta

    Returns
    -------
    `float`
        encircled energy for given radius

    """
    integration_fourier = m.j1(2 * m.pi * radius * nu_p) / nu_p
    # division by nu_p will cause a NaN at the origin, 0.5 is the
    # analytical value of jinc there
    integration_fourier[m.isnan(integration_fourier)] = 0.5
    dat = mtf_data * integration_fourier
    return radius * dat.sum() * dx * dy
Beispiel #3
0
    def fcn(self):
        """Complex wavefunction associated with the pupil."""
        phase = self.change_phase_unit(to='waves', inplace=False)

        fcn = m.exp(1j * 2 * m.pi *
                    phase)  # phase implicitly in units of waves, no 2pi/l
        # guard against nans in phase
        fcn[m.isnan(phase)] = 0

        if self.mask_target in ('fcn', 'both'):
            fcn *= self._mask

        return fcn
Beispiel #4
0
    def _phase_to_wavefunction(self):
        """Compute the wavefunction from the phase.

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

        """
        phase = self.change_phase_unit(to='waves', inplace=False)

        self.fcn = m.exp(1j * 2 * m.pi *
                         phase)  # phase implicitly in units of waves, no 2pi/l
        # guard against nans in phase
        self.fcn[m.isnan(phase)] = 0
        return self
Beispiel #5
0
    def fill(self, _with=0):
        """Fill invalid (NaN) values.

        Parameters
        ----------
        _with : `float`, optional
            value to fill with

        Returns
        -------
        `Interferogram`
            self

        """
        nans = m.isnan(self.phase)
        self.phase[nans] = _with
        return self
Beispiel #6
0
 def dropout_percentage(self):
     """Percentage of pixels in the data that are invalid (NaN)."""
     return m.count_nonzero(m.isnan(self.phase)) / self.phase.size * 100
Beispiel #7
0
    def __init__(self,
                 samples=128,
                 dia=1.0,
                 wavelength=0.55,
                 opd_unit='waves',
                 mask='circle',
                 mask_target='both',
                 ux=None,
                 uy=None,
                 phase=None):
        """Create a new `Pupil` instance.

        Parameters
        ----------
        samples : `int`, optional
            number of samples across the pupil interior
        dia : `float`, optional
            diameter of the pupil, mm
        wavelength : `float`, optional
            wavelength of light, um
        opd_unit : `str`, optional, {'waves', 'um', 'nm'}
            unit used to m.express the OPD.  Equivalent strings may be used to the
            valid options, e.g. 'microns', or 'nanometers'
        mask : `str` or `numpy.ndarray`
            mask used to define the amplitude and boundary of the pupil; any
            regular polygon from `prysm.geometry` as a string, e.g. 'circle' is
            valid.  A user-provided ndarray can also be used.
        mask_target : `str`, {'phase', 'fcn', 'both', None}
            which array to mask during pupil creation; only masking fcn is
            faster for numerical propagations but will make plot2d() and the
            phase array not be truncated properly.  Note that if the mask is not
            binary and `phase` or `both` is used, phase plots will also not be
            correct, as they will be attenuated by the mask.
        ux : `np.ndarray`
            x axis units
        uy : `np.ndarray`
            y axis units
        phase : `np.ndarray`
            phase data

        Notes
        -----
        If ux give, assume uy and phase also given; skip much of the pupil building process
        and simply copy values.

        Raises
        ------
        ValueError
            if the OPD unit given is invalid

        """
        if ux is None:
            # must build a pupil
            self.dia = dia
            ux = m.linspace(-dia / 2, dia / 2, samples)
            uy = m.linspace(-dia / 2, dia / 2, samples)
            self.samples = samples
            need_to_build = True
        else:
            # data already known
            need_to_build = False
        super().__init__(unit_x=ux,
                         unit_y=uy,
                         phase=phase,
                         wavelength=wavelength,
                         phase_unit=opd_unit,
                         spatial_unit='mm')
        self.xaxis_label = 'Pupil ξ'
        self.yaxis_label = 'Pupil η'
        self.zaxis_label = 'OPD'
        self.rho = self.phi = None

        if need_to_build:
            if type(mask) is not m.ndarray:
                mask = mcache(mask, self.samples)

            self._mask = mask
            self.mask_target = mask_target
            self.build()
            self.mask(self._mask, self.mask_target)
        else:
            protomask = m.isnan(phase)
            mask = m.ones(protomask.shape)
            mask[protomask] = 0
            self._mask = mask
            self.mask_target = 'fcn'
Beispiel #8
0
def write_zygo_ascii(file,
                     phase,
                     unit_x,
                     unit_y,
                     wavelength=0.6328,
                     intensity=None,
                     high_phase_res=False):

    # construct the header
    timestamp = datetime.datetime.now()
    line1 = 'Zygo ASCII Data File - Format 2'
    line2 = '0 0 0 0 ' + timestamp.strftime('"%a %b %d %H:%M:%S %Y').ljust(
        30, ' ') + '"'
    if intensity is None:
        line3 = '0 0 0 0 0 0'
    else:
        raise NotImplementedError(
            'writing of ASCII files with nonempty intensity not yet supported.'
        )
    px, py = phase.shape
    ox = m.searchsorted(unit_x, 0)
    oy = m.searchsorted(unit_y, 0)
    line4 = f'{oy} {ox} {py} {px}'
    line5 = '"' + ' ' * 81 + '"'
    line6 = '"' + ' ' * 39 + '"'
    line7 = '"' + ' ' * 39 + '"'

    timestamp_int = int(str(timestamp.timestamp()).split('.')[0])
    res = (unit_x[1] - unit_x[0]) * 1e-3  # mm to m
    line8 = f'0 0.5 {wavelength*1e-6} 0 1 0 {res} {timestamp_int}'  # end is timestamp in integer seconds
    line9 = f'{py} {px} 0 0 0 0 ' + '"' + ' ' * 9 + '"'
    line10 = '0 0 0 0 0 0 0 0 0 0'
    line11 = f'{int(high_phase_res)} 1 20 2 0 0 0 0 0'
    line12 = '0 ' + '"' + ' ' * 12 + '"'
    line13 = '1 0'
    line14 = '"' + ' ' * 7 + '"'

    header_lines = (line1, line2, line3, line4, line5, line6, line7, line8,
                    line9, line10, line11, line12, line13, line14)
    header = '\n'.join(header_lines) + '\n'

    if intensity is None:
        line15 = '#'

    line16 = '#'

    # process the phase and write out
    coef = ZYGO_PHASE_RES_FACTORS[int(high_phase_res)]
    encoded_phase = phase * (coef / wavelength / wavelength / 0.5)
    encoded_phase[m.isnan(encoded_phase)] = ZYGO_INVALID_PHASE
    encoded_phase = m.flipud(encoded_phase.astype(m.int64))
    encoded_phase = encoded_phase.flatten()
    npts = encoded_phase.shape[0]
    fits_by_ten = npts // 10
    boundary = 10 * fits_by_ten

    # create an in-memory buffer and write out the phase to it
    s = StringIO()
    s.write(header)
    s.write('#\n#\n')
    m.savetxt(s,
              encoded_phase[:boundary].reshape(-1, 10),
              fmt='%d',
              delimiter=' ',
              newline=' \n')
    tail = ' '.join((str(d) for d in encoded_phase[boundary:]))
    s.write(tail)
    s.write('\n#\n')
    s.seek(0)

    if not isinstance(file, IOBase):
        with open(file, 'w') as fd:
            shutil.copyfileobj(s, fd)
    else:
        shutil.copyfileobj(s, fd)