Esempio n. 1
0
def test_TipTiltStage(display=False, verbose=False):
    """ Test tip tilt stage moves the PSF by the requested amount
    """
    ap = poppy.HexagonAperture(flattoflat=0.75 * u.m)
    det = poppy.Detector(pixelscale=0.1 * u.arcsec / u.pix, fov_pixels=128)

    tt = poppy.active_optics.TipTiltStage(ap, include_factor_of_two=False)

    wave = poppy.Wavefront(npix=128, diam=1 * u.m)

    trans = ap.get_transmission(wave)
    assert np.allclose(tt.get_transmission(wave),
                       trans), "Transmission does not match expectations"
    assert np.allclose(tt.get_opd(wave),
                       0), "OPD without tilt does not match expectation"

    for tx, ty in ((0 * u.arcsec, 1 * u.arcsec), (1 * u.arcsec, 0 * u.arcsec),
                   (-0.23 * u.arcsec, 0.65 * u.arcsec)):
        for include_factor_of_two in [True, False]:

            if verbose:
                print(
                    f"Testing {tx}, {ty}, with include_factor_of_two={include_factor_of_two}"
                )

            tt.include_factor_of_two = include_factor_of_two
            tt.set_tip_tilt(tx, ty)

            wave = poppy.Wavefront(npix=64, diam=1 * u.m)
            wave *= ap
            wave *= tt

            if display:
                plt.figure()
                wave.display(what='both')
                plt.suptitle(f"Wavefront with {tx}, {ty}")

            wave.propagate_to(det)

            if display:
                plt.figure()
                wave.display()
                plt.title(f"PSF with {tx}, {ty}")

            cen = poppy.measure_centroid(wave.as_fits(),
                                         boxsize=5,
                                         relativeto='center',
                                         units='arcsec')

            factor = 2 if include_factor_of_two else 1
            assert np.isclose(cen[1] * u.arcsec, tx * factor,
                              atol=1e-4), "X pos not as expected"
            assert np.isclose(
                cen[0] * u.arcsec, ty * factor, atol=1e-4
            ), f"Y pos not as expected: {cen[0]*u.arcsec}, {ty*factor}"
Esempio n. 2
0
def test_wavefront_tilt_sign_and_direction_fresnel(plot=False, npix=128):
    """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction
    Fresnel propagation version

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

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

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

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

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

    wave.propagate_fresnel(focal_length)

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

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

    cen = poppy.measure_centroid(wave.as_fits())
    assert np.allclose(
        cen[0], nominal_cen), "Tilt in X should not displace the PSF in Y"
    assert cen[
        1] < nominal_cen, "WFE tilt increasing to +X should displace the PSF to -X"
    assert np.allclose(
        ((cen[1] - nominal_cen) * u.pixel * wave.pixelscale).to_value(u.m),
        ((tilt_angle * u.arcsec).to_value(u.radian) * focal_length).to_value(
            u.m)), "PSF offset distance did not match expected amount"
Esempio n. 3
0
    def do_test_source_offset(self, iname, theta=0.0):

        nc = webbpsf.Instrument(iname)
        nc.pupilopd=None

        nsteps = 3
        oversample = 2

        shift_req = []
        psfs = []

        for i in range(nsteps+1):
            nc.options['source_offset_r'] = i*0.1
            nc.options['source_offset_theta'] = theta
            nc.options['source_offset_r'] = i*nc.pixelscale*5
            shift_req.append(nc.options['source_offset_r'])
            psfs.append(  nc.calcPSF(nlambda=1, oversample=oversample) )

        poppy.display_PSF(psfs[0])

        cent0 = np.asarray(poppy.measure_centroid(psfs[0]))
        center_pix = (psfs[0][0].data.shape[0]-1)/2.0
        self.assertAlmostEqual(cent0[0], center_pix, 3)
        self.assertAlmostEqual(cent0[1], center_pix, 3)
        _log.info("Center of unshifted image: (%d, %d)" % tuple(cent0))


        for i in range(1, nsteps+1):
            poppy.display_PSF(psfs[i])
            cent = poppy.measure_centroid(psfs[i])
            rx = shift_req[i] * (-np.sin(theta*np.pi/180))
            ry = shift_req[i] * (np.cos(theta*np.pi/180))
            _log.info("   Shift_requested:\t(%10.3f, %10.3f)" % (rx, ry))
            shift = (cent-cent0) * (nc.pixelscale/oversample)
            _log.info("   Shift_achieved: \t(%10.3f, %10.3f)" % (shift[1], shift[0]))
            self.assertAlmostEqual(rx, shift[1], 3)
            self.assertAlmostEqual(ry, shift[0], 3)
Esempio n. 4
0
def test_wavefront_tilt_sign_and_direction(plot=False, npix=128):
    """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction
    Fraunhofer propagation version

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

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

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

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

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

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

    n = wave.shape[0]
    cen = poppy.measure_centroid(wave.as_fits())
    assert np.allclose(cen[0], (n - 1) /
                       2), "Tilt in X should not displace the PSF in Y"
    assert cen[1] < (
        n - 1) / 2, "WFE tilt increasing to +X should displace the PSF to -X"
    assert np.allclose(((cen[1] - (n - 1) / 2) * u.pixel *
                        wave.pixelscale).to_value(u.arcsec),
                       tilt_angle), "PSF offset did not match expected amount"
