Exemple #1
0
def display_multiHex(rings_number_mother, sec_rad_mother, side_dist_mother = 1.0,segment_gap_mother = 0.01, pixel_scale = 0.010, fov_arcsec_mag = 2.0, figure_size = (12 ,8),wvl = 1e-6):
    
    def multi_hexagon(rings_number, sec_rad, side_dist = 1.0, segment_gap = 0.01):
            """ 
    multi_hexagon(rings_number, sec_rad, side_dist = 1.0, segment_gap = 0.01)
    # rings : The number of rings of hexagons to include, not counting the central segment
    
    # side_dist : Distance between sides (flat-to-flat) of the hexagon, in meters. Default is 1.0
    
    # segment_gap : Gap between adjacent segments, in meters. Default is 0.01 m = 1 cm
    
    # sec_rad : scondary obstacle radius
        
            """
        
            ap = poppy.MultiHexagonAperture(name='ApertureHex', flattoflat = side_dist, gap = segment_gap,rings =rings_number)  # 3 rings of 2 m segments yields 14.1 m circumscribed diameter
            sec = poppy.SecondaryObscuration(secondary_radius = float(sec_rad), n_supports = 4, support_width = 0.1)   # secondary with spiders
            atlast = poppy.CompoundAnalyticOptic( opticslist=[ap, sec], name='Mock ATLAST')           # combine into one optic
        
            plt.figure(figsize=(12,8))
            atlast.display(npix=1024, colorbar_orientation='vertical')
            return atlast
    
    osys = poppy.OpticalSystem()
    osys.add_pupil(multi_hexagon(rings_number_mother,sec_rad_mother,side_dist_mother,segment_gap_mother))
    osys.add_detector(pixelscale=pixel_scale, fov_arcsec=fov_arcsec_mag)
    psf = osys.calc_psf(wvl)

    plt.figure(figsize=figure_size)
    poppy.display_psf(psf, title="Diffraction Pattern")
def rectangle_intensity(rot=0, wd=2, ht=1, xshi=0, yshi=0):
    """Calculate light intensity for light going through a rectangular slit
    rectangle_intensity(rot = 0, wd= 2,ht = 1, xshi = 0,yshi = 0)
    
    rot : rotation in degrees
    
    wd : width in m
    
    ht : height in m
    
    xshi : x axis shift in m
    
    yshi : y axis shift in m
    
    """
    ap = poppy.RectangleAperture(rotation=rot,
                                 width=wd,
                                 height=ht,
                                 shift_x=xshi,
                                 shift_y=yshi)
    ap.display(colorbar=False)

    osys = poppy.OpticalSystem()
    osys.add_pupil(ap)
    osys.add_detector(pixelscale=0.05, fov_arcsec=2.0)
    psf = osys.calc_psf(1e-6)

    plt.figure(figsize=(12, 8))
    poppy.display_psf(psf, title="Diffraction Pattern")
Exemple #3
0
def test_rotation_in_OpticalSystem(display=False, npix=1024):
    """ Test that rotation planes within an OpticalSystem work as
    expected to rotate the wavefront. We can get equivalent results
    by rotating an Optic a given amount counterclockwise, or rotating the wavefront
    in the same direction.
    """

    angles_and_tolerances = ((90, 1e-8), (45, 3e-7))

    for angle, atol in angles_and_tolerances:
        osys = poppy.OpticalSystem(npix=npix)
        osys.add_pupil(poppy.optics.ParityTestAperture(rotation=angle))
        osys.add_detector(fov_pixels=128, pixelscale=0.01)
        psf1 = osys.calc_psf()

        osys2 = poppy.OpticalSystem(npix=npix)
        osys2.add_pupil(poppy.optics.ParityTestAperture())
        osys2.add_rotation(angle=angle)  # note, same sign here.
        osys2.add_detector(fov_pixels=128, pixelscale=0.01)
        psf2 = osys2.calc_psf()

        if display:
            fig, axes = plt.subplots(figsize=(16, 5), ncols=2)
            poppy.display_psf(psf1, ax=axes[0])
            axes[0].set_title("Optic rotated {} deg".format(angle))
            poppy.display_psf(psf2, ax=axes[1])
            axes[1].set_title("Wavefront rotated {} deg".format(angle))

        assert np.allclose(psf1[0].data, psf2[0].data, atol=atol), (
            "PSFs did not agree "
            f"within the requested tolerance, for angle={angle}."
            f"Max |difference| = {np.max(np.abs(psf1[0].data - psf2[0].data))}"
        )
