Exemple #1
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))}"
        )
Exemple #2
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, or rotating the wavefront
    in the opposite 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)

        if display: plt.figure()
        psf1 = osys.calc_psf(display=display)
        if display: plt.title("Optic rotated {} deg".format(angle))

        osys = poppy.OpticalSystem(npix=npix)
        osys.add_pupil(poppy.optics.ParityTestAperture())
        osys.add_rotation(angle=-angle)  # note, opposite sign here.
        osys.add_detector(fov_pixels=128, pixelscale=0.01)
        if display: plt.figure()
        psf2 = osys.calc_psf(display=display)
        if display: plt.title("Wavefront rotated {} deg".format(angle))

        assert np.allclose(psf1[0].data, psf2[0].data,
                           atol=atol), ("PSFs did not agree "
                                        "within the requested tolerance")
Exemple #3
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)
Exemple #4
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])
Exemple #5
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()
Exemple #6
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")
Exemple #7
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")
Exemple #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
Exemple #9
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")
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")
    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
    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 #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"
Exemple #14
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')
Exemple #15
0
def make_image_corners_only(poppy_optical_system,npix,wavs,subimage_pix=256):
    """ Make an image of the sidelobes only. This is done by simulating 4 detector offsets.
    Unfortunately it didn't speed up the calculation significantly compared to doing the whole image.
    """

    # Get the TolimanAperture object from the optical system object
    for p in poppy_optical_system.planes:
        if isinstance(p,TolimanAperture):
            aperture = p
        elif isinstance(p,poppy.Detector):
            detector = p
            
    pixelscale = detector.pixelscale.value
    
    image = np.zeros((npix,npix))
    
    # Where are the sidelobes?
    wmin = np.min(wavs)
    wmax = np.max(wavs)
    ideal_offset_pix = diffraction_spot_offset((wmax+wmin)/2,aperture,pixelscale)
    
    poppy_optical_system = poppy.OpticalSystem()
    poppy_optical_system.add_pupil(aperture)
    poppy_optical_system.add_detector(pixelscale=pixelscale, fov_arcsec=subimage_pix*pixelscale/2*u.arcsec)
    
    # Loop through the 4 quadrants and calculate them, then add it to the full image
    for offset_angle in [45,135,225,315]:
        for j, wavelength in enumerate(wavs):
            
            ideal_offset_pix = diffraction_spot_offset(wavelength,aperture,pixelscale)
        
            # Snap it to a grid
            ideal_offset_x_pix = ideal_offset_pix*np.sign(np.sin(offset_angle*np.pi/180.))
            ideal_offset_y_pix = ideal_offset_pix*np.sign(np.cos(offset_angle*np.pi/180.))

            offset_x_pix = np.int(np.round(ideal_offset_x_pix))
            offset_y_pix = np.int(np.round(ideal_offset_y_pix))
            actual_offset_angle = np.arctan2(offset_y_pix,offset_x_pix)*180./np.pi # should be == offset_angle...
            actual_offset_r_pix = np.sqrt(offset_x_pix**2+offset_y_pix**2)

            poppy_optical_system.source_offset_theta = -offset_angle
            poppy_optical_system.source_offset_r =  0.5*actual_offset_r_pix*pixelscale#*u.arcsec

            corner_im = poppy_optical_system.calc_psf(wavelength)[0].data
            
            # Add it to the full image
            x1 = npix//2-offset_x_pix-subimage_pix//2
            x2 = npix//2-offset_x_pix+subimage_pix//2
            y1 = npix//2-offset_y_pix-subimage_pix//2
            y2 = npix//2-offset_y_pix+subimage_pix//2
            image[y1:y2,x1:x2] += corner_im
            
    return image
