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_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.º 3
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.º 4
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.º 5
0
def test_radial_profile(plot=False):
    """ Test radial profile calculation, including circular and square apertures,
    and including with the pa_range option.
    """

    ### Tests on a circular aperture

    o = poppy_core.OpticalSystem()
    o.add_pupil(poppy.CircularAperture(radius=1.0))
    o.add_detector(0.010, fov_pixels=512)
    psf = o.calc_psf()

    rad, prof = poppy.radial_profile(psf)
    rad2, prof2 = poppy.radial_profile(psf, pa_range=[-20, 20])
    rad3, prof3 = poppy.radial_profile(psf, pa_range=[-20 + 90, 20 + 90])

    # Compute analytical Airy function, on exact same radial sampling as that profile.
    v = np.pi * rad * poppy.misc._ARCSECtoRAD * 2.0 / 1e-06
    airy = ((2 * scipy.special.jn(1, v)) / v)**2
    r0 = 33  # 0.33 arcsec ~ first airy ring in this case.
    airy_peak_envelope = airy[r0] * prof.max() / (rad / rad[r0])**3

    absdiff = np.abs(prof - airy * prof.max())

    if plot:
        import matplotlib.pyplot as plt
        plt.figure(figsize=(12, 6))
        plt.subplot(1, 2, 1)
        poppy.display_psf(psf,
                          colorbar_orientation='horizontal',
                          title='Circular Aperture, d=2 m')

        plt.subplot(1, 2, 2)
        plt.semilogy(rad, prof)
        plt.semilogy(rad2, prof2, ls='--', color='red')
        plt.semilogy(rad3, prof3, ls=':', color='cyan')
        plt.semilogy(rad, airy_peak_envelope, color='gray')
        plt.semilogy(rad, airy_peak_envelope / 50, color='gray', alpha=0.5)

        plt.semilogy(rad, absdiff, color='purple')

    # Test the radial profile is close to the analytical Airy function.
    # It's hard to define relative fractional closeness for comparisons to
    # a function with many zero crossings; we can't just take (f1-f2)/(f1+f2)
    # This is a bit of a hack but let's test that the difference between
    # numerical and analytical is always less than 1/50th of the peaks of the
    # Airy function (fit based on the 1/r^3 power law fall off)

    assert np.all(absdiff[0:300] < airy_peak_envelope[0:300] / 50)

    # Test that the partial radial profiles agree with the full one. This test is
    # a little tricky since the sampling in r may not agree exactly.
    # TODO write test comparison here

    # Let's also test that the partial radial profiles on 90 degrees agree with each other.
    # These should match to machine precision.
    assert np.allclose(prof2, prof3)

    ### Next test is on a square aperture
    o = poppy.OpticalSystem()
    o.add_pupil(poppy.SquareAperture())
    o.add_detector(0.010, fov_pixels=512)
    psf = o.calc_psf()
    rad, prof = poppy.radial_profile(psf)
    rad2, prof2 = poppy.radial_profile(psf, pa_range=[-20, 20])
    rad3, prof3 = poppy.radial_profile(psf, pa_range=[-20 + 90, 20 + 90])

    if plot:
        plt.figure(figsize=(12, 6))
        plt.subplot(1, 2, 1)
        poppy.display_psf(psf,
                          colorbar_orientation='horizontal',
                          title='Square Aperture, size=1 m')

        plt.subplot(1, 2, 2)
        plt.semilogy(rad, prof)
        plt.semilogy(rad2, prof2, ls='--', color='red')
        plt.semilogy(rad3, prof3, ls=':', color='cyan')

    assert np.allclose(prof2, prof3)