def test_MFT_FFT_equivalence_in_OpticalSystem(tmpdir, display=False, source_offset=1):
    """ Test that propagating Wavefronts through an OpticalSystem
    using an MFT and an FFT give equivalent results.

    This is a somewhat higher level test that involves all the
    Wavefront class's _propagateTo() machinery, which is not
    tested in the above function. Hence the two closely related tests.

    This test now includes a source offset, to test equivalence of handling for
    nonzero WFE, in this case for tilts.
    """


    # Note that the Detector class and Wavefront propagation always uses
    # ADJUSTABLE-style MFTs (output centered in the array)
    # which is not compatible with FFT outputs for even-sized arrays.
    # Thus in order to get an exact equivalence, we have to set up our
    # OpticalSystem so that it, very unusually, uses an odd size for
    # its input wavefront. The easiest way to do this is to discretize
    # an AnalyticOpticalElement onto a specific grid.

    fn = str(tmpdir / "test.fits")
    fits511 = optics.ParityTestAperture().to_fits(fn, wavelength=1e-6, npix=511)
    pup511 = poppy_core.FITSOpticalElement(transmission=fits511)


    # set up simple optical system that will just FFT
    fftsys = poppy_core.OpticalSystem(oversample=1)
    fftsys.add_pupil(pup511)
    fftsys.add_image()
    fftsys.source_offset_r = source_offset
    fftsys.source_offset_theta = 90

    fftpsf, fftplanes = fftsys.calc_psf(display=False, return_intermediates=True)

    # set up equivalent using an MFT, tuned to get the exact same scale
    # for the image plane
    mftsys = poppy_core.OpticalSystem(oversample=1)
    mftsys.add_pupil(pup511)
    mftsys.add_detector(pixelscale=fftplanes[1].pixelscale , fov_pixels=fftplanes[1].shape, oversample=1) #, offset=(pixscale/2, pixscale/2))
    mftsys.source_offset_r = source_offset
    mftsys.source_offset_theta = 90

    mftpsf, mftplanes = mftsys.calc_psf(display=False, return_intermediates=True)


    if display:
        import poppy
        plt.figure(figsize=(15,4))
        plt.subplot(131)
        poppy.display_psf(fftpsf, title="FFT PSF")
        plt.subplot(132)
        poppy.display_psf(mftpsf, title='MFT PSF')
        plt.subplot(133)
        poppy.display_psf_difference(fftpsf, mftpsf, title='Diff FFT-MFT')



    assert( np.all(  np.abs(mftpsf[0].data-fftpsf[0].data) < 1e-10 ))
    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
Exemple #6
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"
    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