Exemple #16
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))
Exemple #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!')
Exemple #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!')
Exemple #19
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 #20
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 #21
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().'
Exemple #22
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")
Exemple #23
0
def test_segment_tilt_sign_and_direction(display=False):
    hexdm = poppy.HexSegmentedDeformableMirror(flattoflat=0.5 * u.m, rings=1)

    osys2 = poppy.OpticalSystem(pupil_diameter=2 * u.m, npix=128, oversample=1)
    osys2.add_pupil(
        poppy.MultiHexagonAperture(flattoflat=0.5 * u.m, rings=1, center=True))
    osys2.add_pupil(hexdm)
    osys2.add_detector(0.10, fov_arcsec=10)

    psf_ref = osys2.calc_psf()  # reference, with no tilts

    hexdm.set_actuator(0, 0.2 * u.micron, 0, 0)  # piston
    hexdm.set_actuator(1, 0, 2 * u.arcsec, 0)  # tip
    hexdm.set_actuator(2, 0, 0, 1 * u.arcsec)  # tilt

    if display:
        import matplotlib.pyplot as plt
        hexdm.display(what='opd',
                      colorbar_orientation='vertical',
                      opd_vmax=2e-6)
        plt.figure(figsize=(14, 5))
        plt.suptitle("Segment tilt sign test (Fraunhofer propagation)",
                     fontweight='bold')

    psf2 = osys2.calc_psf(display_intermediates=display)

    diff_psf = psf2[0].data - psf_ref[0].data

    if display:
        plt.figure()
        poppy.display_psf_difference(psf2, psf_ref)

    assert brighter_left_half(
        diff_psf
    ), 'Tilting a segment with +X tilt WFE should move its PSF to -X'
    assert not brighter_top_half(
        diff_psf
    ), 'Tilting a segment with +Y tilt WFE should move its PSF to -Y'
Exemple #24
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 #25
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 #26
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"
    )
Exemple #27
0
def test_measure_FWHM(display=False, verbose=False):
    """ Test the utils.measure_FWHM function

    Current implementation can be off by a
    couple percent for small FWHMs that are only
    marginally well sampled by the array pixels, so
    the allowed tolerance for measured_fwhm = input_fwhm
    is that it's allowed to be off by a couple percent.

    """

    # Test the basic output on simple Gaussian arrays
    desired = (3, 4.5, 5, 8, 12)
    tolerance = 0.01

    for des in desired:

        desired_fwhm = des  #4.0 # pixels
        pxscl = 0.010

        center = (24.5, 26.25)
        ar = makeGaussian(50, fwhm=desired_fwhm, center=center)

        testfits = fits.HDUList(fits.PrimaryHDU(ar))
        testfits[0].header['PIXELSCL'] = pxscl

        meas_fwhm = utils.measure_fwhm(testfits, center=center)
        if verbose:
            print("Measured FWHM: {0:.4f} arcsec, {1:.4f} pixels ".format(
                meas_fwhm, meas_fwhm / pxscl))

        reldiff = np.abs((meas_fwhm / pxscl) - desired_fwhm) / desired_fwhm
        result = "Measured: {3:.4f} pixels; Desired: {0:.4f} pixels. Relative difference: {1:.4f}    Tolerance: {2:.4f}".format(
            desired_fwhm, reldiff, tolerance, meas_fwhm / pxscl)
        if verbose:
            print(result)
        assert reldiff < tolerance, result

    # Test on Poppy outputs too
    # We test both well sampled and barely sampled cases.
    # In this test case the FWHM is 0.206265 arcsec, so pixel scale up to 0.2 arcsec.
    pixscales = [0.01, 0.1, 0.2]
    # We allow slightly worse accurance for less well sampled data
    tolerances = [0.01, 0.015, 0.04]

    for pixscale, tolerance in zip(pixscales, tolerances):

        import astropy.units as u
        o = poppy.OpticalSystem()
        o.add_pupil(poppy.CircularAperture(radius=0.5 * u.m))
        o.add_detector(pixscale, fov_pixels=128)
        psf = o.calc_psf(wavelength=1 * u.micron)

        meas_fwhm = poppy.measure_fwhm(psf)
        expected_fwhm = ((1 * u.micron / (1 * u.m)).decompose().value *
                         u.radian).to(u.arcsec).value

        reldiff = np.abs((meas_fwhm - expected_fwhm) / expected_fwhm)

        result = "Measured: {3:.4f} arcsec; Desired: {0:.4f} arcsec. Relative difference: {1:.4f}    Tolerance: {2:.4f}".format(
            expected_fwhm, reldiff, tolerance, meas_fwhm)

        assert reldiff < tolerance, result