Esempio n. 5
0
def do_test_source_offset(iname, distance=0.5,  nsteps=1, theta=0.0, tolerance=0.05, monochromatic=None, display=False):
    """ Test source offsets
    Does the star PSF center end up in the desired location?

    The tolerance threshold for success is by default 1/20th of a pixel
    in the SI pixel units. But this can be adjusted by the calling function if needed.

    This is chosen somewhat arbitrarily as pretty good subpixel performance
    for most applications. Trying for greater accuracy would be limited by
    subpixel sampling in the simulations, as well as by the accuracy of the
    centroid measuring function itself.
    """
    _log.info("Calculating shifted image PSFs for "+iname)

    si = webbpsf_core.Instrument(iname)
    si.pupilopd=None

    if iname=='NIRSpec':
        si.image_mask = None # remove default MSA since it overcomplicates this test.

    oversample = 2

    # Calculations
    shift_req = []
    psfs = []

    # unshifted PSF
    #psfs.append(  nc.calc_psf(nlambda=1, oversample=oversample) )
    #shift_req.append(0)

    steps = np.linspace(0, distance, nsteps+1)
    for i, value in enumerate(steps):
        si.options['source_offset_r'] =  steps[i]
        si.options['source_offset_theta'] = theta
        #nc.options['source_offset_r'] = i*nc.pixelscale*5
        shift_req.append(si.options['source_offset_r'])
        psfs.append(  si.calc_psf(nlambda=1, monochromatic=monochromatic, oversample=oversample) )


    # Control case: an unshifted image
    cent0 = np.asarray(poppy.measure_centroid(psfs[0]))
    center_pix = (psfs[0][0].data.shape[0]-1)/2.0
    assert( abs(cent0[0] == center_pix) < 1e-3 )
    assert( abs(cent0[1] == center_pix) < 1e-3 )
    _log.info("Center of unshifted image: ({0:.3f}, {1:.3f}) pixels measured".format(*cent0))
    _log.info(" vs center of the array is ({0}, {0})".format(center_pix))

    if display:
        poppy.display_PSF(psfs[0])

    # Compare to shifted case(s)
    for i in range(1, nsteps+1):

        if display:
            poppy.display_PSF(psfs[i])

        cent = poppy.measure_centroid(psfs[i])
        rx = shift_req[i] * (-np.sin(theta*np.pi/180))
        ry = shift_req[i] * (np.cos(theta*np.pi/180))
        _log.info("   Shift_requested:\t(%10.3f, %10.3f) arcsec" % (rx, ry))
        shift = (cent-cent0) * (si.pixelscale/oversample)
        _log.info("   Shift_achieved: \t(%10.3f, %10.3f) arcsec" % (shift[1], shift[0]))

        deltax =  abs(rx -  shift[1])
        deltay =  abs(ry -  shift[0])
        _log.info("   X offset:\t{0:.3f}\t\tTolerance:\t{1:.3f}".format(deltax, (si.pixelscale*tolerance)))
        assert( deltax  <  (si.pixelscale*tolerance) )
        _log.info("   Y offset:\t{0:.3f}\t\tTolerance:\t{1:.3f}".format(deltay, (si.pixelscale*tolerance)))
        assert( deltay  <  (si.pixelscale*tolerance) )