Exemple #8
0
def test_MatrixFT_FFT_Lyot_propagation_equivalence(display=False):
    """ Using a simple Lyot coronagraph prescription,
    perform a simple numerical check for consistency
    between calc_psf 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.add_pupil(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.add_image(annFPM)
    fftcoron_annFPM_osys.add_pupil(poppy.CircularAperture(radius=0.9 * D / 2))
    fftcoron_annFPM_osys.add_detector(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.calc_psf(wavelen)
    annFPM_mft_psf = matrixFTcoron_annFPM_osys.calc_psf(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))
Exemple #9
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)
Exemple #10
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')
Exemple #11
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)
def test_MatrixFT_FFT_Lyot_propagation_equivalence(display=False):
    """ Using a simple Lyot coronagraph prescription,
    perform a simple numerical check for consistency
    between calc_psf 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.add_pupil( 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.add_image( annFPM )
    fftcoron_annFPM_osys.add_pupil( poppy.CircularAperture(radius=0.9*D/2) )
    fftcoron_annFPM_osys.add_detector( 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.calc_psf(wavelen)
    annFPM_mft_psf = matrixFTcoron_annFPM_osys.calc_psf(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) )
Exemple #13
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')
                      fov_pixels=n_pix,
                      distance=fl_obj + delta)

    # System description just one time
    if image_idx == 1:
        print('\nOptical System description:')
        osys.describe()
        print('\n')

    #plt.figure(figsize=(12,8))
    psf = osys.calc_psf(wavelength=wavelength,
                        display_intermediates=False,
                        return_intermediates=False)

    plt.figure(figsize=(10, 10))
    poppy.display_psf(psf, normalize='total')
    plt.title('objective lens PSF_{},'.format(image_idx))

    if crop:

        psf_train = crop_in_center(psf[0].data, crop_size=crop_size)
    else:
        psf_train = psf[0].data

    if normalisation:

        psf_train = psf_train / np.max(psf_train)

    if gauss_noise:

        psf_train = gaussian_noise(psf_train)
Exemple #15
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)
Exemple #16
0
def measure_strehl(HDUlist_or_filename=None,
                   ext=0,
                   slice=0,
                   center=None,
                   display=True,
                   verbose=True,
                   cache_perfect=False):
    """ Estimate the Strehl ratio for a PSF.

    This requires computing a simulated PSF with the same
    properties as the one under analysis.

    Note that this calculation will not be very accurate unless both PSFs are well sampled,
    preferably several times better than Nyquist. See
    `Roberts et al. 2004 SPIE 5490 <http://adsabs.harvard.edu/abs/2004SPIE.5490..504R>`_
    for a discussion of the various possible pitfalls when calculating Strehl ratios.

    WARNING: This routine attempts to infer how to calculate a perfect reference
    PSF based on FITS header contents. It will likely work for simple direct imaging
    cases with WebbPSF but will not work (yet) for more complicated cases such as
    coronagraphy, anything with image or pupil masks, etc. Code contributions to add
    such cases are welcomed.


    Parameters
    ----------
    HDUlist_or_filename : string
        Either a fits.HDUList object or a filename of a FITS file on disk
    ext : int
        Extension in that FITS file
    slice : int, optional
        If that extension is a 3D datacube, which slice (plane) of that datacube to use
    center : tuple
        center to compute around.  Default is image center. If the center is on the
        crosshairs between four pixels, then the mean of those four pixels is used.
        Otherwise, if the center is in a single pixel, then that pixel is used.
    verbose, display : bool
        control whether to print the results or display plots on screen.

    cache_perfect : bool
        use caching for perfect images? greatly speeds up multiple calcs w/ same config

    Returns
    ---------
    strehl : float
        Strehl ratio as a floating point number between 0.0 - 1.0

    """

    from .webbpsf_core import instrument
    from poppy import display_psf

    if isinstance(HDUlist_or_filename, str):
        HDUlist = fits.open(HDUlist_or_filename)
    elif isinstance(HDUlist_or_filename, fits.HDUList):
        HDUlist = HDUlist_or_filename
    else:
        raise ValueError("input must be a filename or HDUlist")

    image = HDUlist[ext].data
    header = HDUlist[ext].header

    if image.ndim >= 3:  # handle datacubes gracefully
        image = image[slice, :, :]

    if center is None:
        # get exact center of image
        # center = (image.shape[1]/2, image.shape[0]/2)
        center = tuple((a - 1) / 2.0 for a in image.shape[::-1])

    # Compute a comparison image
    _log.info("Now computing image with zero OPD for comparison...")
    inst = instrument(header['INSTRUME'])
    inst.filter = header['FILTER']
    inst.pupilopd = None  # perfect image
    inst.include_si_wfe = False  # perfect image
    inst.pixelscale = header['PIXELSCL'] * header[
        'OVERSAMP']  # same pixel scale pre-oversampling
    cache_key = (header['INSTRUME'], header['FILTER'], header['PIXELSCL'],
                 header['OVERSAMP'], header['FOV'], header['NWAVES'])
    try:
        comparison_psf = _Strehl_perfect_cache[cache_key]
    except KeyError:
        comparison_psf = inst.calc_psf(fov_arcsec=header['FOV'],
                                       oversample=header['OVERSAMP'],
                                       nlambda=header['NWAVES'])
        if cache_perfect: _Strehl_perfect_cache[cache_key] = comparison_psf

    comparison_image = comparison_psf[0].data

    if (int(center[1]) == center[1]) and (int(center[0]) == center[0]):
        # individual pixel
        meas_peak = image[center[1], center[0]]
        ref_peak = comparison_image[center[1], center[0]]
    else:
        # average across a group of 4
        bot = [int(np.floor(f)) for f in center]
        top = [int(np.ceil(f) + 1) for f in center]
        meas_peak = image[bot[1]:top[1], bot[0]:top[0]].mean()
        ref_peak = comparison_image[bot[1]:top[1], bot[0]:top[0]].mean()
    strehl = (meas_peak / ref_peak)

    if display:
        plt.clf()
        plt.subplot(121)
        display_psf(HDUlist, title="Observed PSF")
        plt.subplot(122)
        display_psf(comparison_psf, title="Perfect PSF")
        plt.gcf().suptitle("Strehl ratio = %.3f" % strehl)

    if verbose:
        print("Measured peak:  {0:.3g}".format(meas_peak))
        print("Reference peak: {0:.3g}".format(ref_peak))
        print("  Strehl ratio: {0:.3f}".format(strehl))

    return strehl
