Ejemplo n.º 1
0
    def test_compound_osys_errors():
        """ Test that it rejects incompatible inputs"""
        import poppy

        inputs_and_errors = ((
            None, "Missing required optsyslist argument"
        ), ([], "The provided optsyslist argument is an empty list"), ([
            poppy.CircularAperture()
        ], "All items in the optical system list must be OpticalSystem instances"
                                                                       ))

        for test_input, expected_error in inputs_and_errors:
            with pytest.raises(ValueError) as excinfo:
                poppy.CompoundOpticalSystem(test_input)
            assert _exception_message_starts_with(excinfo, expected_error)

        osys = poppy.OpticalSystem()
        osys.add_pupil(poppy.CircularAperture())

        cosys = poppy.CompoundOpticalSystem([osys])

        with pytest.raises(RuntimeError) as excinfo:
            cosys.add_pupil(poppy.SquareAperture())
        assert _exception_message_starts_with(
            excinfo, "Adding individual optical elements is disallowed")
Ejemplo n.º 2
0
def test_ShackHartmannWFS():
    """ 
	Test Shack Hartmann Wavefront Sensor class functionality. Show that spot centroid measurement changes when optical system reflects off a deformed mirror
	"""
    wavelength = 635 * u.nm
    # define nominal shack harttmann wavefront sensor:
    shwfs = sub_sampled_optics.ShackHartmannWavefrontSensor()
    dm_size = shwfs.lenslet_pitch * 24

    # setup flat wavefront and calculate nominal spot locations on SHWFS
    wf_flat = poppy.Wavefront(
        diam=dm_size,
        wavelength=wavelength,
        npix=int((shwfs.lenslet_pitch / shwfs.pixel_pitch).value *
                 shwfs.n_lenslets * 2))
    wf_flat *= poppy.CircularAperture(radius=dm_size / 2)
    # sample flat wavefront:
    shwfs.sample_wf(wf_flat)
    shwfs.get_psfs()
    flat_centroid_list = shwfs.get_centroids()

    ## define DM
    act_x = 2
    act_y = 2
    stroke = .3e-6
    dm_actuator_pitch = dm_size / 4
    dm = poppy.dms.ContinuousDeformableMirror(
        dm_shape=(4, 4),
        actuator_spacing=dm_actuator_pitch,
        radius=dm_size / 2,
        include_factor_of_two=True)
    dm.set_actuator(act_x, act_y, stroke)

    # define Wavefront object for simulation, reflect off DM
    wf = poppy.Wavefront(diam=dm_size,
                         wavelength=wavelength,
                         npix=int(
                             (shwfs.lenslet_pitch / shwfs.pixel_pitch).value *
                             shwfs.n_lenslets * 2))
    wf *= poppy.CircularAperture(radius=dm_size / 2)
    wf *= dm

    #sample actual wf and propagate to detector:
    shwfs.sample_wf(wf)
    shwfs.get_psfs()
    # reconstruct wavefront and ensure that it is nonzero after reflecting off of deformed DM
    reconstruction = shwfs.reconstruct_wavefront(flat_centroid_list).value

    assert np.count_nonzero(
        reconstruction
    ) > 0, "Wavefront reconstruction was not non-zero as expected for input DM actuation"

    return np.count_nonzero(reconstruction) > 0
Ejemplo n.º 3
0
def test_MatrixFT_FFT_Lyot_propagation_equivalence(display=False):
    """ Using a simple Lyot coronagraph prescription,
    perform a simple numerical check for consistency
    between calcPSF result of standard (FFT) propagation and
    MatrixFTCoronagraph subclass of OpticalSystem."""

    D = 2.
    wavelen = 1e-6
    ovsamp = 4

    fftcoron_annFPM_osys = poppy.OpticalSystem(oversample=ovsamp)
    fftcoron_annFPM_osys.addPupil(poppy.CircularAperture(radius=D / 2))
    spot = poppy.CircularOcculter(radius=0.4)
    diaphragm = poppy.InverseTransmission(poppy.CircularOcculter(radius=1.2))
    annFPM = poppy.CompoundAnalyticOptic(opticslist=[diaphragm, spot])
    fftcoron_annFPM_osys.addImage(annFPM)
    fftcoron_annFPM_osys.addPupil(poppy.CircularAperture(radius=0.9 * D / 2))
    fftcoron_annFPM_osys.addDetector(pixelscale=0.05, fov_arcsec=3.)

    # Re-cast as MFT coronagraph with annular diaphragm FPM
    matrixFTcoron_annFPM_osys = poppy.MatrixFTCoronagraph(
        fftcoron_annFPM_osys,
        occulter_box=diaphragm.uninverted_optic.radius_inner)

    annFPM_fft_psf = fftcoron_annFPM_osys.calcPSF(wavelen)
    annFPM_mft_psf = matrixFTcoron_annFPM_osys.calcPSF(wavelen)

    diff_img = annFPM_mft_psf[0].data - annFPM_fft_psf[0].data
    abs_diff_img = np.abs(diff_img)

    if display:
        plt.figure(figsize=(16, 3))
        plt.subplot(131)
        poppy.display_PSF(annFPM_fft_psf,
                          vmin=1e-10,
                          vmax=1e-6,
                          title='Annular FPM Lyot coronagraph, FFT')
        plt.subplot(132)
        poppy.display_PSF(annFPM_mft_psf,
                          vmin=1e-10,
                          vmax=1e-6,
                          title='Annular FPM Lyot coronagraph, Matrix FT')
        plt.subplot(133)
        plt.imshow((annFPM_mft_psf[0].data - annFPM_fft_psf[0].data),
                   cmap='gist_heat')
        plt.colorbar()
        plt.title('Difference (MatrixFT - FFT)')
        plt.show()

    print("Max of absolute difference: %.10g" % np.max(abs_diff_img))

    assert (np.all(abs_diff_img < 2e-7))