Ejemplo n.º 6
0
def test_CompoundOpticalSystem_hybrid(npix=128):
    """ Test that the CompoundOpticalSystem container works for hybrid Fresnel+Fraunhofer systems

    Defining "works correctly" here is a bit arbitrary given the different physical assumptions.
    For the purpose of this test we consider a VERY simple case, mostly a Fresnel system. We split
    out the first optic and put that in a Fraunhofer system. We then test that a compound hybrid
    system yields the same results as the original fully-Fresnel system.

    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)

    ###### Simple test case to exercise the conversion functions, with only trivial propagation
    osys1 = poppy.OpticalSystem()
    osys1.add_pupil(opt1)
    osys2 = poppy.FresnelOpticalSystem()
    osys2.add_optic(poppy.ScalarTransmission())
    osys3 = poppy.OpticalSystem()
    osys3.add_pupil(poppy.ScalarTransmission())
    osys3.add_detector(fov_pixels=64, pixelscale=0.01)
    cosys = poppy.CompoundOpticalSystem([osys1, osys2, osys3])
    psf, ints = cosys.calc_psf(return_intermediates=True)
    assert len(ints) == 4, "Unexpected number of intermediate  wavefronts"
    assert isinstance(ints[0], poppy.Wavefront), "Unexpected output type"
    assert isinstance(ints[1],
                      poppy.FresnelWavefront), "Unexpected output type"
    assert isinstance(ints[2], poppy.Wavefront), "Unexpected output type"

    ###### Harder case involving more complex actual propagations

    #===== a single Fresnel optical system =====
    osys = poppy.FresnelOpticalSystem(beam_ratio=0.25,
                                      npix=128,
                                      pupil_diameter=2 * u.m)
    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.125 * u.micron / u.pixel,
                                  fov_pixels=512),
                   distance=1 * u.m)

    #===== two systems, joined into a CompoundOpticalSystem =====
    # First part is Fraunhofer then second is Fresnel
    osys1 = poppy.OpticalSystem(npix=128,
                                oversample=4,
                                name="FIRST PART, FRAUNHOFER")
    # Note for strict consistency we need to apply a half pixel shift to optics in the Fraunhofer part;
    # this accomodates the differences between different types of image centering.
    pixscl = osys.input_wavefront().pixelscale
    halfpixshift = (pixscl * 0.5 * u.pixel).to(u.m).value
    opt1shifted = poppy.SquareAperture(shift_x=halfpixshift,
                                       shift_y=halfpixshift)
    osys1.add_pupil(opt1shifted)

    osys2 = poppy.FresnelOpticalSystem(name='SECOND PART, FRESNEL')
    osys2.add_optic(opt2, distance=10 * u.cm)
    osys2.add_optic(poppy.QuadraticLens(1.0 * u.m))
    osys2.add_optic(poppy.Detector(pixelscale=0.125 * u.micron / u.pixel,
                                   fov_pixels=512),
                    distance=1 * u.m)

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

    #===== PSF calculations =====
    psf_simple = osys.calc_psf(return_intermediates=False)
    poppy.poppy_core._log.info(
        "******=========calculation divider============******")
    psf_compound = cosys.calc_psf(return_intermediates=False)

    np.testing.assert_allclose(
        psf_simple[0].data,
        psf_compound[0].data,
        err_msg=
        "PSFs do not match between equivalent simple and compound/hybrid optical systems"
    )
Ejemplo n.º 7
0
def make_coronagraph(wfe_coeffs,npix_pupil=512,npix_detector=128,wavelength=1e-6*u.m,oversample=4,pixelscale=0.01,sensor_defocus=0.5,vortex_charge=2,llowfs=False,mask_type='fqpm',obscuration=False,lyot_factor=0.9):
    #sensor_defocus: defocus of llowfs detector in waves peak-to-valley
    
    #these values are picked rather arbitrarily, but seem to work
    aperture_radius = 3*u.m
    #lyot_radius=2.6*u.m
    lyot_radius=lyot_factor*aperture_radius
    pupil_radius = 3*aperture_radius

    
    #create the optical system
    osys = poppy.OpticalSystem("LLOWFS", oversample=oversample, npix=npix_pupil, pupil_diameter=2*pupil_radius)
    
    ap = poppy.CircularAperture(radius=aperture_radius)
    if obscuration:
        obsc = poppy.SecondaryObscuration(secondary_radius=0.5*u.m, n_supports=0)
        osys.add_pupil(poppy.CompoundAnalyticOptic(opticslist=[ap,obsc])) #osys.add_pupil(poppy.AsymmetricSecondaryObscuration(secondary_radius=0.5,support_width=0.2*u.meter,support_angle=(0,120,240)))
    else:
        osys.add_pupil(ap)
        
    error = poppy.ZernikeWFE(radius=aperture_radius, coefficients=wfe_coeffs)
    osys.add_pupil(error)
    
    #inject wavefrotn error at the pupil
    
    
    #correct for fqpm (and vvc) alignment
    osys.add_pupil(poppy.FQPM_FFT_aligner())
    osys.add_image() #helper for plotting intermediates
    
    #select mask type
    if mask_type is 'fqpm':
        cgph_mask = poppy.IdealFQPM(wavelength=wavelength,name='FQPM Mask')
    elif mask_type is 'vortex':
        cgph_mask = VortexMask(charge=vortex_charge,wavelength=wavelength,name='Vortex Mask')
    else:
        raise ValueError("mask_type must be 'fqpm' or 'vortex'")
    cgph_mask._wavefront_display_hint='phase'
    osys.add_image(cgph_mask)
    
    #correct alignment back the other way
    osys.add_pupil(poppy.FQPM_FFT_aligner(direction='backward'))
    #osys.add_pupil()
    
    lyot = poppy.CircularAperture(radius=lyot_radius,name='Lyot Stop')
    lyot._wavefront_display_hint='intensity'
    if obscuration:
        lyot_obsc = poppy.InverseTransmission(poppy.SquareAperture(size=1.4*u.m))
        lyot = poppy.CompoundAnalyticOptic(opticslist=[lyot,lyot_obsc])

    if llowfs:
        #take the rejected light for the LLOWFS
        lyot_reflection = poppy.InverseTransmission(lyot)
        lyot_extent = poppy.CircularAperture(radius=pupil_radius)
        lyot = poppy.CompoundAnalyticOptic(opticslist = [lyot_reflection,lyot_extent])
        lyot._wavefront_display_hint='intensity'
        osys.add_pupil(lyot)
        #if obscuration:
        #    obsc = poppy.InverseTransmission(obsc)
        #    osys.add_pupil(obsc)

        
        #Add a defocus term to the sensor
        #Calc of peak-to-valley WFE: https://poppy-optics.readthedocs.io/en/stable/wfe.html
        defocus_coeff = sensor_defocus*wavelength.to(u.m).value
        sensor_defocus_wfe = poppy.ZernikeWFE(radius=pupil_radius,coefficients=[0,0,0,defocus_coeff])
        osys.add_pupil(sensor_defocus_wfe)
        

        #osys.add_pupil()
        osys.add_detector(pixelscale=pixelscale, fov_pixels=npix_detector, oversample=1)
    else:
        #add lyot stop
        osys.add_pupil(lyot)
        osys.add_detector(pixelscale=pixelscale, fov_arcsec=1)
        
    return osys