Exemple #17
0
    def create_image(self, coefficient_set_init=None, input_noise=None):
        # This is the function that creates the image (will probably call the config file) from some zernike coefficients
        # Copy paste the code that creates the image here

        import poppy
        from astropy.io import fits

        pupil_diameter = 6.559  # (in meter) As used in WebbPSF
        pupil_radius = pupil_diameter / 2

        osys = poppy.OpticalSystem()

        transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz'
        #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits'
        opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz'
        hdul = fits.open(opd)
        hdul2 = fits.open(transmission)

        # Create wavefront map
        #print(coefficient_set_init)
        zernike_coefficients = np.append(0, coefficient_set_init)
        #zernike_coefficients *= 1e6 # conversion from meters to microns
        #wavefront_map = poppy.ZernikeWFE(radius=pupil_radius,
        #                                     coefficients=zernike_coefficients,
        #                                     aperture_stop=False)
        #print(zernike_coefficients)
        wavefront_map = poppy.zernike.opd_from_zernikes(
            zernike_coefficients,
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)
        wavefront_map = np.nan_to_num(wavefront_map) * hdul2[0].data

        fits.writeto('wavefront_map.fits',
                     wavefront_map,
                     hdul[0].header,
                     overwrite=True)
        #opd = wavefront_map
        opd = 'wavefront_map.fits'

        #myoptic = poppy.FITSOpticalElement(transmission='transfile.fits', opd='opdfile.fits', pupilscale="PIXELSCL")
        #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz'

        jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd)
        #jwst_opd = poppy.FITSOpticalElement(transmission=transmission)

        osys.add_pupil(jwst_opd)  # JWST pupil
        osys.add_detector(
            pixelscale=0.063, fov_arcsec=self.fov_arcsec,
            oversample=4)  # image plane coordinates in arcseconds

        psf = osys.calc_psf(4.44e-6)  # wavelength in microns
        psf_poppy = np.array(psf[0].data)
        poppy.display_psf(psf, title='JWST NIRCam test')

        #psf1 = osys.calc_psf(4.44e-6)                            # wavelength in microns
        #psf2 = osys.calc_psf(2.50e-6)                            # wavelength in microns
        #psf_poppy = psf1[0].data/2 + psf2[0].data/2

        psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy)
        # Adding photon noise
        #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy))
        if np.all(input_noise) == None:
            #noise =  np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000))
            #noise =  self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000))
            noise = np.random.normal(
                loc=psf_poppy,
                scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000))
            #noise = rs.poisson(psf_poppy)
            #print('Estimated Noise', np.mean(noise))
        else:
            noise = input_noise
            #print('Input Noise', np.mean(noise))

        image = psf_poppy + noise

        dict_ = {
            'image': image,
            'noise': noise,
            'wavefront_map': wavefront_map
        }
        #print(np.mean(image),np.mean(noise))

        return dict_