Exemple #28
0
    def _get_optical_system(self,
                            fft_oversample=2,
                            detector_oversample=None,
                            fov_arcsec=2.8,
                            fov_pixels=None,
                            options=dict()):
        """ Return an OpticalSystem instance corresponding to the instrument as currently configured.

        When creating such an OpticalSystem, you must specify the parameters needed to define the
        desired sampling, specifically the oversampling and field of view.


        Parameters
        ----------

        fft_oversample : int
            Oversampling factor for intermediate plane calculations. Default is 2
        detector_oversample: int, optional
            By default the detector oversampling is equal to the intermediate calculation oversampling.
            If you wish to use a different value for the detector, set this parameter.
            Note that if you just want images at detector pixel resolution you will achieve higher fidelity
            by still using some oversampling (i.e. *not* setting `oversample_detector=1`) and instead rebinning
            down the oversampled data.
        fov_pixels : float
            Field of view in pixels. Overrides fov_arcsec if both set.
        fov_arcsec : float
            Field of view, in arcseconds. Default is 2
        options : dict
            Other arbitrary options for optical system creation


        Returns
        -------
        osys : poppy.OpticalSystem
            an optical system instance representing the desired configuration.

        """

        if detector_oversample is None: detector_oversample = fft_oversample

        #poppy_core._log.debug("Oversample: %d  %d " % (fft_oversample, detector_oversample))
        optsys = poppy.OpticalSystem(name=self.name, oversample=fft_oversample)
        if 'source_offset_r' in options.keys():
            optsys.source_offset_r = options['source_offset_r']
        if 'source_offset_theta' in options.keys():
            optsys.source_offset_theta = options['source_offset_theta']

        optsys.npix = self.npix

        #---- set pupil intensity
        pupil_optic = GeminiPrimary(undersized=self._undersized_secondary)
        #if self._undersized_secondary:
        #pupil_optic.obscuration_diameter = 1.02375 # SM outer diameter (vs inner hole projected diameter)

        #---- set pupil OPD
        if isinstance(self.pupilopd, str):  # simple filename
            full_opd_path = self.pupilopd if os.path.exists(
                self.pupilopd) else os.path.join(self._datapath, "OPD",
                                                 self.pupilopd)
        elif hasattr(self.pupilopd, '__getitem__') and isinstance(
                self.pupilopd[0], basestring):  # tuple with filename and slice
            full_opd_path = (self.pupilopd[0] if os.path.exists(
                self.pupilopd[0]) else os.path.join(
                    self._datapath, "OPD", self.pupilopd[0]), self.pupilopd[1])
        elif isinstance(self.pupilopd,
                        fits.HDUList):  # OPD supplied as FITS HDUList object
            full_opd_path = self.pupilopd  # not a path per se but this works correctly to pass it to poppy
        elif self.pupilopd is None:
            full_opd_path = None
        else:
            raise TypeError(
                "Not sure what to do with a pupilopd of that type:" +
                str(type(self.pupilopd)))

        #---- apply pupil intensity and OPD to the optical model
        optsys.add_pupil(name='Gemini Primary',
                         optic=pupil_optic,
                         opd=full_opd_path,
                         opdunits='micron',
                         rotation=self._rotation)

        if self.dms:
            optsys.add_pupil(optic=self.woofer)
            optsys.add_pupil(optic=self.tweeter)

        # GPI Apodizer
        apod = GPI_Apodizer(name=self.apodizer, satspots=self.satspots)
        optsys.add_pupil(optic=apod)

        if self._display_before:
            optsys.add_image(optic=poppy.ScalarTransmission(name='Before FPM',
                                                            transmission=1))

        # GPI FPM
        fpm = GPI_FPM(name=self.occulter)
        optsys.add_image(optic=fpm)

        if self._display_before:
            optsys.add_pupil(optic=poppy.ScalarTransmission(name='Before Lyot',
                                                            transmission=1))

        # GPI Lyot Mask
        lyot = GPI_LyotMask(name=self.lyotmask, tabs=self.lyot_tabs)
        optsys.add_pupil(optic=lyot)

        #--- add the detector element.
        if fov_pixels is None:
            fov_pixels = np.round(fov_arcsec / self.pixelscale)
            if 'parity' in self.options.keys():
                if self.options['parity'].lower() == 'odd' and np.remainder(
                        fov_pixels, 2) == 0:
                    fov_pixels += 1
                if self.options['parity'].lower() == 'even' and np.remainder(
                        fov_pixels, 2) == 1:
                    fov_pixels += 1

        optsys.add_detector(self.pixelscale,
                            fov_pixels=fov_pixels,
                            oversample=detector_oversample,
                            name=self.name + " lenslet array")

        return optsys