Ejemplo n.º 4
0
def test_CompoundOpticalSystem():
    """ Verify basic functionality of concatenating optical systems
    """
    opt1 = poppy.SquareAperture()
    opt2 = poppy.CircularAperture(radius=0.55)

    # a single optical system
    osys = poppy.OpticalSystem()
    osys.add_pupil(opt1)
    osys.add_pupil(opt2)
    osys.add_detector(pixelscale=0.1, fov_pixels=128)

    # two systems, joined into a CompoundOpticalSystem
    osys1 = poppy.OpticalSystem()
    osys1.add_pupil(opt1)

    osys2 = poppy.OpticalSystem()
    osys2.add_pupil(opt2)
    osys2.add_detector(pixelscale=0.1, fov_pixels=128)

    cosys = poppy.CompoundOpticalSystem([osys1, osys2])

    # PSF calculations
    psf_simple = osys.calc_psf()
    psf_compound = cosys.calc_psf()

    np.testing.assert_allclose(
        psf_simple[0].data,
        psf_compound[0].data,
        err_msg=
        "PSFs do not match between equivalent simple and compound optical systems"
    )

    # check the planes
    assert len(cosys.planes) == len(osys1.planes) + len(osys2.planes)
Ejemplo n.º 5
0
def test_OPD_in_waves_for_FITSOpticalElement():
    pupil_radius = 1 * u.m
    pupil = poppy.CircularAperture(radius=pupil_radius)
    reference_wavelength = 1 * u.um
    npix = 16
    single_wave_1um_lens = poppy.ThinLens(
        name='Defocus',
        nwaves=1,
        reference_wavelength=reference_wavelength,
        radius=pupil_radius)
    osys = poppy.OpticalSystem(oversample=1, npix=npix)
    osys.add_pupil(pupil)
    osys.add_pupil(single_wave_1um_lens)
    osys.add_detector(0.01 * u.arcsec / u.pixel, fov_pixels=3)
    # We have applied 1 wave of defocus at 1 um, so verify the center
    # has lower flux than at 2 um (it should be the 'hole' of the donut)
    psf_1um = osys.calc_psf(reference_wavelength)
    center_pixel_value = psf_1um[0].data[1, 1]
    psf_2um = osys.calc_psf(2 * reference_wavelength)
    assert psf_2um[0].data[1, 1] > psf_1um[0].data[1, 1]
    # Now, use the single_wave_1um_lens optic to make a
    # wavelength-independent 1 wave defocus
    lens_as_fits = single_wave_1um_lens.to_fits(what='opd', npix=3 * npix // 2)
    lens_as_fits[0].header['BUNIT'] = 'radian'
    lens_as_fits[0].data *= 2 * np.pi / reference_wavelength.to(u.m).value
    thin_lens_wl_indep = poppy.FITSOpticalElement(opd=lens_as_fits,
                                                  opdunits='radian')
    # We expect identical peak flux for all wavelengths, so check at 0.5x and 2x
    for prefactor in (0.5, 1.0, 2.0):
        osys = poppy.OpticalSystem(oversample=1, npix=npix)
        osys.add_pupil(pupil)
        osys.add_pupil(thin_lens_wl_indep)
        osys.add_detector(prefactor * 0.01 * u.arcsec / u.pixel, fov_pixels=3)
        psf = osys.calc_psf(wavelength=prefactor * u.um)
        assert np.isclose(center_pixel_value, psf[0].data[1, 1])
Ejemplo n.º 6
0
def test_measure_radius_at_ee():
    """ Test the function measure_radius_at_ee in poppy/utils.py which measures the encircled
    energy vs radius and return as an interpolator.
    """

    # Tests on a circular aperture
    o = poppy.OpticalSystem()
    o.add_pupil(poppy.CircularAperture())
    o.add_detector(0.010, fov_pixels=512)
    psf = o.calc_psf()

    # Create outputs of the 2 inverse functions
    rad = utils.measure_radius_at_ee(psf)
    ee = utils.measure_ee(psf)

    # The ee and rad functions should undo each other and yield the input value
    for i in [0.1, 0.5, 0.8]:
        np.testing.assert_almost_equal(i,
                                       ee(rad(i)),
                                       decimal=3,
                                       err_msg="Error: Values not equal")

    # Repeat test with normalization to psf sum=1.
    # This time we can go right up to 1.0, or at least arbitrarilyclose to it.
    rad = utils.measure_radius_at_ee(psf, normalize='total')
    ee = utils.measure_ee(psf, normalize='total')
    for i in [0.1, 0.5, 0.9999]:
        np.testing.assert_almost_equal(i,
                                       ee(rad(i)),
                                       decimal=3,
                                       err_msg="Error: Values not equal")
Ejemplo n.º 7
0
    def __init__(self):
        # CLEARP pupil info from:
        #   MODIFIED CALIBRATION OPTIC HOLDER - NIRISS
        #   DRAWING NO 196847  REV 0  COMDEV
        #   Design file name 196847Rev0.pdf sent by Loic Albert
        # Properties:
        #  39 mm outer diam, corresponds to the circumscribing pupil of JWST
        #  2.0 mm vane width
        #  6.0 mm radius for central obstruction
        # Note the circumscribing pupil of JWST is 6603.464 mm in diameter
        #  (Ball SER on geometric optics model: BALL-JWST-SYST-05-003)

        pupil_mag = 6.603464 / 39.0
        poppy.CompoundAnalyticOptic.__init__(
            self,
            (
                poppy.SecondaryObscuration(
                    secondary_radius=6.0 * pupil_mag,
                    support_width=2.0 * pupil_mag,
                    n_supports=3,
                    support_angle_offset=90 +
                    180),  # align first support with +V2 axis
                # but invert to match OTE exit pupil
                poppy.CircularAperture(radius=39 * pupil_mag / 2)),
            name='CLEARP')
Ejemplo n.º 8
0
def generate_psf(coeffs):
    coeffs = np.insert(coeffs, 0, 0)
    # Declare physical constants
    radius = 2e-3
    wavelength = 1500e-9
    FOV_pixels = 512  #Increase this (finer) 1st.
    h = 60e-6 / 2  #Increase this (wider) 2nd
    f = 4.5e-3 * 2
    theta = np.arctan(h / f) / np.pi * 180 * 60 * 60  # in arcsec
    pixscale = theta / FOV_pixels  #somehow resize this after - bilinear reduction of resolution

    coeffs = (np.asarray(coeffs) / 2) * 1e-6
    # Create PSF
    osys = poppy.OpticalSystem()
    circular_aperture = poppy.CircularAperture(radius=radius)
    osys.add_pupil(circular_aperture)
    thinlens = poppy.ZernikeWFE(radius=radius, coefficients=coeffs)
    osys.add_pupil(thinlens)
    osys.add_detector(pixelscale=pixscale, fov_pixels=FOV_pixels)
    psf_with_zernikewfe, all_wfs = osys.calc_psf(wavelength=wavelength,
                                                 display_intermediates=False,
                                                 return_intermediates=True)
    pupil_wf = all_wfs[1]  #this one ***
    final_wf = all_wfs[-1]  #sometimes referred to as wf
    # psf = psf_with_zernikewfe[0].data
    return pupil_wf.phase, final_wf.amplitude**2
Ejemplo n.º 9
0
def test_FITSOpticalElement(tmpdir):
    circ_fits = poppy.CircularAperture().to_fits(grid_size=3, npix=10)
    fn = str(tmpdir / "circle.fits")
    circ_fits.writeto(fn, overwrite=True)

    # Test passing aperture via file on disk
    foe = poppy.FITSOpticalElement(transmission=fn)
    assert foe.amplitude_file == fn
    assert np.allclose(foe.amplitude, circ_fits[0].data)

    # Test passing OPD via FITS object, along with unit conversion
    circ_fits[0].header['BUNIT'] = 'micron'  # need unit for OPD
    foe = poppy.FITSOpticalElement(opd=circ_fits)
    assert foe.opd_file == 'supplied as fits.HDUList object'
    assert np.allclose(foe.opd, circ_fits[0].data * 1e-6)

    # make a cube
    rect_mask = poppy.RectangleAperture().sample(grid_size=3, npix=10)
    circ_mask = circ_fits[0].data
    circ_fits[0].data = np.stack([circ_mask, rect_mask])
    circ_fits[0].header['BUNIT'] = 'nm'  # need unit for OPD
    fn2 = str(tmpdir / "cube.fits")
    circ_fits.writeto(fn2, overwrite=True)

    # Test passing OPD as cube, with slice default, units of nanometers
    foe = poppy.FITSOpticalElement(opd=fn2)
    assert foe.opd_file == fn2
    assert foe.opd_slice == 0
    assert np.allclose(foe.opd, circ_mask * 1e-9)

    # Same cube but now we ask for the next slice
    foe = poppy.FITSOpticalElement(opd=(fn2, 1))
    assert foe.opd_file == fn2
    assert foe.opd_slice == 1
    assert np.allclose(foe.opd, rect_mask * 1e-9)
Ejemplo n.º 10
0
def test_shifting_optics( npix=30,  grid_size = 3, display=False):
    """Test shifting (translation) of Analytic and FITS Optical elements.
    Does shifting work as expected? Is it consistent between the two classes?

    Tests the fix for #247
    """
    import poppy
    pixsize =grid_size/npix
    shift_size = np.round(0.2/pixsize)*pixsize  # by construction, an integer # of pixels

    # Create a reference array
    circ = poppy.CircularAperture()
    circ_samp = circ.sample(npix=npix, grid_size=grid_size)

    # Create a shifted version, and make sure it's different
    circ_shift = poppy.CircularAperture( shift_x=shift_size)
    circ_shift_samp = circ_shift.sample(npix=npix, grid_size=grid_size)

    if display:
        plt.imshow(circ_samp-circ_shift_samp)
    assert np.allclose(circ_samp, circ_shift_samp) is False, "Shift didn't change array"

    # Make a FITS element.
    circ_fits = circ.to_fits(npix=npix, grid_size=grid_size)

    # Show we can shift that and get the same result as shifting the analytic element
    fits_shifted = poppy.FITSOpticalElement(transmission=circ_fits, shift_x=shift_size)
    np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9,
                                       err_msg="Shifting Analytic and FITS versions are not consistent (v1, via shift_x)")

    # FITSOpticalElement also lets you specify shifts via fraction of the array. Let's
    # show that is  consistent.  This is older syntax that is discouraged, and may be
    # deprecated and removed eventually. But while available it should be correct.
    array_frac = shift_size/grid_size
    fits_shifted_v2 = poppy.FITSOpticalElement(transmission=circ_fits, shift=(array_frac, 0))
    np.testing.assert_allclose(fits_shifted.amplitude, fits_shifted_v2.amplitude, atol=1e-9,
                                       err_msg="Shifting FITS via shift/shift_x are not consistent")
    np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9,
                                       err_msg="Shifting Analytic and FITS versions are not consistent (v2, via shift)")


    # Check in a 1D cut that the amount of shift is as expected -
    # this is implicitly also checked above via the match of Analytic and FITS
    # which use totally different methods to perform the shift.
    shift_in_pixels = int(shift_size/pixsize)
    assert np.allclose(np.roll(circ_samp[npix//2], shift_in_pixels),
                               circ_shift_samp[npix//2])
    def makePSF(self, makePSFInputDict: dict, makePSFOptions: zernikeoptions):
        coeffs = makePSFInputDict["coeffs"]
        show = makePSFOptions["show"]
        units = makePSFOptions["units"]
        extraPlots = makePSFOptions["extraPlots"]

        if units is "microns":
            coeffs = np.asarray(coeffs) * 1e-6

        osys = poppy.OpticalSystem()
        circular_aperture = poppy.CircularAperture(radius=self.radius)
        osys.add_pupil(circular_aperture)
        thinlens = poppy.ZernikeWFE(radius=self.radius, coefficients=coeffs)
        osys.add_pupil(thinlens)
        # osys.add_detector(pixelscale=self.pixscale, fov_arcsec=self.FOV)
        osys.add_detector(pixelscale=self.pixscale, fov_pixels=self.FOV_pixels)

        if extraPlots:
            plt.figure(1)
        # psf_with_zernikewfe, final_wf = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show,
        #                                               return_final=True)
        psf_with_zernikewfe, all_wfs = osys.calc_psf(
            wavelength=self.wavelength,
            display_intermediates=show,
            return_intermediates=True,
        )
        final_wf = all_wfs[-1]
        pupil_wf = all_wfs[1]

        if extraPlots:
            psf = psf_with_zernikewfe
            psfImage = psf[0].data
            # plt.figure(2)
            # plt.clf()
            # poppy.display_psf(psf, normalize='peak', cmap='viridis', scale='linear', vmin=0, vmax=1)
            plt.figure(3)

            wf = final_wf
            wf = pupil_wf

            plt.clf()
            plt.pause(0.001)
            plt.subplot(1, 2, 1)
            plt.imshow(wf.amplitude**2)
            plt.title("Amplitude ^2")
            plt.colorbar()
            plt.subplot(1, 2, 2)
            plt.imshow(wf.phase)
            plt.title("Phase")
            plt.colorbar()
            plt.tight_layout()
            poppy.display_psf(psf_with_zernikewfe, title="PSF")

        self.psf = psf_with_zernikewfe
        self.wf = final_wf
        self.complex_psf = self.wf.amplitude * np.exp(1j * self.wf.phase)
        self.osys_obj = osys
        self.pupil_wf = pupil_wf
Ejemplo n.º 12
0
    def makeZernikePSF(self, coeffs=(0, 0, 0, 0, 0), show=False, units='microns',
                       extraPlots=False):
        # RADIUS = 1.0 # meters
        # WAVELENGTH = 1500e-9 # meters
        # PIXSCALE = 0.01 # arcsec / pix
        # FOV = 1 # arcsec
        # NWAVES = 1.0
        # FOV_PIXELS = 128

        if units == 'microns':
            coeffs = np.asarray(coeffs) * 1e-6

        osys = poppy.OpticalSystem()
        circular_aperture = poppy.CircularAperture(radius=self.radius)
        osys.add_pupil(circular_aperture)
        thinlens = poppy.ZernikeWFE(radius=self.radius, coefficients=coeffs)
        osys.add_pupil(thinlens)
        #osys.add_detector(pixelscale=self.pixscale, fov_arcsec=self.FOV)
        osys.add_detector(pixelscale=self.pixscale, fov_pixels=self.FOV_pixels)

        if extraPlots:
            plt.figure(1)
        # psf_with_zernikewfe, final_wf = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show,
        #                                               return_final=True)
        psf_with_zernikewfe, all_wfs = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show,
                                                      return_intermediates=True)
        final_wf = all_wfs[-1]
        pupil_wf = all_wfs[1] #this one ***

        if extraPlots:
            psf = psf_with_zernikewfe
            psfImage = psf[0].data
            plt.figure(2)
            plt.clf()
            poppy.display_psf(psf, normalize='peak', cmap='viridis', scale='linear', vmin=0, vmax=1)
            plt.pause(0.001)
            plt.figure(3)

            wf = final_wf
            wf = pupil_wf

            plt.clf()
            plt.pause(0.001)
            plt.subplot(1, 2, 1)
            plt.imshow(wf.amplitude ** 2)
            plt.title('Amplitude ^2')
            plt.colorbar()
            plt.subplot(1, 2, 2)
            plt.imshow(wf.phase)
            plt.title('Phase')
            plt.colorbar()
            plt.tight_layout()

        self.psf = psf_with_zernikewfe
        self.wf = final_wf
        self.osys_obj = osys
        self.pupil_wf = pupil_wf
Ejemplo n.º 13
0
def test_radial_profile_of_offset_source():
    """Test that we can compute the radial profile for a source slightly outside the FOV,
    compare that to a calculation for a centered source, and check we get consistent results
    for the overlapping range of the radius parameter space.

    Also, make a plot showing the consistency.
    """
    import matplotlib.pyplot as plt

    osys = poppy.OpticalSystem()
    osys.add_pupil(poppy.CircularAperture(radius=1.0))
    osys.add_detector(pixelscale=0.01, fov_pixels=128)

    # compute centered PSF
    psf0 = osys.calc_psf()

    # Compute a PSF with the source offset
    osys.source_offset_r = 1.0  # outside of FOV
    psf1 = osys.calc_psf()

    # Calculate the radial profiles of those two PSFs
    r0, p0 = poppy.radial_profile(psf0)
    # For the offset PSF, compute apparent coordinates of the offset source in that image
    # (this will be a 'virtual' pixel value outside of the FOV)
    halfsize = psf1[0].header['NAXIS1'] // 2
    offset_ypos_in_pixels = osys.source_offset_r / psf1[0].header[
        'PIXELSCL'] + halfsize
    offset_target_center_pixels = (halfsize, offset_ypos_in_pixels)
    r1, p1 = poppy.radial_profile(psf1, center=offset_target_center_pixels)

    fig, axes = plt.subplots(figsize=(16, 5), ncols=3)
    poppy.display_psf(psf0,
                      ax=axes[0],
                      title='Centered',
                      colorbar_orientation='horizontal')
    poppy.display_psf(psf1,
                      ax=axes[1],
                      title='Offset',
                      colorbar_orientation='horizontal')
    axes[2].semilogy(r0, p0)
    axes[2].semilogy(r1, p1)

    # Measure radial profiles as interpolator objects, so we can evaluate them at identical radii
    prof0 = poppy.measure_radial(psf0)
    prof1 = poppy.measure_radial(psf1, center=offset_target_center_pixels)

    # Test consistency of those two radial profiles at various radii within the overlap region
    test_radii = np.linspace(0.4, 0.8, 7)
    for rad in test_radii:
        print(prof0(rad), prof1(rad))
        axes[2].axvline(rad, ls=':', color='black')

        # Check PSF agreement within 10%;
        # also add an absolute tolerance since relative error can be higher for radii right on the dark Airy nuls
        assert np.allclose(
            prof0(rad), prof1(rad), rtol=0.1, atol=5e-8
        ), "Disagreement between centered and offset radial profiles"
Ejemplo n.º 14
0
def test_wavefront_tilt_sign_and_direction_fresnel(plot=False, npix=128):
    """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction
    Fresnel propagation version

    See also test_core.test_source_offsets_in_OpticalSystem
    """
    # Create a wavefront and apply a tilt
    wave = poppy.FresnelWavefront(beam_radius=0.5 * u.m,
                                  npix=npix,
                                  oversample=8)
    wave *= poppy.CircularAperture(radius=0.5 * u.m)

    # tilt in arcseconds
    tilt_angle = -0.2  # must be a negative number (for -X direction shift), and within the FOV

    wave.tilt(
        Xangle=tilt_angle
    )  # for this function, the input is the desired direction for the image to tilt.
    # A shift to -X is implemented by creating an OPD that increases toward +X
    n = wave.shape[0]
    assert wave.wfe[n // 2, n // 2 -
                    5] < wave.wfe[n // 2, n // 2 +
                                  5], "Wavefront error should increase to +X"

    if plot:
        plt.suptitle("Wavefront tilt sign test (Fresnel propagation)",
                     fontweight='bold')
        wave.display(what='both')

    focal_length = 1 * u.m
    wave *= poppy.QuadraticLens(f_lens=focal_length)

    wave.propagate_fresnel(focal_length)

    if plot:
        plt.figure()
        wave.display(what='both',
                     crosshairs=True,
                     imagecrop=0.00001,
                     scale='log')

    n = wave.shape[0]
    nominal_cen = n // 2  # In Fresnel mode, PSFs are centered on a pixel by default
    # (different from in Fraunhofer mode by half a pixel)

    cen = poppy.measure_centroid(wave.as_fits())
    assert np.allclose(
        cen[0], nominal_cen), "Tilt in X should not displace the PSF in Y"
    assert cen[
        1] < nominal_cen, "WFE tilt increasing to +X should displace the PSF to -X"
    assert np.allclose(
        ((cen[1] - nominal_cen) * u.pixel * wave.pixelscale).to_value(u.m),
        ((tilt_angle * u.arcsec).to_value(u.radian) * focal_length).to_value(
            u.m)), "PSF offset distance did not match expected amount"
Ejemplo n.º 15
0
def test_displays():
    # Right now doesn't check the outputs are as expected in any way
    # TODO consider doing that? But it's hard given variations in matplotlib version etc

    # As a result this just tests that the code runs to completion, without any assessment
    # of the correctness of the output displays.
    import poppy
    import matplotlib.pyplot as plt

    osys = poppy.OpticalSystem()
    osys.add_pupil(poppy.CircularAperture())
    osys.add_detector(fov_pixels=128, pixelscale=0.01 * u.arcsec / u.pixel)

    # Test optical system display
    # This implicitly exercises the optical element display paths, too
    osys.display()

    # Test PSF calculation with intermediate wavefronts
    plt.figure()
    psf = osys.calc_psf(display_intermediates=True)

    # Test PSF display
    plt.figure()
    poppy.display_psf(psf)

    # Test PSF display with other units too
    poppy.display_psf(psf, angular_coordinate_unit=u.urad)

    # Test PSF calculation with intermediate wavefronts and other units
    plt.figure()
    psf = osys.calc_psf(display_intermediates=True)
    osys2 = poppy.OpticalSystem()
    osys2.add_pupil(poppy.CircularAperture())
    osys2.add_detector(fov_pixels=128, pixelscale=0.05 * u.urad / u.pixel)
    psf2, waves = osys.calc_psf(display_intermediates=True,
                                return_intermediates=True)

    # Test wavefront display, implicitly including other units
    waves[-1].display()

    plt.close('all')
Ejemplo n.º 16
0
def test_inwave_fresnel(plot=False):
    '''Verify basic functionality of the inwave kwarg for a basic FresnelOpticalSystem()'''
    npix = 128
    oversample = 2
    # HST example - Following example in PROPER Manual V2.0 page 49.
    lambda_m = 0.5e-6 * u.m
    diam = 2.4 * u.m
    fl_pri = 5.52085 * u.m
    d_pri_sec = 4.907028205 * u.m
    fl_sec = -0.6790325 * u.m
    d_sec_to_focus = 6.3919974 * u.m

    m1 = poppy.QuadraticLens(fl_pri, name='Primary')
    m2 = poppy.QuadraticLens(fl_sec, name='Secondary')

    hst = poppy.FresnelOpticalSystem(pupil_diameter=diam,
                                     npix=npix,
                                     beam_ratio=1 / oversample)
    hst.add_optic(poppy.CircularAperture(radius=diam.value / 2))
    hst.add_optic(
        poppy.SecondaryObscuration(secondary_radius=0.396,
                                   support_width=0.0264,
                                   support_angle_offset=45.0))
    hst.add_optic(m1)
    hst.add_optic(m2, distance=d_pri_sec)
    hst.add_optic(poppy.ScalarTransmission(
        planetype=poppy_core.PlaneType.image, name='focus'),
                  distance=d_sec_to_focus)

    if plot:
        plt.figure(figsize=(12, 8))
    psf1, wfs1 = hst.calc_psf(wavelength=lambda_m,
                              display_intermediates=plot,
                              return_intermediates=True)

    # now test the system by inputting a wavefront first
    wfin = poppy.FresnelWavefront(beam_radius=diam / 2,
                                  wavelength=lambda_m,
                                  npix=npix,
                                  oversample=oversample)
    if plot:
        plt.figure(figsize=(12, 8))
    psf2, wfs2 = hst.calc_psf(wavelength=lambda_m,
                              display_intermediates=plot,
                              return_intermediates=True,
                              inwave=wfin)

    wf = wfs1[-1].wavefront
    wf_no_in = wfs2[-1].wavefront

    assert np.allclose(
        wf, wf_no_in
    ), 'Results differ unexpectedly when using inwave argument for FresnelOpticalSystem().'
Ejemplo n.º 17
0
def test_grad():
    osys = poppy.OpticalSystem()
    osys.add_pupil(poppy.CircularAperture(radius=3))  # pupil radius in meters
    osys.add_detector(pixelscale=0.025,
                      fov_arcsec=0.75)  # image plane coordinates in arcseconds

    def objective(wavelength):
        psf, intermediate = osys.propagate_mono(wavelength * 1e-6)
        return (np.sum(psf.amplitude**2.))

    thisgrad = grad(objective)
    output = thisgrad(2.0)
    assert (output - -0.01927825) < 0.0000001
    print('Gradient worked!')
Ejemplo n.º 18
0
def test_propagate():

    osys = poppy.OpticalSystem()
    osys.add_pupil(poppy.CircularAperture(radius=3))  # pupil radius in meters
    osys.add_detector(pixelscale=0.025,
                      fov_arcsec=0.75)  # image plane coordinates in arcseconds

    def objective(wavelength):
        psf, intermediate = osys.propagate_mono(wavelength * 1e-6)
        return (np.sum(psf.amplitude**2.))

    output = objective(2.)
    assert (output - 0.96685994) < 0.00001
    print('Propagation worked!')
Ejemplo n.º 19
0
def make_hawki_poppy_psf():
    import poppy

    osys = poppy.OpticalSystem()
    m1 = poppy.CircularAperture(radius=4.1)
    spiders = poppy.SecondaryObscuration(secondary_radius=0.6,
                                         n_supports=4,
                                         support_width=0.1)
    vlt = poppy.CompoundAnalyticOptic(opticslist=[m1, spiders], name='VLT')

    psfs = []
    seeing = 0.4
    see_psf = simcado.psf.seeing_psf(fwhm=seeing, size=384, pix_res=0.106 / 2)

    for lam in [1.2, 1.6, 2.2]:
        osys = poppy.OpticalSystem()
        osys.add_pupil(vlt)
        osys.add_detector(pixelscale=0.106, fov_arcsec=0.106 * 192)
        diff_psf = osys.calc_psf(lam * 1e-6)

        tmp = deepcopy(diff_psf)
        tmp[0].data = fftconvolve(diff_psf[0].data,
                                  see_psf[0].data,
                                  mode="same")
        tmp[0].data /= np.sum(tmp[0].data)

        tmp[0].header["SEEING"] = seeing
        tmp[0].header["CDELT1"] = tmp[0].header["PIXELSCL"]
        tmp[0].header["CDELT2"] = tmp[0].header["PIXELSCL"]

        psfs += tmp

    hdus = fits.HDUList(psfs)
    for i in range(1, len(hdus)):
        hdus[i] = fits.ImageHDU(data=hdus[i].data, header=hdus[i].header)

    hdus.writeto("HAWK-I_config/PSF_HAWKI_poppy.fits", clobber=True)

    fname = "HAWK-I_config/PSF_HAWKI_poppy.fits"

    plt.figure(figsize=(15, 4))
    for i in range(3):
        plt.subplot(1, 3, i + 1)
        poppy.display_PSF(fname,
                          ext=i,
                          title="HAWKI PSF lambda=" +
                          str(fits.getheader(fname, ext=i)["WAVELEN"]))

    plt.show()
Ejemplo n.º 20
0
    def __init__(self,
                 lenslet_pitch=300 * u.um,
                 lenslet_fl=14.2 * u.mm,
                 pixel_pitch=2.2 * u.um,
                 n_lenslets=12,
                 circular=False,
                 detector=None,
                 **kwargs):

        self.lenslet_pitch = lenslet_pitch
        self.lenslet_fl = lenslet_fl
        self.pixel_pitch = pixel_pitch
        self.r_lenslet = self.lenslet_pitch / 2.
        self.n_lenslets = n_lenslets

        if circular:
            aperture = poppy.CircularAperture(radius=self.lenslet_pitch / 2,
                                              planetype=PlaneType.pupil)
        else:
            ap_keywords = {
                "size": self.lenslet_pitch,
                "planetype": PlaneType.pupil
            }
            aperture = poppy.SquareAperture(size=self.lenslet_pitch,
                                            planetype=PlaneType.pupil)

        optic_array = np.array([[aperture, aperture], [aperture, aperture]])

        if detector is None:
            pixelscale = 1.0 * u.rad / (lenslet_fl * u.pix / pixel_pitch)
            pix_per_lenslet = int(lenslet_pitch / pixel_pitch)
            detector = Detector(pixelscale, fov_pixels=pix_per_lenslet)

        # expand the array to make big_optic_array
        if n_lenslets % 2 != 0:
            raise ValueError(
                "aperture replication only works for even numbers of apertures"
            )

        big_optic_array = optic_array.repeat(n_lenslets / 2.,
                                             axis=0).repeat(n_lenslets / 2.,
                                                            axis=1)

        Subapertures.__init__(self,
                              optic_array=big_optic_array,
                              detector=detector,
                              **kwargs)
        return
Ejemplo n.º 21
0
def test_CompoundOpticalSystem_fresnel(npix=128, display=False):
    """ Test that the CompoundOpticalSystem container works for Fresnel systems

    Parameters
    ----------
    npix : int
        Number of pixels for the pupil sampling. Kept small by default to
        reduce test run time.
    """

    import poppy

    opt1 = poppy.SquareAperture()
    opt2 = poppy.CircularAperture(radius=0.55)

    # a single optical system
    osys = poppy.FresnelOpticalSystem(beam_ratio=0.25, npix=npix)
    osys.add_optic(opt1)
    osys.add_optic(opt2, distance=10 * u.cm)
    osys.add_optic(poppy.QuadraticLens(1.0 * u.m))
    osys.add_optic(poppy.Detector(pixelscale=0.25 * u.micron / u.pixel,
                                  fov_pixels=512),
                   distance=1 * u.m)

    psf = osys.calc_psf(display_intermediates=display)

    if display:
        plt.figure()

    # a Compound Fresnel optical system
    osys1 = poppy.FresnelOpticalSystem(beam_ratio=0.25, npix=npix)
    osys1.add_optic(opt1)
    osys2 = poppy.FresnelOpticalSystem(beam_ratio=0.25)
    osys2.add_optic(opt2, distance=10 * u.cm)
    osys2.add_optic(poppy.QuadraticLens(1.0 * u.m))
    osys2.add_optic(poppy.Detector(pixelscale=0.25 * u.micron / u.pixel,
                                   fov_pixels=512),
                    distance=1 * u.m)

    cosys = poppy.CompoundOpticalSystem([osys1, osys2])

    psf2 = cosys.calc_psf(display_intermediates=display)

    assert np.allclose(
        psf[0].data, psf2[0].data
    ), "Results from simple and compound Fresnel systems differ unexpectedly."

    return psf, psf2
Ejemplo n.º 22
0
def get_magaox_pupil(npix, rotation=38.75, grid_size=6.5, sm=False):
    pupil_diam = 6.5 #m
    secondary = 0.293 * pupil_diam
    primary = poppy.CircularAperture(radius=pupil_diam/2.)
    sec = poppy.AsymmetricSecondaryObscuration(secondary_radius=secondary/2.,
                                                 #support_angle=(45, 135, 225, 315),
                                                 #support_width=[0.01905,]*4,
                                                 #support_offset_y=[0, -0.34, 0.34, 0],
                                                 rotation=rotation,
                                                 name='Complex secondary')
    opticslist = [primary,]
    if sm:
        opticslist.append(sec)
    pupil = poppy.CompoundAnalyticOptic( opticslist=opticslist, name='Magellan')
    sampled = pupil.sample(npix=npix, grid_size=grid_size)
    norm = np.sum(sampled)
    return sampled
Ejemplo n.º 23
0
def test_displays():
    # Right now doesn't check the outputs are as expected in any way
    # TODO consider doing that? But it's hard given variations in matplotlib version etc
    import poppy
    import matplotlib.pyplot as plt

    osys = poppy.OpticalSystem()
    osys.add_pupil(poppy.CircularAperture())
    osys.add_detector(fov_pixels=128, pixelscale=0.01)

    osys.display()

    plt.figure()
    psf = osys.calc_psf(display_intermediates=True)

    plt.figure()
    #psf = osys.calc_psf(display_intermediates=True)
    poppy.display_psf(psf)
Ejemplo n.º 24
0
    def __init__(self,  name='Gemini South Primary', undersized=False):
        outer = poppy.CircularAperture(radius=self.primary_diameter/2)
        outer.pupil_diam = 8.0   # slightly oversized array

        # Secondary obscuration from pupil diagram provided by Gemini

        sr = self.obscuration_diameter/2
        if undersized:
            sr = 1.02375/2 # SM outer diameter (vs inner hole projected diameter)

        # FIXME could antialias using same technique as used for apodizer grids
        obscuration = poppy.AsymmetricSecondaryObscuration(
                            secondary_radius=sr,
                            support_angle=self.support_angles,
                            support_width=self.support_widths,
                            support_offset_y=self.support_offset_y)

        return super(GeminiPrimary,self).__init__(opticslist=[outer,obscuration], name=name)
Ejemplo n.º 25
0
def circular_intensity(wvl, pupil_radius, pixel_scale_par=0.009):
    """ Calculate and plot circular intensity
        circular_intensity(wvl, pupil_radius, pixel_scale_par = 0.05)
        wvl : wavelength in microns
    """

    osys = poppy.OpticalSystem()
    osys.add_pupil(
        poppy.CircularAperture(radius=pupil_radius))  # pupil radius in meters
    planeCor = pupil_radius  # This line will let us change coordinates of the plane according to the pupil radius to better represent the diffraction pattern
    if pupil_radius <= 0.49:
        planeCor = pupil_radius * 10
    osys.add_detector(
        pixelscale=pixel_scale_par,
        fov_arcsec=planeCor)  # image plane coordinates in arcseconds

    psf = osys.calc_psf(wvl)  # wavelength in meters
    poppy.display_psf(psf, title='The Circular Aperture')
Ejemplo n.º 26
0
def test_inwave_fraunhofer(plot=False):
    '''Verify basic functionality of the inwave kwarg for a basic OpticalSystem()'''
    npix = 128
    oversample = 2
    diam = 2.4 * u.m
    lambda_m = 0.5e-6 * u.m
    # calculate the Fraunhofer diffraction pattern
    hst = poppy.OpticalSystem(pupil_diameter=diam,
                              npix=npix,
                              oversample=oversample)
    hst.add_pupil(poppy.CircularAperture(radius=diam.value / 2))
    hst.add_pupil(
        poppy.SecondaryObscuration(secondary_radius=0.396,
                                   support_width=0.0264,
                                   support_angle_offset=45.0))
    hst.add_image(
        poppy.ScalarTransmission(planetype=poppy_core.PlaneType.image,
                                 name='focus'))

    if plot:
        plt.figure(figsize=(9, 3))
    psf1, wfs1 = hst.calc_psf(wavelength=lambda_m,
                              display_intermediates=plot,
                              return_intermediates=True)

    # now test the system by inputting a wavefront first
    wfin = poppy.Wavefront(wavelength=lambda_m,
                           npix=npix,
                           diam=diam,
                           oversample=oversample)
    if plot:
        plt.figure(figsize=(9, 3))
    psf2, wfs2 = hst.calc_psf(wavelength=lambda_m,
                              display_intermediates=plot,
                              return_intermediates=True,
                              inwave=wfin)

    wf = wfs1[-1].wavefront
    wf_no_in = wfs2[-1].wavefront

    assert np.allclose(
        wf, wf_no_in
    ), 'Results differ unexpectedly when using inwave argument in OpticalSystem().'
Ejemplo n.º 27
0
def test_wavefront_tilt_sign_and_direction(plot=False, npix=128):
    """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction
    Fraunhofer propagation version

    See also test_core.test_source_offsets_in_OpticalSystem
    """
    # Create a wavefront and apply a tilt
    wave = poppy.Wavefront(diam=1 * u.m, npix=npix)
    wave *= poppy.CircularAperture(radius=0.5 * u.m)

    tilt_angle = -0.2  # must be a negative number (for -X direction shift), and within the FOV

    wave.tilt(
        Xangle=tilt_angle
    )  # for this function, the input is the desired direction for the image to tilt.
    # A shift to -X is implemented by creating an OPD that increases toward +X
    n = wave.shape[0]
    assert wave.wfe[n // 2, n // 2 -
                    5] < wave.wfe[n // 2, n // 2 +
                                  5], "Wavefront error should increase to +X"

    if plot:
        plt.suptitle("Wavefront tilt sign test (Fraunhofer propagation)",
                     fontweight='bold')
        wave.display(what='both')

    wave.propagate_to(poppy.Detector(pixelscale=0.05, fov_pixels=128))

    if plot:
        plt.figure()
        wave.display(what='both', crosshairs=True, imagecrop=2)

    n = wave.shape[0]
    cen = poppy.measure_centroid(wave.as_fits())
    assert np.allclose(cen[0], (n - 1) /
                       2), "Tilt in X should not displace the PSF in Y"
    assert cen[1] < (
        n - 1) / 2, "WFE tilt increasing to +X should displace the PSF to -X"
    assert np.allclose(((cen[1] - (n - 1) / 2) * u.pixel *
                        wave.pixelscale).to_value(u.arcsec),
                       tilt_angle), "PSF offset did not match expected amount"
Ejemplo n.º 28
0
def test_measure_radius_at_ee():
    """ Test the function measure_radius_at_ee in poppy/utils.py which measures the encircled
    energy vs radius and return as an interpolator.
    """

    # Tests on a circular aperture
    o = poppy.OpticalSystem()
    o.add_pupil(poppy.CircularAperture())
    o.add_detector(0.010, fov_pixels=512)
    psf = o.calc_psf()

    # Create outputs of the 2 inverse functions
    rad = utils.measure_radius_at_ee(psf)
    ee = utils.measure_ee(psf)

    # The ee and rad functions should undo each other and yield the input value
    for i in [0.1, 0.5, 0.8]:
        np.testing.assert_almost_equal(i,
                                       ee(rad(i)),
                                       decimal=3,
                                       err_msg="Error: Values not equal")
Ejemplo n.º 29
0
def test_wavefront_conversions():
    """ Test conversions between Wavefront and FresnelWavefront
    in both directions.
    """
    import poppy

    props = lambda wf: (wf.shape, wf.ispadded, wf.oversample, wf.pixelscale)

    optic = poppy.CircularAperture()
    w = poppy.Wavefront(diam=4 * u.m)
    w *= optic

    fw = poppy.FresnelWavefront(beam_radius=2 * u.m)
    fw *= optic

    # test convert from Fraunhofer to Fresnel
    fw2 = poppy.FresnelWavefront.from_wavefront(w)
    assert props(fw) == props(fw2)
    #np.testing.assert_allclose(fw.wavefront, fw2.wavefront)

    # test convert from Fresnel to Fraunhofer
    w2 = poppy.Wavefront.from_fresnel_wavefront(fw)
    assert props(w) == props(w2)
wf_error_budget = [] * n_coeff

print('Select desired coefficient distribution (default Sigmoid):')
print('Sigmoid: 1 \nGaussian: 2 \nExponential: 3 \n')
distribution = input()

if distribution == 2:
    wf_error_budget = gaussian_budget(n_coeff)
elif distribution == 3:
    wf_error_budget = exponential_budget(n_coeff)
else:
    wf_error_budget = sigmoid_budget(n_coeff)

#%% --------------------------------- OPTICAL ELEMENTS ----------------------------------------------

aperture = poppy.CircularAperture(radius=radius, name='Pupil')

objective = poppy.QuadraticLens(fl_obj, name='Objective lens')

# ----------------------------------- OPTICAL SYSTEM ------------------------------------------------

# Initialize results as lists
psf_results = []
zernike_coeff = []

file_h5, group_h5 = init_h5(save_dir)

for image_idx in range(n_set):

    osys = poppy.FresnelOpticalSystem(pupil_diameter=10 * radius,
                                      npix=256,