Exemple #18
0
    def create_image_from_opd_file(self, opd=None, input_noise=None):
        # This is the function that creates the image (will probably call the config file) from some zernike coefficients
        # Copy paste the code that creates the image here

        import poppy
        from astropy.io import fits

        pupil_diameter = 6.559  # (in meter) As used in WebbPSF
        pupil_radius = pupil_diameter / 2

        osys = poppy.OpticalSystem()

        transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz'
        #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits'
        #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz'
        hdul = fits.open(opd)
        hdul2 = fits.open(transmission)

        wavefront_map = hdul[0].data

        jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd)
        #jwst_opd = poppy.FITSOpticalElement(transmission=transmission)

        osys.add_pupil(jwst_opd)  # JWST pupil
        osys.add_detector(
            pixelscale=0.063, fov_arcsec=self.fov_arcsec,
            oversample=4)  # image plane coordinates in arcseconds

        psf = osys.calc_psf(4.44e-6)  # wavelength in microns
        psf_poppy = np.array(psf[0].data)
        poppy.display_psf(psf, title='JWST NIRCam test')

        #psf1 = osys.calc_psf(4.44e-6)                            # wavelength in microns
        #psf2 = osys.calc_psf(2.50e-6)                            # wavelength in microns
        #psf_poppy = psf1[0].data/2 + psf2[0].data/2

        psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy)
        # Adding photon noise
        #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy))
        if np.all(input_noise) == None:
            #noise =  np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000))
            #noise =  self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000))
            noise = np.random.normal(
                loc=psf_poppy,
                scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000))
            #noise = rs.poisson(psf_poppy)
            #print('Estimated Noise', np.mean(noise))
        else:
            noise = input_noise
            #print('Input Noise', np.mean(noise))

        image = psf_poppy + noise

        dict_ = {
            'image': image,
            'noise': noise,
            'wavefront_map': wavefront_map
        }
        #print(np.mean(image),np.mean(noise))

        return dict_
Exemple #19
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)
Exemple #20
0
logging.basicConfig(level=logging.DEBUG)

np.seterr(divide='ignore', invalid='ignore')
osys = poppy.OpticalSystem()
osys.add_pupil(poppy.CircularAperture(radius=3))  # pupil radius in meters
osys.add_detector(pixelscale=0.010, fov_arcsec=5.0)

plt.figure(figsize=(16, 12))

ap = poppy.MultiHexagonAperture(
    rings=3, flattoflat=2
)  # 3 rings of 2 m segments yields 14.1 m circumscribed diameter
sec = poppy.SecondaryObscuration(secondary_radius=1.5,
                                 n_supports=4,
                                 support_width=0.1)  # secondary with spiders
atlast = poppy.CompoundAnalyticOptic(
    opticslist=[ap, sec], name='Mock ATLAST')  # combine into one optic

atlast.display(npix=1024, colorbar_orientation='vertical')
plt.savefig('example_atlast_pupil.png', dpi=100)

plt.figure(figsize=(8, 6))

osys = poppy.OpticalSystem()
osys.add_pupil(atlast)
osys.add_detector(pixelscale=0.010, fov_arcsec=2.0)
psf = osys.calc_psf(1e-6)