Exemple #29
0
def test_source_offsets_in_OpticalSystem(npix=128, fov_size=1, verbose=False):
    """Test source offsets within the field move in the expected
    directions and by the expected amounts

    The source offset positions are specified in the *output* detector coordinate frame,
    (i.e. for where the PSF should appear in the output image), but we create the
    wavefront in the entrance pupil coordinate frame. These may be different if
    there are coordinate transforms for axes flips or rotations. Therefore test several cases
    and ensure the output PSF appears in the expected location in each case.


    Parameters:
    ----------
    npix : int
        number of pixels
    fov_size :
        fov size in arcsec (pretty much arbitrary)
    """
    if npix < 110:
        raise ValueError(
            "npix < 110 results in too few pixels for fwcentroid to work properly."
        )

    pixscale = fov_size / npix
    center_coords = np.asarray((npix - 1, npix - 1)) / 2

    ref_psf1 = None  # below we will save and compare PSFs with transforms to one without.

    for transform in ['no', 'inversion', 'rotation', 'both']:

        osys = poppy.OpticalSystem(oversample=1, npix=npix)
        osys.add_pupil(poppy.CircularAperture(radius=1.0))
        if transform == 'inversion' or transform == 'both':
            if verbose:
                print("ADD INVERSION")
            osys.add_inversion(axis='y')
        if transform == 'rotation' or transform == 'both':
            if verbose:
                print("ADD ROTATION")
            osys.add_rotation(angle=12.5)
        osys.add_detector(pixelscale=pixscale, fov_pixels=npix)

        # a PSF with no offset should be centered
        psf0 = osys.calc_psf()
        cen = poppy.measure_centroid(psf0)
        assert np.allclose(
            cen, center_coords), "PSF with no source offset should be centered"
        if verbose:
            print(
                f"PSF with no offset OK for system with {transform} transform.\n"
            )

        # Compute a PSF with the source offset towards PA=0 (+Y), still within the FOV
        osys.source_offset_r = 0.3 * fov_size

        # Shift to PA=0 should move in +Y
        osys.source_offset_theta = 0
        psf1 = osys.calc_psf()
        cen = poppy.measure_centroid(psf1)
        assert np.allclose(
            (cen[0] - center_coords[0]) * pixscale,
            osys.source_offset_r,
            rtol=0.1), "Measured centroid in Y did not match expected offset"
        assert np.allclose(
            cen[1], center_coords[1], rtol=0.1
        ), "Measured centroid in X should not shift for this test case"
        if verbose:
            print(
                f"PSF with +Y offset OK for system with {transform} transform.\n"
            )

        if ref_psf1 is None:
            ref_psf1 = psf1
        else:
            assert np.allclose(
                ref_psf1[0].data, psf1[0].data, atol=1e-4
            ), "PSF is inconsistent with the system without any transforms"

        # Shift to PA=90 should move in -X
        osys.source_offset_theta = 90
        psf2 = osys.calc_psf()
        cen = poppy.measure_centroid(psf2)
        assert np.allclose(
            (cen[1] - center_coords[1]) * pixscale,
            -osys.source_offset_r,
            rtol=0.1), "Measured centroid in X did not match expected offset"
        assert np.allclose(
            cen[0], center_coords[0], rtol=0.1
        ), "Measured centroid in Y should not shift for this test case"

        if verbose:
            print(
                f"PSF with -X offset OK for system with {transform} transform.\n"
            )
Exemple #30
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_