Esempio n. 6
0
def validate_vs_jwpsf_nircam():
    """ Compare results from WebbPSF with earlier simulations produced with JWPSF
    """

    models = [ ('NIRCam','F200W', 'f200w_perfect_offset', '/Users/mperrin/software/jwpsf_v3.0/data/NIRCam/OPD/perfect_opd.fits', 0.034,True),
            ('NIRCam','F200W', 'f200w_perfect', '/Users/mperrin/software/jwpsf_v3.0/data/NIRCam/OPD/perfect_opd.fits', 0.034,False),
            ('NIRCam','F200W', 'f200w', '/Users/mperrin/software/jwpsf_v3.0/data/NIRCam/OPD/nircam_obs_w_rsrv1.fits', 0.034,True),
                ('MIRI','F1000W', 'f1000w', '/Users/mperrin/software/jwpsf_v3.0/data/MIRI/OPD/MIRI_OPDisim1.fits', 0.11,True)]


    fig = P.figure(1, figsize=(13,8.5), dpi=80)
    oversamp=4
    for params in models:

        nc = webbpsf_core.Instrument(params[0])
        nc.filter = params[1]
        nc.pupilopd = params[3] #'/Users/mperrin/software/jwpsf_v3.0/data/NIRCam/OPD/nircam_obs_w_rsrv1.fits'
        nc.pixelscale = params[4] #0.034 # this is wrong, but compute this way to match JWPSF exactly
        if params[5]:
            # offset by half a pixel to match the JWPSF convention
            nc.options['source_offset_r'] = params[4]/2 * N.sqrt(2)/oversamp  # offset half a pixel each in X and Y
            nc.options['source_offset_theta'] = -45


        jw_fn = 'jwpsf_%s_%s.fits' % (params[0].lower(), params[2].lower())
        my_fn = 'test_vs_' + jw_fn

        if not os.path.exists( my_fn):
            my_psf = nc.calcPSF(my_fn, oversample=oversamp, fov_pixels=512./oversamp)
        else:
            my_psf = fits.open(my_fn)

        jw_psf = fits.open(jw_fn)
        jw_psf[0].header.update('PIXELSCL', jw_psf[0].header['CDELT1']*3600)


        P.clf()
        #P.subplots_adjust(top=0.95, bottom=0.05, left=0.01, right=0.99)
        P.subplot(231)
        titlestr = "%s %s, \n"%  (params[0], params[2])
        poppy.display_PSF(my_psf, title=titlestr+"computed with WebbPSF" , colorbar=False)
        P.subplot(232)
        poppy.display_PSF(jw_psf, title=titlestr+"computed with JWPSF" , colorbar=False)
        P.subplot(233)
        poppy.display_PSF_difference(my_psf,jw_psf, title=titlestr+'Difference Image', colorbar=False)

        imagecrop = 30*params[4]

        P.subplot(234)
        poppy.display_PSF(my_psf, title=titlestr+"computed with WebbPSF", colorbar=False, imagecrop=imagecrop)
        centroid = poppy.measure_centroid(my_psf)
        P.gca().set_xlabel("centroid = (%.3f,%.3f)" % centroid)

        P.subplot(235)
        poppy.display_PSF(jw_psf, title=titlestr+"computed with JWPSF", colorbar=False, imagecrop=imagecrop)
        centroid = poppy.measure_centroid(jw_psf)
        P.gca().set_xlabel("centroid = (%.3f,%.3f)" % centroid)

        P.subplot(236)
        poppy.display_PSF_difference(my_psf,jw_psf, title='Difference Image', colorbar=False, imagecrop=imagecrop)

        P.savefig("results_vs_jwpsf_%s_%s.pdf" % (params[0], params[2]))
Esempio n. 7
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"
            )