poppy.display_psf(psf, title="Mock ATLAST PSF")
plt.savefig('example_atlast_psf.png', dpi=100)
Exemple #21
0
def test_ThinLens(display=False):
    """ Test that a +0.5 wave lens creates +0.5 waves of OPD


    """
    pupil_radius = 1

    # let's add < 1 wave here so we don't have to worry about wrapping
    lens = optics.ThinLens(nwaves=0.5,
                           reference_wavelength=1e-6,
                           radius=pupil_radius)
    # n.b. npix is 99 so that there are an integer number of pixels per meter (hence multiple of 3)
    # and there is a central pixel at 0,0 (hence odd npix)
    # Otherwise the strict test against half a wave min max doesn't work
    # because we're missing some (tiny but nonzero) part of the aperture
    wave = poppy_core.Wavefront(npix=99, diam=3.0, wavelength=1e-6)
    wave *= lens

    # Now test the values at some precisely chosen pixels
    y, x = wave.coordinates()
    at_radius = ((x == 1) & (y == 0))
    assert np.allclose(wave.phase[at_radius],
                       np.pi / 2), "Didn't get 1/2 wave OPD at edge of optic"
    assert len(
        at_radius[0]
    ) > 0, "Array indices messed up - need to have a pixel at exactly (1,0)"

    at_radius = ((x == 0) & (y == 1))
    assert np.allclose(wave.phase[at_radius],
                       np.pi / 2), "Didn't get 1/2 wave OPD at edge of optic"
    assert len(
        at_radius[0]
    ) > 0, "Array indices messed up - need to have a pixel at exactly (0,1)"

    at_center = ((x == 0) & (y == 0))
    assert np.allclose(wave.phase[at_center], -np.pi /
                       2), "Didn't get -1/2 wave OPD at center of optic"
    assert len(
        at_radius[0]
    ) > 0, "Array indices messed up - need to have a pixel at exactly (0,0)"

    # TODO test intermediate pixel values between center and edge?
    #   OK - This is now tested in test_sign_conventions.test_lens_wfe_sign

    # regression test to ensure null optical elements don't change ThinLens behavior
    # see https://github.com/mperrin/poppy/issues/14
    osys = poppy_core.OpticalSystem()
    osys.add_pupil(optics.CircularAperture(radius=1))
    for i in range(3):
        osys.add_image()
        osys.add_pupil()

    osys.add_pupil(
        optics.ThinLens(nwaves=0.5,
                        reference_wavelength=1e-6,
                        radius=pupil_radius))
    osys.add_detector(pixelscale=0.01, fov_arcsec=3.0)
    psf = osys.calc_psf(wavelength=1e-6)

    osys2 = poppy_core.OpticalSystem()
    osys2.add_pupil(optics.CircularAperture(radius=1))
    osys2.add_pupil(
        optics.ThinLens(nwaves=0.5,
                        reference_wavelength=1e-6,
                        radius=pupil_radius))
    osys2.add_detector(pixelscale=0.01, fov_arcsec=3.0)
    psf2 = osys2.calc_psf()

    if display:
        import poppy
        poppy.display_psf(psf)
        poppy.display_psf(psf2)

    assert np.allclose(psf[0].data, psf2[0].data), (
        "ThinLens shouldn't be affected by null optical elements! Introducing extra image planes "
        "made the output PSFs differ beyond numerical tolerances.")
Exemple #22
0
import poppy
import numpy as np
import matplotlib.pyplot as plt
import logging
logging.basicConfig(level=logging.DEBUG)

np.seterr(divide='ignore', invalid='ignore')
osys = poppy.OpticalSystem()
osys.add_pupil(poppy.CircularAperture(radius=3))    # pupil radius in meters
osys.add_detector(pixelscale=0.010, fov_arcsec=5.0)  # image plane coordinates in arcseconds

psf = osys.calc_psf(2e-6)                            # wavelength in microns
poppy.display_psf(psf, title='The Airy Function') 

plt.savefig('example_airy.png', dpi=100)