예제 #1
0
def test_r0_weights():
    """Check that r0_weights functions as expected."""
    r0_500 = 0.2

    # Check that reassembled net r0_500 matches input
    atm = galsim.Atmosphere(screen_size=10.0,
                            altitude=[0, 1, 2, 3],
                            r0_500=r0_500)
    r0s = [screen.r0_500 for screen in atm]
    np.testing.assert_almost_equal(
        np.sum([r0**(-5. / 3) for r0 in r0s])**(-3. / 5), r0_500)
    np.testing.assert_almost_equal(atm.r0_500_effective, r0_500)

    # Check that old manual calculation matches automatic calculation inside Atmosphere()
    weights = [1, 2, 3, 4]
    normalized_weights = np.array(weights, dtype=float) / np.sum(weights)
    r0s_ref = [r0_500 * w**(-3. / 5) for w in normalized_weights]
    atm = galsim.Atmosphere(screen_size=10.0,
                            altitude=[0, 1, 2, 3],
                            r0_500=r0_500,
                            r0_weights=weights)
    r0s_test = [screen.r0_500 for screen in atm]
    np.testing.assert_almost_equal(r0s_test, r0s_ref)
    np.testing.assert_almost_equal(
        np.sum([r0**(-5. / 3) for r0 in r0s_test])**(-3. / 5), r0_500)
    np.testing.assert_almost_equal(atm.r0_500_effective, r0_500)
예제 #2
0
def test_phase_psf_reset():
    """Test that phase screen reset() method correctly resets the screen to t=0."""
    rng = galsim.BaseDeviate(1234)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng)
    aper = galsim.Aperture(diam=1.0, lam=500.0)
    wf1 = atm.wavefront(aper)
    atm.advance()
    wf2 = atm.wavefront(aper)
    # Verify that atmosphere did advance
    assert not np.all(wf1 == wf2)

    # Now verify that reset brings back original atmosphere
    atm.reset()
    wf3 = atm.wavefront(aper)
    np.testing.assert_array_equal(wf1, wf3, "Phase screen didn't reset")

    # Now check with boiling, but no wind.
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, alpha=0.997, rng=rng)
    wf1 = atm.wavefront(aper)
    atm.advance()
    wf2 = atm.wavefront(aper)
    # Verify that atmosphere did advance
    assert not np.all(wf1 == wf2)

    # Now verify that reset brings back original atmosphere
    atm.reset()
    wf3 = atm.wavefront(aper)
    np.testing.assert_array_equal(wf1, wf3, "Phase screen didn't reset")
예제 #3
0
def test_instantiation_check():
    """Check that after instantiating, drawing with the other method will emit a warning.
    """
    atm1 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2)
    psf1 = atm1.makePSF(lam=500.0, diam=1.0)
    psf1.drawImage()
    with assert_warns(galsim.GalSimWarning):
        psf1.drawImage(method='phot', n_photons=10)

    atm2 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2)
    psf2 = atm2.makePSF(lam=500.0, diam=1.0)  # exptime = 0, so reasonable to draw w/ FFT
    psf2.drawImage(method='phot', n_photons=10)
    with assert_warns(galsim.GalSimWarning):
        psf2.drawImage()
예제 #4
0
def test_phase_psf_batch():
    """Test that PSFs generated serially match those generated in batch."""
    import time
    NPSFs = 10
    exptime = 0.06
    rng = galsim.BaseDeviate(1234)
    atm = galsim.Atmosphere(screen_size=10.0, altitude=10.0, alpha=0.997, rng=rng)
    theta = [(i*galsim.arcsec, i*galsim.arcsec) for i in range(NPSFs)]

    kwargs = dict(lam=1000.0, exptime=exptime, diam=1.0)

    t1 = time.time()
    psfs = atm.makePSF(theta=theta, **kwargs)
    print('time for {0} PSFs in batch: {1:.2f} s'.format(NPSFs, time.time() - t1))

    t2 = time.time()
    more_psfs = []
    for th in theta:
        atm.reset()
        more_psfs.append(atm.makePSF(theta=th, **kwargs))
    print('time for {0} PSFs in serial: {1:.2f} s'.format(NPSFs, time.time() - t2))

    for psf1, psf2 in zip(psfs, more_psfs):
        np.testing.assert_array_equal(
            psf1.img, psf2.img,
            "Individually generated AtmosphericPSF differs from AtmosphericPSF generated in batch")
예제 #5
0
def test_phase_psf_batch():
    """Test that PSFs generated and drawn serially match those generated and drawn in batch."""
    import time
    NPSFs = 10
    exptime = 0.3
    rng = galsim.BaseDeviate(1234)
    atm = galsim.Atmosphere(screen_size=10.0, altitude=10.0, alpha=0.997, time_step=0.01, rng=rng)
    theta = [(i*galsim.arcsec, i*galsim.arcsec) for i in range(NPSFs)]

    kwargs = dict(lam=1000.0, exptime=exptime, diam=1.0)

    t1 = time.time()
    psfs = [atm.makePSF(theta=th, **kwargs) for th in theta]
    imgs = [psf.drawImage() for psf in psfs]
    print('time for {0} PSFs in batch: {1:.2f} s'.format(NPSFs, time.time() - t1))

    t2 = time.time()
    more_imgs = []
    for th in theta:
        psf = atm.makePSF(theta=th, **kwargs)
        more_imgs.append(psf.drawImage())
    print('time for {0} PSFs in serial: {1:.2f} s'.format(NPSFs, time.time() - t2))

    for img1, img2 in zip(imgs, more_imgs):
        np.testing.assert_array_equal(
            img1, img2,
            "Individually generated AtmosphericPSF differs from AtmosphericPSF generated in batch")
예제 #6
0
def test_scale_unit():
    """Test that `scale_unit` keyword correctly sets the units for PhaseScreenPSF."""
    aper = galsim.Aperture(diam=1.0)
    rng = galsim.BaseDeviate(1234)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng)
    psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec)
    im1 = psf.drawImage(nx=32, ny=32, scale=0.1, method='no_pixel')
    psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit='arcmin')
    im2 = psf2.drawImage(nx=32, ny=32, scale=0.1/60.0, method='no_pixel')
    np.testing.assert_almost_equal(
            im1.array, im2.array, 8,
            'PhaseScreenPSF inconsistent use of scale_unit')

    opt_psf1 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit=galsim.arcsec)
    opt_psf2 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit='arcsec')
    assert opt_psf1 == opt_psf2, "scale unit did not parse as string"

    assert_raises(ValueError, galsim.OpticalPSF, lam=500.0, diam=1.0, scale_unit='invalid')
    assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, scale_unit='invalid')
    # Check a few other construction errors now too.
    assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, scale_unit='arcmin')
    assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=34.*galsim.degrees)
    assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=(34, 5))
    assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, exptime=-1)
예제 #7
0
def test_scale_unit():
    """Test that `scale_unit` keyword correctly sets the units for PhaseScreenPSF."""
    aper = galsim.Aperture(diam=1.0)
    rng = galsim.BaseDeviate(1234)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0,
                            altitude=10.0,
                            speed=0.1,
                            alpha=1.0,
                            rng=rng)
    psf = galsim.PhaseScreenPSF(atm,
                                500.0,
                                aper=aper,
                                scale_unit=galsim.arcsec)
    im1 = psf.drawImage(nx=32, ny=32, scale=0.1, method='no_pixel')
    psf2 = galsim.PhaseScreenPSF(atm,
                                 500.0,
                                 aper=aper,
                                 scale_unit=galsim.arcmin)
    im2 = psf2.drawImage(nx=32, ny=32, scale=0.1 / 60.0, method='no_pixel')
    np.testing.assert_almost_equal(
        im1.array, im2.array, 8,
        'PhaseScreenPSF inconsistent use of scale_unit')

    opt_psf1 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit=galsim.arcsec)
    opt_psf2 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit='arcsec')
    assert opt_psf1 == opt_psf2, "scale unit did not parse as string"
예제 #8
0
def test_stepk_maxk():
    """Test options to specify (or not) stepk and maxk.
    """
    aper = galsim.Aperture(diam=1.0)
    rng = galsim.BaseDeviate(123456)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng)
    psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec)
    stepk = psf.stepK()
    maxk = psf.maxK()

    psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec,
                                 _force_stepk=stepk/1.5, _force_maxk=maxk*2.0)
    np.testing.assert_almost_equal(
            psf2.stepK(), stepk/1.5, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for stepK")
    np.testing.assert_almost_equal(
            psf2.maxK(), maxk*2.0, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for maxK")
    do_pickle(psf)
    do_pickle(psf2)

    # Try out non-geometric-shooting
    psf3 = atm.makePSF(lam=500.0, aper=aper, geometric_shooting=False)
    img = galsim.Image(32, 32, scale=0.2)
    do_shoot(psf3, img, "PhaseScreenPSF")
    # Also make sure a few other methods at least run
    psf3.centroid()
    psf3.maxSB()
예제 #9
0
    def _build_atm(self):
        """Build an atmosphere following Jee and Tyson, galsim and my gut."""
        max_wind_speed = 20  # m/s

        max_screen_size = (self.exposure_time * max_wind_speed)  # m

        altitude = [0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
        max_screen_size = np.maximum(
            max_screen_size * 1.5,  # factor of 1.5 for periodic FFTs
            (max_screen_size +
             2 * np.array(altitude) * 1e3 * self.field_of_view / 180 * np.pi
             )  # 2 fields of view
        )

        # draw a new effective r0 w/ 1% deviation around th fiducial value
        effr0 = self.rng.lognormal(np.log(self.effective_r0_500), 0.01)

        # draw weights with similar variations
        fid_weights = np.array([0.652, 0.172, 0.055, 0.025, 0.074, 0.022])
        weights = np.exp(
            self.rng.normal(size=len(fid_weights)) * 0.01 +
            np.log(fid_weights))
        weights /= np.sum(weights)

        # get effective screen r0's and the pixel scales
        screen_r0_500 = effr0 * np.power(weights, -3.0 / 5.0)
        screen_scales = np.clip(0.25 * screen_r0_500, 0.1, np.inf)

        # compute a good FFT size, clipping at max of 8192
        nominal_npix = np.clip(
            np.ceil(max_screen_size / screen_scales).astype(int), 0, 8192)
        npix = get_good_fft_sizes(nominal_npix)

        # for screens that need too many pixels,
        # we can either go back and make the overall size smaller
        # or make the pixels bigger
        # here I am going to make the size smaller since we want the proper
        # power but can tolerate some wrapping of the FFTs on average
        screen_sizes = screen_scales * npix

        LOGGER.debug('screen # of pixels: %s', npix)
        LOGGER.debug('screen pixel sizes: %s', screen_scales)
        LOGGER.debug('pixel size / r0_500: %s', screen_scales / screen_r0_500)
        LOGGER.debug('fraction of ideal screen size: %s',
                     screen_sizes / max_screen_size)

        speed = self.rng.uniform(0, max_wind_speed, size=6)  # m/s
        direction = [
            self.rng.uniform(0, 360) * galsim.degrees for i in range(6)
        ]

        self._screens = galsim.Atmosphere(r0_500=screen_r0_500,
                                          screen_size=screen_sizes,
                                          altitude=altitude,
                                          L0=25.0,
                                          speed=speed,
                                          direction=direction,
                                          screen_scale=screen_scales,
                                          rng=self.base_deviate)
예제 #10
0
def test_phase_psf():
    atm = galsim.Atmosphere(screen_size=10.0,
                            altitude=0,
                            r0_500=0.15,
                            suppress_warning=True)
    psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0)
    check_dep(galsim.PhaseScreenPSF.__getattribute__, psf, "img")
    check_dep(galsim.PhaseScreenPSF.__getattribute__, psf, "finalized")
예제 #11
0
def test_speedup():
    """Make sure that photon-shooting a PhaseScreenPSF with geometric approximation yields
    significant speedup.
    """
    import time
    atm = galsim.Atmosphere(screen_size=10.0, altitude=[0,1,2,3], r0_500=0.2)
    # Should be ~seconds if _prepareDraw() gets executed, ~0.01s otherwise.
    psf = atm.makePSF(lam=500.0, diam=1.0, exptime=15.0, time_step=0.025)
    t0 = time.time()
    psf.drawImage(method='phot', n_photons=1e3)
    t1 = time.time()
    assert (t1-t0) < 0.1, "Photon-shooting took too long ({0} s).".format(t1-t0)
예제 #12
0
def test_sk_phase_psf():
    """Test that analytic second kick profile matches what can be obtained from PhaseScreenPSF with
    an appropriate truncated power spectrum.
    """
    # import matplotlib.pyplot as plt

    lam = 500.0
    r0 = 0.2
    diam = 4.0
    obscuration = 0.5

    rng = galsim.UniformDeviate(1234567890)
    weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    speed = [rng() * 20 for _ in range(6)]
    direction = [rng() * 360 * galsim.degrees for _ in range(6)]
    aper = galsim.Aperture(4.0, obscuration=obscuration, pad_factor=0.5)
    kcrits = [1, 3, 10] if __name__ == '__main__' else [1]
    for kcrit in kcrits:
        # Technically, we should probably use a smaller screen_scale here, but that runs really
        # slowly.  The below seems to work well enough for the tested kcrits.
        atm = galsim.Atmosphere(r0_500=r0,
                                r0_weights=weights,
                                rng=rng,
                                speed=speed,
                                direction=direction,
                                screen_size=102.4,
                                screen_scale=0.05,
                                suppress_warning=True)
        atm.instantiate(kmin=kcrit)
        print('instantiated atm')
        psf = galsim.PhaseScreenPSF(atm,
                                    lam=lam,
                                    exptime=10,
                                    aper=aper,
                                    time_step=0.1)
        print('made psf')
        phaseImg = psf.drawImage(nx=64, ny=64, scale=0.02)
        sk = galsim.SecondKick(lam=lam,
                               r0=r0,
                               diam=diam,
                               obscuration=obscuration,
                               kcrit=kcrit)
        print('made sk')
        skImg = sk.drawImage(nx=64, ny=64, scale=0.02)
        print('made skimg')
        phaseMom = galsim.hsm.FindAdaptiveMom(phaseImg)
        skMom = galsim.hsm.FindAdaptiveMom(skImg)

        print('moments: ', phaseMom.moments_sigma, skMom.moments_sigma)
        np.testing.assert_allclose(phaseMom.moments_sigma,
                                   skMom.moments_sigma,
                                   rtol=2e-2)
예제 #13
0
def test_gc():
    """Make sure that pending psfs don't leak memory.
    """
    import gc
    atm = galsim.Atmosphere(screen_size=10.0, altitude=0, r0_500=0.15, suppress_warning=True)

    # First check that no PhaseScreenPSFs are known to the garbage collector
    assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])

    # Make a PhaseScreenPSF and check that it's known to the garbage collector
    psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0)
    assert any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])

    # If we delete it, it disappears everywhere
    del psf
    gc.collect()
    assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])

    # If we draw one using photon-shooting, it still exists in _pending
    psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0)
    psf.drawImage(nx=10, ny=10, scale=0.2, method='phot', n_photons=100)
    assert psf in [p[1]() for p in atm._pending]

    # If we draw even one of many using fft, _pending gets completely emptied
    psf2 = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0)
    psf.drawImage(nx=10, ny=10, scale=0.2)
    assert atm._pending == []

    # And if then deleted, they again don't exist anywhere
    del psf, psf2
    gc.collect()
    assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])

    # A corner case revealed in coverage tests:
    # Make sure that everything still works if some, but not all static pending PSFs are deleted.
    screen = galsim.OpticalScreen(diam=1.1)
    phaseScreenList = galsim.PhaseScreenList(screen)
    psf1 = phaseScreenList.makePSF(lam=1000.0, diam=1.1)
    psf2 = phaseScreenList.makePSF(lam=1000.0, diam=1.1)
    psf3 = phaseScreenList.makePSF(lam=1000.0, diam=1.1)
    del psf2
    psf1.drawImage(nx=10, ny=10, scale=0.2)
    del psf1, psf3
    assert phaseScreenList._pending == []
    gc.collect()
    assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])
예제 #14
0
def test_stepk_maxk():
    """Test options to specify (or not) stepk and maxk.
    """
    aper = galsim.Aperture(diam=1.0)
    rng = galsim.BaseDeviate(1234)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng)
    psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec)
    stepk = psf.stepK()
    maxk = psf.maxK()

    psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec,
                                 _force_stepk=stepk/1.5, _force_maxk=maxk*2.0)
    np.testing.assert_almost_equal(
            psf2.stepK(), stepk/1.5, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for stepK")
    np.testing.assert_almost_equal(
            psf2.maxK(), maxk*2.0, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for maxK")
    do_pickle(psf)
    do_pickle(psf2)
예제 #15
0
def make_movie(args):
    """Actually make the movie of the atmosphere given command line arguments stored in `args`.
    """

    # Initiate some GalSim random number generators.
    rng = galsim.BaseDeviate(args.seed)
    u = galsim.UniformDeviate(rng)

    # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series
    # of 2D turbulent screens.  The galsim.Atmosphere() helper function is useful for constructing
    # this screen list.

    # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower
    # layers consistent with direct measurements.  The specific values we use are from SCIDAR
    # measurements on Cerro Pachon as part of the 1998 Gemini site selection process
    # (Ellerbroek 2002, JOSA Vol 19 No 9).

    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts,
                                           Ellerbroek_weights,
                                           interpolant='linear')

    # Use given number of uniformly spaced altitudes
    alts = np.max(Ellerbroek_alts) * np.arange(
        args.nlayers) / (args.nlayers - 1)
    weights = Ellerbroek_interp(alts)  # interpolate the weights
    weights /= sum(weights)  # and renormalize

    # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried
    # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that
    # the size of each screen is actually made infinite by "wrapping" the edges of the screen.)  The
    # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of
    # parameters for the different layers.

    max_speed = 20  # Pick (an arbitrary) maximum wind speed in m/s.
    spd = []  # Wind speed in m/s
    dirn = []  # Wind direction in radians
    r0_500 = []  # Fried parameter in m at a wavelength of 500 nm.
    for i in range(args.nlayers):
        spd.append(u() *
                   max_speed)  # Use a random speed between 0 and max_speed
        dirn.append(
            u() * 360 *
            galsim.degrees)  # And an isotropically distributed wind direction.
        # The turbulence strength of each layer is specified by through its Fried parameter r0_500,
        # which can be thought of as the diameter of a telescope for which atmospheric turbulence
        # and unaberrated diffraction contribute equally to image resolution (at a wavelength of
        # 500nm).  The weights above are for the refractive index structure function (similar to a
        # variance or covariance), however, so we need to use an appropriate scaling relation to
        # distribute the input "net" Fried parameter into a Fried parameter for each layer.  For
        # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5):
        r0_500.append(args.r0_500 * weights[i]**(-3. / 5))
        print(
            "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, "
            "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(),
                                           spd[i] * dirn[i].sin(), r0_500[i]))

    # Additionally, we set the screen temporal evolution `time_step`, and the screen size and scale.
    atm = galsim.Atmosphere(r0_500=r0_500,
                            speed=spd,
                            direction=dirn,
                            altitude=alts,
                            rng=rng,
                            time_step=args.time_step,
                            screen_size=args.screen_size,
                            screen_scale=args.screen_scale)
    # `atm` is now an instance of a galsim.PhaseScreenList object.

    # Place to store the cumulative PSF image if args.accumulate is set.
    psf_img_sum = galsim.ImageD(args.psf_nx, args.psf_nx, scale=args.psf_scale)

    # Field angle (angle on the sky wrt the telescope boresight) at which to compute the PSF.
    theta = (args.x * galsim.arcmin, args.y * galsim.arcmin)

    # Construct an Aperture object for computing the PSF.  The Aperture object describes the
    # illumination pattern of the telescope pupil, and chooses good sampling size and resolution
    # for representing this pattern as an array.
    aper = galsim.Aperture(diam=args.diam,
                           lam=args.lam,
                           obscuration=args.obscuration,
                           nstruts=args.nstruts,
                           strut_thick=args.strut_thick,
                           strut_angle=args.strut_angle * galsim.degrees,
                           screen_list=atm,
                           pad_factor=args.pad_factor,
                           oversampling=args.oversampling)

    # Code to setup the Matplotlib animation.
    metadata = dict(title='Wavefront Movie', artist='Matplotlib')
    writer = anim.FFMpegWriter(fps=15, bitrate=5000, metadata=metadata)

    # For the animation code, we essentially draw a single figure first, and then use various
    # `set_XYZ` methods to update each successive frame.
    fig = plt.figure(facecolor='k', figsize=(11, 6))

    # Axis for the PSF image on the left.
    psf_ax = fig.add_axes([0.08, 0.15, 0.35, 0.7])
    psf_ax.set_xlabel("Arcsec")
    psf_ax.set_ylabel("Arcsec")
    psf_im = psf_ax.imshow(np.ones((128, 128), dtype=np.float64),
                           animated=True,
                           vmin=0.0,
                           vmax=args.psf_vmax,
                           cmap='hot',
                           extent=np.r_[-1, 1, -1, 1] * 0.5 * args.psf_nx *
                           args.psf_scale)

    # Axis for the wavefront image on the right.
    wf_ax = fig.add_axes([0.51, 0.15, 0.35, 0.7])
    wf_ax.set_xlabel("Meters")
    wf_ax.set_ylabel("Meters")
    wf_im = wf_ax.imshow(np.ones((128, 128), dtype=np.float64),
                         animated=True,
                         vmin=-args.wf_vmax,
                         vmax=args.wf_vmax,
                         cmap='YlGnBu',
                         extent=np.r_[-1, 1, -1, 1] * 0.5 *
                         aper.pupil_plane_size)
    cbar_ax = fig.add_axes([0.88, 0.175, 0.03, 0.65])
    cbar_ax.set_ylabel("Radians")
    plt.colorbar(wf_im, cax=cbar_ax)

    # Overlay an alpha-mask on the wavefront image showing which parts are actually illuminated.
    ilum = np.ma.masked_greater(aper.illuminated, 0.5)
    wf_ax.imshow(ilum,
                 alpha=0.4,
                 extent=np.r_[-1, 1, -1, 1] * 0.5 * aper.pupil_plane_size)

    # Color items white to show up on black background
    for ax in [psf_ax, wf_ax, cbar_ax]:
        for _, spine in ax.spines.items():
            spine.set_color('w')
        ax.title.set_color('w')
        ax.xaxis.label.set_color('w')
        ax.yaxis.label.set_color('w')
        ax.tick_params(axis='both', colors='w')

    etext = psf_ax.text(0.05, 0.92, '', transform=psf_ax.transAxes)
    etext.set_color('w')

    nstep = int(args.exptime / args.time_step)
    # Use astropy ProgressBar to keep track of progress and show an estimate for time to completion.
    with ProgressBar(nstep) as bar:
        with writer.saving(fig, args.outfile, 100):
            for i in range(nstep):
                # The wavefront() method with `compact=False` returns a 2D array over the pupil
                # plane described by `aper`.  Here, we just get the wavefront for visualization;
                # this step is normally handled automatically by the PhasePSF code behind the
                # scenes.
                wf = atm.wavefront(
                    aper, theta,
                    compact=False) * 2 * np.pi / args.lam  # radians
                # To make an actual PSF GSObject, we use the makePSF() method, including arguments
                # for the wavelength `lam`, the field angle `theta`, the aperture `aper`, and the
                # exposure time `exptime`.  Here, since we're making a movie, we set the exptime
                # equal to just a single timestep, though normally we'd want to set this to the
                # full exposure time.
                psf = atm.makePSF(lam=args.lam,
                                  theta=theta,
                                  aper=aper,
                                  exptime=args.time_step)
                # `psf` is now just like an any other GSObject, ready to be convolved, drawn, or
                # transformed.  Here, we just draw it into an image to add to our movie.
                psf_img0 = psf.drawImage(nx=args.psf_nx,
                                         ny=args.psf_nx,
                                         scale=args.psf_scale)

                if args.accumulate:
                    psf_img_sum += psf_img0
                    psf_img = psf_img_sum / (i + 1)
                else:
                    psf_img = psf_img0

                # Calculate simple estimate of ellipticity
                e = ellip(simple_moments(psf_img))

                # Matplotlib code updating plot elements
                wf_im.set_array(wf)
                wf_ax.set_title("t={:5.2f} s".format(i * args.time_step))
                psf_im.set_array(psf_img.array)
                etext.set_text(
                    "$e_1$={:6.3f}, $e_2$={:6.3f}, $r^2$={:6.3f}".format(
                        e['e1'], e['e2'], e['rsqr']))
                with warnings.catch_warnings():
                    warnings.simplefilter("ignore")
                    writer.grab_frame(facecolor=fig.get_facecolor())
                bar.update()
예제 #16
0
def simulate_speckle_psf(args):
    '''
    Returns an atmospheric simulation (Van Karman) to replicate Zorro data. 
    Inputs
    ======
    - color: wavelength of the filter desired
    - rnd_seed: random seed for the number generators
    - scale: screen scale for the turbulence layer. TBD what the default should be for robust results
    - total_time: total exposure time (default: 60s)
    - params_save_path: path to save the parameters, except if None
    - exp_time: time for each exposure. Default is .06s
    Returns
    =======
    an array of simulated short exposure (60ms) PSFs
    '''
    # pixel scale depends on color!
    if args.color == 562:
        pixel_scale = .00992
    else:
        pixel_scale = .01095

    # GS quantities
    diameter = 8.1
    obscuration = 1.024 / 8.1
    nx, ny = 256, 256

    # draw random values of r0, r0_weights, altitude, speed, and direction
    atm_args = draw_atmosphere_params(args.rnd_seed, args.save_params_path)

    rng = galsim.BaseDeviate(args.rnd_seed)

    # fix screen size by the highest speed of the layers and the telescope diameter
    # except if speed > 40, which leads to memory errors
    screen_size = min(40, max(atm_args['speed'])) * args.total_time + diameter

    # number of exposures to fit in the total time
    N = int(args.total_time / args.exp_time)
    dead_time = 4. / 1000

    # make the atmosphere object:
    atm = galsim.Atmosphere(screen_size=screen_size, rng=rng, **atm_args)

    if args.scale != 'default':
        for layer in atm:
            layer.screen_scale = args.scale * layer.screen_scale

    # for each exposure in the series, draw a PSF of exposure length exp_time
    psf_series = np.zeros((N, nx, ny))
    for n in range(N):
        psf = atm.makePSF(lam=args.color,
                          t0=n * (args.exp_time + dead_time),
                          exptime=args.exp_time,
                          diam=diameter,
                          obscuration=obscuration)

        psf_series[n] = psf.drawImage(nx=nx, ny=ny, scale=pixel_scale).array

    if args.save_psfs_path is not None:
        hdu = fits.PrimaryHDU(psf_series)
        hdu.writeto(args.save_psfs_path)

    return psf_series
예제 #17
0
def test_phase_gradient_shoot():
    """Test that photon-shooting PSFs match Fourier optics PSFs when using the same phase screens,
    and also match the expected size from an analytic VonKarman-convolved-with-Airy PSF.
    """
    # Make the atmosphere
    seed = 12345
    r0_500 = 0.15  # m
    L0 = 20.0  # m
    nlayers = 6
    screen_size = 102.4  # m

    # Ideally, we'd use as small a screen scale as possible here.  The runtime for generating
    # phase screens scales like `screen_scale`^-2 though, which is pretty steep, so we use a larger-
    # than-desireable scale for the __name__ != '__main__' branch.  This is known to lead to a bias
    # in PSF size, which we attempt to account for below when actually comparing FFT PSF moments to
    # photon-shooting PSF moments.  Note that we don't need to apply such a correction when
    # comparing the photon-shooting PSF to the analytic VonKarman PSF since these both avoid the
    # screen_scale problem to begin with.  (Even though we do generate screens for the
    # photon-shooting PSF, because we truncate the power spectrum above kcrit, we don't require as
    # high of resolution).
    if __name__ == '__main__':
        screen_scale = 0.025 # m
    else:
        screen_scale = 0.1 # m
    max_speed = 20  # m/s

    rng = galsim.BaseDeviate(seed)
    u = galsim.UniformDeviate(rng)

    # Use atmospheric weights from 1998 Gemini site selection process as something reasonably
    # realistic.  (Ellerbroek 2002, JOSA Vol 19 No 9).
    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(
            Ellerbroek_alts,
            Ellerbroek_weights,
            interpolant='linear')
    alts = np.max(Ellerbroek_alts)*np.arange(nlayers)/(nlayers-1)
    weights = Ellerbroek_interp(alts)
    weights /= sum(weights)

    spd = []  # Wind speed in m/s
    dirn = [] # Wind direction in radians
    r0_500s = [] # Fried parameter in m at a wavelength of 500 nm.
    for i in range(nlayers):
        spd.append(u()*max_speed)
        dirn.append(u()*360*galsim.degrees)
        r0_500s.append(r0_500*weights[i]**(-3./5))
    rng2 = rng.duplicate()
    atm = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts, rng=rng,
                            screen_size=screen_size, screen_scale=screen_scale)
    # Make a second atmosphere to use for geometric photon-shooting
    atm2 = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts,
                             rng=rng2, screen_size=screen_size, screen_scale=screen_scale)
    # These should be equal at the moment, before we've actually instantiated any screens by drawing
    # with them.
    assert atm == atm2

    lam = 500.0
    diam = 4.0
    pad_factor = 0.5
    oversampling = 0.5

    aper = galsim.Aperture(diam=diam, lam=lam,
                           screen_list=atm, pad_factor=pad_factor,
                           oversampling=oversampling)

    xs = np.empty((10,), dtype=float)
    ys = np.empty((10,), dtype=float)
    u.generate(xs)
    u.generate(ys)
    thetas = [(x*galsim.degrees, y*galsim.degrees) for x, y in zip(xs, ys)]

    if __name__ == '__main__':
        exptime = 15.0
        time_step = 0.05
        centroid_tolerance = 0.06
        size_tolerance = 0.06  # absolute
        size_bias = 0.02  # as a fraction
        shape_tolerance = 0.01
    else:
        exptime = 1.0
        time_step = 0.1
        centroid_tolerance = 0.3
        size_tolerance = 0.3
        size_bias = 0.15
        shape_tolerance = 0.04

    psfs = [atm.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper) for th in thetas]
    psfs2 = [atm2.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper, time_step=time_step)
             for th in thetas]
    shoot_moments = []
    fft_moments = []

    vk = galsim.VonKarman(lam=lam, r0=r0_500*(lam/500)**1.2, L0=L0)
    airy = galsim.Airy(lam=lam, diam=diam)
    obj = galsim.Convolve(vk, airy)
    vkImg = obj.drawImage(nx=48, ny=48, scale=0.05)
    vkMom = galsim.hsm.FindAdaptiveMom(vkImg)

    for psf, psf2 in zip(psfs, psfs2):
        im_shoot = psf.drawImage(nx=48, ny=48, scale=0.05, method='phot', n_photons=100000, rng=rng)
        im_fft = psf2.drawImage(nx=48, ny=48, scale=0.05)

        # at this point, the atms should be different.
        assert atm != atm2

        shoot_moment = galsim.hsm.FindAdaptiveMom(im_shoot)
        fft_moment = galsim.hsm.FindAdaptiveMom(im_fft)

        print()
        print()
        print()
        print(shoot_moment.observed_shape.g1)
        print(fft_moment.observed_shape.g1)

        # import matplotlib.pyplot as plt
        # fig, axes = plt.subplots(ncols=2)
        # axes[0].imshow(im_shoot.array)
        # axes[1].imshow(im_fft.array)
        # plt.show()

        np.testing.assert_allclose(
            shoot_moment.moments_centroid.x,
            fft_moment.moments_centroid.x,
            rtol=0, atol=centroid_tolerance,
            err_msg='Phase gradient centroid x not close to fft centroid')

        np.testing.assert_allclose(
            shoot_moment.moments_centroid.y,
            fft_moment.moments_centroid.y,
            rtol=0, atol=centroid_tolerance,
            err_msg='Phase gradient centroid y not close to fft centroid')

        np.testing.assert_allclose(
            shoot_moment.moments_sigma,
            fft_moment.moments_sigma*(1+size_bias),
            rtol=0, atol=size_tolerance,
            err_msg='Phase gradient sigma not close to fft sigma')

        np.testing.assert_allclose(
            shoot_moment.moments_sigma,
            vkMom.moments_sigma,
            rtol=0.1, atol=0,
            err_msg='Phase gradient sigma not close to infinite exposure analytic sigma'
        )

        np.testing.assert_allclose(
            shoot_moment.observed_shape.g1,
            fft_moment.observed_shape.g1,
            rtol=0, atol=shape_tolerance,
            err_msg='Phase gradient shape g1 not close to fft shape')

        np.testing.assert_allclose(
            shoot_moment.observed_shape.g2,
            fft_moment.observed_shape.g2,
            rtol=0, atol=shape_tolerance,
            err_msg='Phase gradient shape g2 not close to fft shape')

        shoot_moments.append(shoot_moment)
        fft_moments.append(fft_moment)

    # I cheated.  Here's code to evaluate how small I could potentially set the tolerances above.
    # I think they're all fine, but this is admittedly a tad bit backwards.
    best_size_bias = np.mean([s1.moments_sigma/s2.moments_sigma
                              for s1, s2 in zip(shoot_moments, fft_moments)])
    print("best_size_bias = ", best_size_bias)
    print("xcentroid")
    print(max(np.abs([s1.moments_centroid.x - s2.moments_centroid.x
                      for s1, s2 in zip(shoot_moments, fft_moments)])))
    print("ycentroid")
    print(max(np.abs([s1.moments_centroid.y - s2.moments_centroid.y
                      for s1, s2 in zip(shoot_moments, fft_moments)])))
    print("size")
    print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(1+size_bias)
                      for s1, s2 in zip(shoot_moments, fft_moments)])))
    print("bestsize")
    print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(best_size_bias)
                      for s1, s2 in zip(shoot_moments, fft_moments)])))
    print("g1")
    print(max(np.abs([s1.observed_shape.g1 - s2.observed_shape.g1
                      for s1, s2 in zip(shoot_moments, fft_moments)])))
    print("g2")
    print(max(np.abs([s1.observed_shape.g2 - s2.observed_shape.g2
                      for s1, s2 in zip(shoot_moments, fft_moments)])))

    # import matplotlib.pyplot as plt
    # fig, ax = plt.subplots(nrows=1, ncols=1)
    # ax.scatter(
    #     [s.observed_shape.g1 for s in shoot_moments],
    #     [s.observed_shape.g1 for s in fft_moments]
    # )
    # xlim = ax.get_xlim()
    # ylim = ax.get_ylim()
    # lim = (min(xlim[0], ylim[0]), max(xlim[1], ylim[1]))
    # ax.set_xlim(lim)
    # ax.set_ylim(lim)
    # ax.plot([-100, 100], [-100, 100])
    # plt.show()

    # Verify that shoot with rng=None runs
    psf.shoot(100, rng=None)

    # Check that second_kick=False and second_kick=GSObject also run, and that we can shoot
    # photons with these settings.
    for second_kick in [False, galsim.Gaussian(fwhm=1)]:
        psf = atm.makePSF(lam=500.0, exptime=10, aper=aper, second_kick=second_kick)
        assert psf.second_kick == second_kick
        img = psf.drawImage(nx=64, ny=64, scale=0.1, method='phot', n_photons=100)

    # Verify that we can phase_gradient_shoot with 0 or 1 photons.
    psf.shoot(0)
    psf.shoot(1)
예제 #18
0
def test_ne():
    """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals."""
    pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file))

    # Test galsim.Aperture __ne__
    objs = [galsim.Aperture(diam=1.0),
            galsim.Aperture(diam=1.1),
            galsim.Aperture(diam=1.0, oversampling=1.5),
            galsim.Aperture(diam=1.0, pad_factor=1.5),
            galsim.Aperture(diam=1.0, circular_pupil=False),
            galsim.Aperture(diam=1.0, obscuration=0.3),
            galsim.Aperture(diam=1.0, nstruts=3),
            galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2),
            galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees),
            galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im),
            galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im,
                            pupil_angle=10.0*galsim.degrees)]
    all_obj_diff(objs)

    # Test AtmosphericScreen __ne__
    rng = galsim.BaseDeviate(1)
    objs = [galsim.AtmosphericScreen(10.0, rng=rng),
            galsim.AtmosphericScreen(1.0, rng=rng),
            galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.01),
            galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.02),
            galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.998, time_step=0.02),
            galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1),
            galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0),
            galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0),
            ]
    all_obj_diff(objs)
    objs.append(galsim.AtmosphericScreen(10.0, rng=rng))
    objs[-1].instantiate()
    # Should still all be __ne__, but first and last will have the same hash this time.
    assert hash(objs[0]) == hash(objs[-1])
    all_obj_diff(objs, check_hash=False)

    # Test OpticalScreen __ne__
    objs = [galsim.OpticalScreen(diam=1.0),
            galsim.OpticalScreen(diam=1.0, tip=1.0),
            galsim.OpticalScreen(diam=1.0, tilt=1.0),
            galsim.OpticalScreen(diam=1.0, defocus=1.0),
            galsim.OpticalScreen(diam=1.0, astig1=1.0),
            galsim.OpticalScreen(diam=1.0, astig2=1.0),
            galsim.OpticalScreen(diam=1.0, coma1=1.0),
            galsim.OpticalScreen(diam=1.0, coma2=1.0),
            galsim.OpticalScreen(diam=1.0, trefoil1=1.0),
            galsim.OpticalScreen(diam=1.0, trefoil2=1.0),
            galsim.OpticalScreen(diam=1.0, spher=1.0),
            galsim.OpticalScreen(diam=1.0, spher=1.0, lam_0=100.0),
            galsim.OpticalScreen(diam=1.0, aberrations=[0,0,1.1]), # tip=1.1
            ]
    all_obj_diff(objs)

    # Test PhaseScreenList __ne__
    atm = galsim.Atmosphere(10.0, vx=1.0)
    objs = [galsim.PhaseScreenList(atm),
            galsim.PhaseScreenList(objs),  # Reuse list of OpticalScreens above
            galsim.PhaseScreenList(objs[0:2])]
    all_obj_diff(objs)

    # Test PhaseScreenPSF __ne__
    psl = galsim.PhaseScreenList(atm)
    objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)]
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)]
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)]
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)]
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')]
    stepk = objs[0].stepk
    maxk = objs[0].maxk
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)]
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)]
    all_obj_diff(objs)
예제 #19
0
def test_stepk_maxk():
    """Test options to specify (or not) stepk and maxk.
    """
    import time
    aper = galsim.Aperture(diam=1.0)
    rng = galsim.BaseDeviate(123456)
    # Test frozen AtmosphericScreen first
    atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng)
    psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec)
    t0 = time.time()
    stepk1 = psf.stepk
    maxk1 = psf.maxk
    t1 = time.time()
    print('stepk1 = ',stepk1)
    print('maxk1 = ',maxk1)
    print('t1 = ',t1-t0)

    psf._prepareDraw()
    stepk2 = psf.stepk
    maxk2 = psf.maxk
    t2 = time.time()
    print('stepk2 = ',stepk2)
    print('maxk2 = ',maxk2)
    print('goodImageSize = ',psf.getGoodImageSize(0.2))
    print('t2 = ',t2-t1)

    np.testing.assert_allclose(stepk1, stepk2, rtol=0.05)
    np.testing.assert_allclose(maxk1, maxk2, rtol=0.05)

    # Also make sure that prepareDraw wasn't called to calculate the first one.
    # Should be very quick to do the first stepk, maxk, but slow to do the second.
    assert t1-t0 < t2-t1

    # Check that stepk changes when gsparams.folding_threshold become more extreme.
    # (Note: maxk is independent of maxk_threshold because of the hard edge of the aperture.)
    psf1 = galsim.PhaseScreenPSF(atm, 500.0, diam=1.0, scale_unit=galsim.arcsec,
                                 gsparams=galsim.GSParams(folding_threshold=1.e-3,
                                                          maxk_threshold=1.e-4))
    stepk3 = psf1.stepk
    maxk3 = psf1.maxk
    print('stepk3 = ',stepk3)
    print('maxk3 = ',maxk3)
    print('goodImageSize = ',psf1.getGoodImageSize(0.2))
    assert stepk3 < stepk1
    assert maxk3 == maxk1

    # Check that it respects the force_stepk and force_maxk parameters
    psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec,
                                 _force_stepk=stepk2/1.5, _force_maxk=maxk2*2.0)
    np.testing.assert_almost_equal(
            psf2.stepk, stepk2/1.5, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for stepk")
    np.testing.assert_almost_equal(
            psf2.maxk, maxk2*2.0, decimal=7,
            err_msg="PhaseScreenPSF did not adopt forced value for maxk")
    do_pickle(psf)
    do_pickle(psf2)

    # Try out non-geometric-shooting
    psf3 = atm.makePSF(lam=500.0, aper=aper, geometric_shooting=False)
    img = galsim.Image(32, 32, scale=0.2)
    do_shoot(psf3, img, "PhaseScreenPSF")
    # Also make sure a few other methods at least run
    psf3.centroid
    psf3.max_sb

    # If we force stepk very low, it will trigger a warning when we try to draw it.
    psf4 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec,
                                 _force_stepk=stepk2/3.5)
    with assert_warns(galsim.GalSimWarning):
        psf4._prepareDraw()

    # Can suppress this warning if desired.
    psf5 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec,
                                 _force_stepk=stepk2/3.5, suppress_warning=True)
    with assert_raises(AssertionError):
        with assert_warns(galsim.GalSimWarning):
            psf5._prepareDraw()
예제 #20
0
def make_plot(args):
    # Initiate some GalSim random number generators.
    rng = galsim.BaseDeviate(args.seed)
    u = galsim.UniformDeviate(rng)

    # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series
    # of 2D turbulent screens.  The galsim.Atmosphere() helper function is useful for constructing
    # this screen list.

    # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower
    # layers consistent with direct measurements.  The specific values we use are from SCIDAR
    # measurements on Cerro Pachon as part of the 1998 Gemini site selection process
    # (Ellerbroek 2002, JOSA Vol 19 No 9).

    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts,
                                           Ellerbroek_weights,
                                           interpolant='linear')

    # Use given number of uniformly spaced altitudes
    alts = np.max(Ellerbroek_alts) * np.arange(
        args.nlayers) / (args.nlayers - 1)
    weights = Ellerbroek_interp(alts)  # interpolate the weights
    weights /= sum(weights)  # and renormalize

    # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried
    # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that
    # the size of each screen is actually made infinite by "wrapping" the edges of the screen.)  The
    # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of
    # parameters for the different layers.

    spd = []  # Wind speed in m/s
    dirn = []  # Wind direction in radians
    r0_500 = []  # Fried parameter in m at a wavelength of 500 nm.
    for i in range(args.nlayers):
        spd.append(u() * 20)  # Use a random speed between 0 and max_speed
        dirn.append(
            u() * 360 *
            galsim.degrees)  # And an isotropically distributed wind direction.
        # The turbulence strength of each layer is specified by through its Fried parameter r0_500,
        # which can be thought of as the diameter of a telescope for which atmospheric turbulence
        # and unaberrated diffraction contribute equally to image resolution (at a wavelength of
        # 500nm).  The weights above are for the refractive index structure function (similar to a
        # variance or covariance), however, so we need to use an appropriate scaling relation to
        # distribute the input "net" Fried parameter into a Fried parameter for each layer.  For
        # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5):
        r0_500.append(args.r0_500 * weights[i]**(-3. / 5))
        print(
            "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, "
            "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(),
                                           spd[i] * dirn[i].sin(), r0_500[i]))

    # Make sure to use a consistent seed for the atmosphere when varying kcrit
    # Additionally, we set the screen size and scale.
    atmRng = galsim.BaseDeviate(args.seed + 1)
    print("Inflating atmosphere")
    atm = galsim.Atmosphere(r0_500=r0_500,
                            L0=args.L0,
                            speed=spd,
                            direction=dirn,
                            altitude=alts,
                            rng=atmRng,
                            screen_size=args.screen_size,
                            screen_scale=args.screen_scale)
    with ProgressBar(args.nlayers) as bar:
        atm.instantiate(_bar=bar)
    print(atm[0].screen_scale, atm[0].screen_size)
    print(atm[0]._tab2d.f.shape)
    # `atm` is now an instance of a galsim.PhaseScreenList object.

    x = np.linspace(-0.5 * args.nx * args.scale, 0.5 * args.nx * args.scale,
                    args.nx)
    x, y = np.meshgrid(x, x)
    img = atm.wavefront(x, y, 0)

    save_plot(img, args.outprefix + "full.png")

    del atm

    kcrits = np.logspace(np.log10(args.kmin), np.log10(args.kmax), 4)
    r0 = args.r0_500 * (args.lam / 500.0)**(6. / 5)
    for icol, kcrit in enumerate(kcrits):
        atmRng = galsim.BaseDeviate(args.seed + 1)
        atmLowK = galsim.Atmosphere(r0_500=r0_500,
                                    L0=args.L0,
                                    speed=spd,
                                    direction=dirn,
                                    altitude=alts,
                                    rng=atmRng,
                                    screen_size=args.screen_size,
                                    screen_scale=args.screen_scale)
        with ProgressBar(args.nlayers) as bar:
            atmLowK.instantiate(kmax=kcrit / r0, _bar=bar)

        img = atmLowK.wavefront(x, y, 0)
        save_plot(img, "{}{}_{}".format(args.outprefix, icol, "low.png"))
        del atmLowK

        atmRng = galsim.BaseDeviate(args.seed + 1)
        atmHighK = galsim.Atmosphere(r0_500=r0_500,
                                     L0=args.L0,
                                     speed=spd,
                                     direction=dirn,
                                     altitude=alts,
                                     rng=atmRng,
                                     screen_size=args.screen_size,
                                     screen_scale=args.screen_scale)
        with ProgressBar(args.nlayers) as bar:
            atmHighK.instantiate(kmin=kcrit / r0, _bar=bar)

        img = atmHighK.wavefront(x, y, 0)
        save_plot(img, "{}{}_{}".format(args.outprefix, icol, "high.png"))
        del atmHighK
예제 #21
0
def test_phase_screen_list():
    """Test list-like behaviors of PhaseScreenList."""
    rng = galsim.BaseDeviate(1234)
    rng2 = galsim.BaseDeviate(123)

    aper = galsim.Aperture(diam=1.0)

    ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, rng=rng)
    do_pickle(ar1)
    do_pickle(ar1, func=lambda x: x.tab2d(12.3, 45.6))
    do_pickle(ar1, func=lambda x: x.wavefront(aper).sum())

    # Check that L0=np.inf and L0=None yield the same thing here too.
    ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, rng=rng)
    assert ar1 == ar2
    # Create a couple new screens with different types/parameters
    ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, rng=rng2)
    assert ar1 != ar2
    ar3 = galsim.OpticalScreen(aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1])
    do_pickle(ar3)
    do_pickle(ar3, func=lambda x:x.wavefront(aper).sum())
    atm = galsim.Atmosphere(screen_size=30.0,
                            altitude=[0.0, 1.0],
                            speed=[1.0, 2.0],
                            direction=[0.0*galsim.degrees, 120*galsim.degrees],
                            r0_500=0.15,
                            rng=rng)
    atm.append(ar3)
    do_pickle(atm)
    do_pickle(atm, func=lambda x:x.wavefront(aper).sum())

    # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__
    atm2 = galsim.PhaseScreenList(atm[:-1])  # Refers to first n-1 screens
    assert atm != atm2
    # Append a different screen to the end of atm2
    atm2.append(ar2)
    assert atm != atm2
    # Swap the last screen in atm2 for the one that should match atm.
    del atm2[-1]
    atm2.append(atm[-1])
    assert atm == atm2

    # Test building from empty PhaseScreenList
    atm3 = galsim.PhaseScreenList()
    atm3.extend(atm2)
    assert atm == atm2

    # Test constructing from existing PhaseScreenList
    atm4 = galsim.PhaseScreenList(atm3)
    del atm4[-1]
    assert atm != atm4
    atm4.append(atm[-1])
    assert atm == atm4

    # Test swap
    atm4[0], atm4[1] = atm4[1], atm4[0]
    assert atm != atm4
    atm4[0], atm4[1] = atm4[1], atm4[0]
    assert atm == atm4

    wf = atm.wavefront(aper)
    wf2 = atm2.wavefront(aper)
    wf3 = atm3.wavefront(aper)
    wf4 = atm4.wavefront(aper)

    np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent")
    np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent")
    np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent")

    # Check copy
    import copy
    # Shallow copy copies by reference.
    atm5 = copy.copy(atm)
    assert atm[0] == atm5[0]
    assert atm[0] is atm5[0]
    atm.advance()
    assert atm[0] == atm5[0]
    assert atm[0] is atm5[0]
    # Deepcopy actually makes an indepedent object in memory.
    atm5 = copy.deepcopy(atm)
    assert atm[0] == atm5[0]
    assert atm[0] is not atm5[0]
    atm.advance()
    assert atm[0] != atm5[0]

    # Constructor should accept both list and indiv layers as arguments.
    atm6 = galsim.PhaseScreenList(atm[0])
    atm7 = galsim.PhaseScreenList([atm[0]])
    assert atm6 == atm7
    atm6 = galsim.PhaseScreenList(atm[0], atm[1])
    atm7 = galsim.PhaseScreenList([atm[0], atm[1]])
    atm8 = galsim.PhaseScreenList(atm[0:2])  # Slice returns PhaseScreenList, so this works too.
    assert atm6 == atm7
    assert atm6 == atm8

    # Check some actual derived PSFs too, not just phase screens.  Use a small pupil_plane_size and
    # relatively large pupil_plane_scale to speed up the unit test.
    atm.advance_by(1.0)
    do_pickle(atm)
    atm.reset()
    kwargs = dict(exptime=0.06, diam=1.0, lam=1000.0)
    psf = atm.makePSF(**kwargs)
    do_pickle(psf)
    do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1))

    # Need to reset atm2 since both atm and atm2 reference the same layer objects (not copies).
    # Not sure if this is a feature or a bug, but it's also how regular python lists work.
    atm2.reset()
    psf2 = atm2.makePSF(**kwargs)

    atm3.reset()
    psf3 = atm3.makePSF(**kwargs)

    atm4.reset()
    psf4 = atm4.makePSF(**kwargs)

    np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent")
    np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent")
    np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent")
예제 #22
0
def test_phase_screen_list():
    """Test list-like behaviors of PhaseScreenList."""
    rng = galsim.BaseDeviate(1234)
    rng2 = galsim.BaseDeviate(123)

    aper = galsim.Aperture(diam=1.0)

    ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, time_step=0.01, rng=rng)
    assert ar1._time == 0.0, "AtmosphericScreen initialized with non-zero time."
    do_pickle(ar1)
    do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, 0.0).sum())
    do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0)))
    t = np.empty_like(aper.u)
    ud = galsim.UniformDeviate(rng.duplicate())
    ud.generate(t.ravel())
    t *= 0.1  # Only do a few boiling steps
    do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, t).sum())
    do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, t)))

    # Try seeking backwards
    assert ar1._time > 0.0
    ar1._seek(0.0)
    # But not before t=0.0
    with assert_raises(ValueError):
        ar1._seek(-1.0)

    # Check that L0=np.inf and L0=None yield the same thing here too.
    ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, time_step=0.01, rng=rng)
    # Before ar2 is instantiated, it's unequal to ar1, even though they were initialized with the
    # same arguments (the hashes are the same though).  After both have been instantiated with the
    # same range of k (ar1 through use of .wavefront() and ar2 explicitly), then they are equal (
    # and the hashes are still the same).
    assert hash(ar1) == hash(ar2)
    assert ar1 != ar2
    ar2.instantiate()
    assert ar1 == ar2
    assert hash(ar1) == hash(ar2)
    # Create a couple new screens with different types/parameters
    ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, time_step=0.015, rng=rng2)
    ar2.instantiate()
    assert ar1 != ar2
    ar3 = galsim.OpticalScreen(diam=1.0, aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1],
                               obscuration=0.3, annular_zernike=True)
    do_pickle(ar3)
    do_pickle(ar3, func=lambda x:x.wavefront(aper.u, aper.v).sum())
    do_pickle(ar3, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v)))
    atm = galsim.Atmosphere(screen_size=30.0,
                            altitude=[0.0, 1.0],
                            speed=[1.0, 2.0],
                            direction=[0.0*galsim.degrees, 120*galsim.degrees],
                            r0_500=0.15,
                            rng=rng)
    atm.append(ar3)
    do_pickle(atm)
    do_pickle(atm, func=lambda x:x.wavefront(aper.u, aper.v, 0.0, theta0).sum())
    do_pickle(atm, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0)))

    # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__
    atm2 = atm[:-1]  # Refers to first n-1 screens
    assert atm != atm2
    # Append a different screen to the end of atm2
    atm2.append(ar2)
    assert atm != atm2
    # Swap the last screen in atm2 for the one that should match atm.
    del atm2[-1]
    atm2.append(atm[-1])
    assert atm == atm2

    with assert_raises(TypeError):
        atm['invalid']
    with assert_raises(IndexError):
        atm[3]

    # Test building from empty PhaseScreenList
    atm3 = galsim.PhaseScreenList()
    atm3.extend(atm2)
    assert atm == atm3

    # Test constructing from existing PhaseScreenList
    atm4 = galsim.PhaseScreenList(atm3)
    del atm4[-1]
    assert atm != atm4
    atm4.append(atm[-1])
    assert atm == atm4

    # Test swap
    atm4[0], atm4[1] = atm4[1], atm4[0]
    assert atm != atm4
    atm4[0], atm4[1] = atm4[1], atm4[0]
    assert atm == atm4

    wf = atm.wavefront(aper.u, aper.v, None, theta0)
    wf2 = atm2.wavefront(aper.u, aper.v, None, theta0)
    wf3 = atm3.wavefront(aper.u, aper.v, None, theta0)
    wf4 = atm4.wavefront(aper.u, aper.v, None, theta0)

    np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent")
    np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent")
    np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent")

    # Check copy
    import copy
    # Shallow copy copies by reference.
    atm5 = copy.copy(atm)
    assert atm[0] == atm5[0]
    assert atm[0] is atm5[0]
    atm._seek(1.0)
    assert atm[0]._time == 1.0, "Wrong time for AtmosphericScreen"
    assert atm[0] == atm5[0]
    assert atm[0] is atm5[0]
    # Deepcopy actually makes an indepedent object in memory.
    atm5 = copy.deepcopy(atm)
    assert atm[0] == atm5[0]
    assert atm[0] is not atm5[0]
    atm._seek(2.0)
    assert atm[0]._time == 2.0, "Wrong time for AtmosphericScreen"
    # But we still get equality, since this doesn't depend on mutable internal state:
    assert atm[0] == atm5[0]

    # Constructor should accept both list and indiv layers as arguments.
    atm6 = galsim.PhaseScreenList(atm[0])
    atm7 = galsim.PhaseScreenList([atm[0]])
    assert atm6 == atm7
    do_pickle(atm6, func=lambda x:x.wavefront(aper.u, aper.v, None, theta0).sum())
    do_pickle(atm6, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0)))

    atm6 = galsim.PhaseScreenList(atm[0], atm[1])
    atm7 = galsim.PhaseScreenList([atm[0], atm[1]])
    atm8 = galsim.PhaseScreenList(atm[0:2])  # Slice returns PhaseScreenList, so this works too.
    assert atm6 == atm7
    assert atm6 == atm8

    # Check some actual derived PSFs too, not just phase screens.  Use a small pupil_plane_size and
    # relatively large pupil_plane_scale to speed up the unit test.
    atm._reset()
    assert atm[0]._time == 0.0, "Wrong time for AtmosphericScreen"
    kwargs = dict(exptime=0.05, time_step=0.01, diam=1.1, lam=1000.0)
    psf = atm.makePSF(**kwargs)
    do_pickle(psf)
    do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1))

    psf2 = atm2.makePSF(**kwargs)
    psf3 = atm3.makePSF(**kwargs)
    psf4 = atm4.makePSF(**kwargs)

    np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent")
    np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent")
    np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent")

    # Check errors in u,v,t shapes.
    assert_raises(ValueError, ar1.wavefront, aper.u, aper.v[:-1,:-1])
    assert_raises(ValueError, ar1.wavefront, aper.u[:-1,:-1], aper.v)
    assert_raises(ValueError, ar1.wavefront, aper.u, aper.v, 0.1 * aper.u[:-1,:-1])
    assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v[:-1,:-1])
    assert_raises(ValueError, ar1.wavefront_gradient, aper.u[:-1,:-1], aper.v)
    assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v, 0.1 * aper.u[:-1,:-1])

    assert_raises(ValueError, ar3.wavefront, aper.u, aper.v[:-1,:-1])
    assert_raises(ValueError, ar3.wavefront, aper.u[:-1,:-1], aper.v)
    assert_raises(ValueError, ar3.wavefront_gradient, aper.u, aper.v[:-1,:-1])
    assert_raises(ValueError, ar3.wavefront_gradient, aper.u[:-1,:-1], aper.v)
예제 #23
0
def make_movie(args):
    rng = galsim.BaseDeviate(args.seed)
    u = galsim.UniformDeviate(rng)
    # Generate 1D Gaussian random fields for each aberration.
    t = np.arange(-args.n/2, args.n/2)
    corr = np.exp(-0.5*t**2/args.ell**2)
    pk = np.fft.fft(np.fft.fftshift(corr))
    ak = np.sqrt(2*pk)
    phi = np.random.uniform(size=(args.n, args.jmax))
    zk = ak[:, None]*np.exp(2j*np.pi*phi)
    aberrations = args.n/2*np.fft.ifft(zk, axis=0).real
    measured_std = np.mean(np.std(aberrations, axis=0))
    aberrations *= args.sigma/measured_std
    aberrations -= np.mean(aberrations, axis=0)

    # For the atmosphere screens, we first estimates weights, so that the turbulence is dominated by
    # the lower layers consistent with direct measurements.  The specific values we use are from
    # SCIDAR measurements on Cerro Pachon as part of the 1998 Gemini site selection process
    # (Ellerbroek 2002, JOSA Vol 19 No 9).
    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights,
                                           interpolant='linear')
    alts = np.max(Ellerbroek_alts)*np.arange(args.nlayers)/(args.nlayers-1)
    weights = Ellerbroek_interp(alts)  # interpolate the weights
    weights /= sum(weights)  # and renormalize
    spd = []  # Wind speed in m/s
    dirn = [] # Wind direction in radians
    r0_500 = [] # Fried parameter in m at a wavelength of 500 nm.
    for i in range(args.nlayers):
        spd.append(u()*args.max_speed)  # Use a random speed between 0 and args.max_speed
        dirn.append(u()*360*galsim.degrees)  # And an isotropically distributed wind direction.
        r0_500.append(args.r0_500*weights[i]**(-3./5))
        print("Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, "
              "and r0_500 {:5.3f} m."
              .format(alts[i], spd[i]*dirn[i].cos(), spd[i]*dirn[i].sin(), r0_500[i]))
    if args.nlayers > 0:
        # Make two identical Atmospheres.  They will diverge when one gets drawn using Fourier
        # optics and the other gets drawn with geometric optics.
        fft_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts,
                                    rng=rng.duplicate(),
                                    screen_size=args.screen_size, screen_scale=args.screen_scale)
        geom_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts,
                                     rng=rng.duplicate(), screen_size=args.screen_size,
                                     screen_scale=args.screen_scale)
    else:
        fft_atm = galsim.PhaseScreenList()
        geom_atm = galsim.PhaseScreenList()

    # Before either of this has been instantiated, they are identical
    assert fft_atm == geom_atm

    # If any AtmosphericScreens are included, we manually instantiate here so we can have a
    # uniformly updating ProgressBar both here and below when actually drawing PSFs.  Normally, it's
    # okay to let the atms automatically instantiate, which happens when the first PSF is drawn, or
    # the first wavefront is queried.
    if args.nlayers > 0:
        print("Instantiating screens")
        with ProgressBar(2*args.nlayers) as bar:
            fft_atm.instantiate(_bar=bar)
            r0 = args.r0_500*(args.lam/500)**1.2
            geom_atm.instantiate(kmax=0.2/r0, _bar=bar)
            # After instantiation, they're only equal if there's no atmosphere.
            assert fft_atm != geom_atm

    # Setup Fourier and geometric apertures
    fft_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration,
                               pad_factor=args.pad_factor, oversampling=args.oversampling,
                               nstruts=args.nstruts, strut_thick=args.strut_thick,
                               strut_angle=args.strut_angle*galsim.degrees)
    geom_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration,
                                pad_factor=args.geom_oversampling, oversampling=0.5,
                                nstruts=args.nstruts, strut_thick=args.strut_thick,
                                strut_angle=args.strut_angle*galsim.degrees)

    scale = args.size/args.nx
    extent = np.r_[-1,1,-1,1]*args.size/2

    fft_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale)
    geom_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale)

    # Code to setup the Matplotlib animation.
    metadata = dict(title="FFT vs geom movie", artist='Matplotlib')
    writer = anim.FFMpegWriter(fps=15, bitrate=10000, metadata=metadata)

    fig = Figure(facecolor='k', figsize=(16, 9))
    FigureCanvasAgg(fig)

    fft_ax = fig.add_axes([0.07, 0.08, 0.36, 0.9])
    fft_ax.set_xlabel("Arcsec")
    fft_ax.set_ylabel("Arcsec")
    fft_ax.set_title("Fourier Optics")
    fft_im = fft_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent,
                           vmin=0.0, vmax=args.vmax)

    # Axis for the wavefront image on the right.
    geom_ax = fig.add_axes([0.50, 0.08, 0.36, 0.9])
    geom_ax.set_xlabel("Arcsec")
    geom_ax.set_ylabel("Arcsec")
    geom_ax.set_title("Geometric Optics")
    geom_im = geom_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent,
                             vmin=0.0, vmax=args.vmax)

    # Color items white to show up on black background
    for ax in [fft_ax, geom_ax]:
        for _, spine in ax.spines.items():
            spine.set_color('w')
        ax.title.set_color('w')
        ax.xaxis.label.set_color('w')
        ax.yaxis.label.set_color('w')
        ax.tick_params(axis='both', colors='w')

    ztext = []
    for i in range(2, args.jmax+1):
        x = 0.88
        y = 0.1 + (args.jmax-i)/args.jmax*0.8
        ztext.append(fig.text(x, y, "Z{:d} = {:5.3f}".format(i, 0.0)))
        ztext[-1].set_color('w')

    M_fft = fft_ax.text(0.02, 0.955, '', transform=fft_ax.transAxes)
    M_fft.set_color('w')
    M_geom = geom_ax.text(0.02, 0.955, '', transform=geom_ax.transAxes)
    M_geom.set_color('w')

    etext_fft = fft_ax.text(0.02, 0.91, '', transform=fft_ax.transAxes)
    etext_fft.set_color('w')
    etext_geom = geom_ax.text(0.02, 0.91, '', transform=geom_ax.transAxes)
    etext_geom.set_color('w')

    fft_mom = np.empty((args.n, 8), dtype=float)
    geom_mom = np.empty((args.n, 8), dtype=float)

    fullpath = args.out+"movie.mp4"
    subdir, filename = os.path.split(fullpath)
    if subdir and not os.path.isdir(subdir):
        os.makedirs(subdir)

    print("Drawing PSFs")
    with ProgressBar(args.n) as bar:
        with writer.saving(fig, fullpath, 100):
            t0 = 0.0
            for i, aberration in enumerate(aberrations):
                optics = galsim.OpticalScreen(args.diam, obscuration=args.obscuration,
                                              aberrations=[0]+aberration.tolist())
                fft_psl = galsim.PhaseScreenList(fft_atm._layers+[optics])
                geom_psl = galsim.PhaseScreenList(geom_atm._layers+[optics])
                fft_psf = fft_psl.makePSF(
                        lam=args.lam, aper=fft_aper, t0=t0, exptime=args.time_step)
                geom_psf = geom_psl.makePSF(
                        lam=args.lam, aper=geom_aper, t0=t0, exptime=args.time_step)

                fft_img0 = fft_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale)

                geom_img0 = geom_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale,
                                               method='phot', n_photons=100000)

                t0 += args.time_step

                if args.accumulate:
                    fft_img_sum += fft_img0
                    geom_img_sum += geom_img0
                    fft_img = fft_img_sum/(i+1)
                    geom_img = geom_img_sum/(i+1)
                else:
                    fft_img = fft_img0
                    geom_img = geom_img0


                fft_im.set_array(fft_img.array)
                geom_im.set_array(geom_img.array)

                for j, ab in enumerate(aberration):
                    if j == 0:
                        continue
                    ztext[j-1].set_text("Z{:d} = {:5.3f}".format(j+1, ab))

                # Calculate simple estimate of ellipticity
                mom_fft = galsim.utilities.unweighted_moments(fft_img, origin=fft_img.true_center)
                mom_geom = galsim.utilities.unweighted_moments(geom_img,
                                                               origin=geom_img.true_center)
                e_fft = galsim.utilities.unweighted_shape(mom_fft)
                e_geom = galsim.utilities.unweighted_shape(mom_geom)

                Is = ("$M_x$={: 6.4f}, $M_y$={: 6.4f}, $M_{{xx}}$={:6.4f},"
                      " $M_{{yy}}$={:6.4f}, $M_{{xy}}$={: 6.4f}")
                M_fft.set_text(Is.format(mom_fft['Mx']*fft_img.scale,
                                         mom_fft['My']*fft_img.scale,
                                         mom_fft['Mxx']*fft_img.scale**2,
                                         mom_fft['Myy']*fft_img.scale**2,
                                         mom_fft['Mxy']*fft_img.scale**2))
                M_geom.set_text(Is.format(mom_geom['Mx']*geom_img.scale,
                                          mom_geom['My']*geom_img.scale,
                                          mom_geom['Mxx']*geom_img.scale**2,
                                          mom_geom['Myy']*geom_img.scale**2,
                                          mom_geom['Mxy']*geom_img.scale**2))
                etext_fft.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format(
                                   e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2))
                etext_geom.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format(
                                    e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2))


                fft_mom[i] = (mom_fft['Mx']*fft_img.scale, mom_fft['My']*fft_img.scale,
                              mom_fft['Mxx']*fft_img.scale**2, mom_fft['Myy']*fft_img.scale**2,
                              mom_fft['Mxy']*fft_img.scale**2,
                              e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2)

                geom_mom[i] = (mom_geom['Mx']*geom_img.scale, mom_geom['My']*geom_img.scale,
                              mom_geom['Mxx']*geom_img.scale**2, mom_geom['Myy']*geom_img.scale**2,
                              mom_geom['Mxy']*geom_img.scale**2,
                              e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2)

                writer.grab_frame(facecolor=fig.get_facecolor())

                bar.update()

    def symmetrize_axis(ax):
        xlim = ax.get_xlim()
        ylim = ax.get_ylim()
        lim = min(xlim[0], ylim[0]), max(xlim[1], ylim[1])
        ax.set_xlim(lim)
        ax.set_ylim(lim)
        ax.plot(lim, lim)

    # Centroid plot
    fig = Figure(figsize=(10, 6))
    FigureCanvasAgg(fig)
    axes = []
    axes.append(fig.add_subplot(1, 2, 1))
    axes.append(fig.add_subplot(1, 2, 2))
    axes[0].scatter(fft_mom[:, 0], geom_mom[:, 0])
    axes[1].scatter(fft_mom[:, 1], geom_mom[:, 1])
    axes[0].set_title("Mx")
    axes[1].set_title("My")
    for ax in axes:
        ax.set_xlabel("Fourier Optics")
        ax.set_ylabel("Geometric Optics")
        symmetrize_axis(ax)
    fig.tight_layout()
    fig.savefig(args.out+"centroid.png", dpi=300)

    # Second moment plot
    fig = Figure(figsize=(16, 6))
    FigureCanvasAgg(fig)
    axes = []
    axes.append(fig.add_subplot(1, 3, 1))
    axes.append(fig.add_subplot(1, 3, 2))
    axes.append(fig.add_subplot(1, 3, 3))
    axes[0].scatter(fft_mom[:, 2], geom_mom[:, 2])
    axes[1].scatter(fft_mom[:, 3], geom_mom[:, 3])
    axes[2].scatter(fft_mom[:, 4], geom_mom[:, 4])
    axes[0].set_title("Mxx")
    axes[1].set_title("Myy")
    axes[2].set_title("Mxy")
    for ax in axes:
        ax.set_xlabel("Fourier Optics")
        ax.set_ylabel("Geometric Optics")
        symmetrize_axis(ax)
    fig.tight_layout()
    fig.savefig(args.out+"2ndMoment.png", dpi=300)

    # Ellipticity plot
    fig = Figure(figsize=(16, 6))
    FigureCanvasAgg(fig)
    axes = []
    axes.append(fig.add_subplot(1, 3, 1))
    axes.append(fig.add_subplot(1, 3, 2))
    axes.append(fig.add_subplot(1, 3, 3))
    axes[0].scatter(fft_mom[:, 5], geom_mom[:, 5])
    axes[1].scatter(fft_mom[:, 6], geom_mom[:, 6])
    axes[2].scatter(fft_mom[:, 7], geom_mom[:, 7])
    axes[0].set_title("e1")
    axes[1].set_title("e2")
    axes[2].set_title("rsqr")
    for ax in axes:
        ax.set_xlabel("Fourier Optics")
        ax.set_ylabel("Geometric Optics")
        symmetrize_axis(ax)
    fig.tight_layout()
    fig.savefig(args.out+"ellipticity.png", dpi=300)
예제 #24
0
    def __init__(self,
                 airmass,
                 rawSeeing,
                 band,
                 rng,
                 t0=0.0,
                 exptime=30.0,
                 kcrit=0.2,
                 gaussianFWHM=0.3,
                 screen_size=819.2,
                 screen_scale=0.1,
                 doOpt=True,
                 logger=None,
                 nproc=None):
        self.airmass = airmass
        self.rawSeeing = rawSeeing

        self.wlen_eff = dict(u=365.49,
                             g=480.03,
                             r=622.20,
                             i=754.06,
                             z=868.21,
                             y=991.66)[band]
        # wlen_eff is from Table 2 of LSE-40 (y=y2)

        self.targetFWHM = rawSeeing * airmass**0.6 * (self.wlen_eff /
                                                      500)**(-0.3)

        self.rng = rng
        self.t0 = t0
        self.exptime = exptime
        self.screen_size = screen_size
        self.screen_scale = screen_scale
        self.logger = logger

        ctx = multiprocessing.get_context('fork')
        self.atm = galsim.Atmosphere(mp_context=ctx, **self._getAtmKwargs())
        self.aper = galsim.Aperture(diam=8.36,
                                    obscuration=0.61,
                                    lam=self.wlen_eff,
                                    screen_list=self.atm)

        # Instantiate screens now instead of delaying until after multiprocessing
        # has started.
        r0_500 = self.atm.r0_500_effective
        r0 = r0_500 * (self.wlen_eff / 500.0)**(6. / 5)
        kmax = kcrit / r0
        if logger:
            logger.warning("Building atmosphere")

        if nproc is None:
            nproc = len(self.atm)

        if nproc == 1:
            self.atm.instantiate(kmax=kmax, check='phot')
        else:
            if logger:
                logger.warning(
                    f"Using {nproc} processes to build the phase screens")
            with ctx.Pool(
                    nproc,
                    initializer=galsim.phase_screens.initWorker,
                    initargs=galsim.phase_screens.initWorkerArgs()) as pool:
                self.atm.instantiate(pool=pool, kmax=kmax, check='phot')

        if logger:
            logger.info("Finished building atmosphere")

        if doOpt:
            self.atm.append(OptWF(rng, self.wlen_eff))

        if logger and gaussianFWHM > 0:
            logger.debug(
                "Convolving in Gaussian with FWHM = {}".format(gaussianFWHM))
        self.gaussianFWHM = gaussianFWHM
예제 #25
0
def make_plot(args):
    # Initiate some GalSim random number generators.
    rng = galsim.BaseDeviate(args.seed)
    u = galsim.UniformDeviate(rng)

    # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series
    # of 2D turbulent screens.  The galsim.Atmosphere() helper function is useful for constructing
    # this screen list.

    # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower
    # layers consistent with direct measurements.  The specific values we use are from SCIDAR
    # measurements on Cerro Pachon as part of the 1998 Gemini site selection process
    # (Ellerbroek 2002, JOSA Vol 19 No 9).

    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights,
                                           interpolant='linear')

    # Use given number of uniformly spaced altitudes
    alts = np.max(Ellerbroek_alts)*np.arange(args.nlayers)/(args.nlayers-1)
    weights = Ellerbroek_interp(alts)  # interpolate the weights
    weights /= sum(weights)  # and renormalize

    # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried
    # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that
    # the size of each screen is actually made infinite by "wrapping" the edges of the screen.)  The
    # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of
    # parameters for the different layers.

    spd = []  # Wind speed in m/s
    dirn = [] # Wind direction in radians
    r0_500 = [] # Fried parameter in m at a wavelength of 500 nm.
    for i in range(args.nlayers):
        spd.append(u()*args.max_speed)  # Use a random speed between 0 and max_speed
        dirn.append(u()*360*galsim.degrees)  # And an isotropically distributed wind direction.
        # The turbulence strength of each layer is specified by through its Fried parameter r0_500,
        # which can be thought of as the diameter of a telescope for which atmospheric turbulence
        # and unaberrated diffraction contribute equally to image resolution (at a wavelength of
        # 500nm).  The weights above are for the refractive index structure function (similar to a
        # variance or covariance), however, so we need to use an appropriate scaling relation to
        # distribute the input "net" Fried parameter into a Fried parameter for each layer.  For
        # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5):
        r0_500.append(args.r0_500*weights[i]**(-3./5))
        print("Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, "
              "and r0_500 {:5.3f} m."
              .format(alts[i], spd[i]*dirn[i].cos(), spd[i]*dirn[i].sin(), r0_500[i]))

    # Apply fudge factor
    r0_500 = [r*args.turb_factor**(-3./5) for r in r0_500]

    # Make sure to use a consistent seed for the atmosphere when varying kcrit
    # Additionally, we set the screen size and scale.
    atmRng = galsim.BaseDeviate(args.seed+1)
    print("Inflating atmosphere")
    fftAtm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0,
                               speed=spd, direction=dirn, altitude=alts, rng=atmRng,
                               screen_size=args.screen_size, screen_scale=args.screen_scale)
    with ProgressBar(args.nlayers) as bar:
        fftAtm.instantiate(_bar=bar)
    print(fftAtm[0].screen_scale, fftAtm[0].screen_size)
    print(fftAtm[0]._tab2d.f.shape)
    # `atm` is now an instance of a galsim.PhaseScreenList object.

    # Construct an Aperture object for computing the PSF.  The Aperture object describes the
    # illumination pattern of the telescope pupil, and chooses good sampling size and resolution
    # for representing this pattern as an array.
    aper = galsim.Aperture(diam=args.diam, lam=args.lam, obscuration=args.obscuration,
                           screen_list=fftAtm, pad_factor=args.pad_factor,
                           oversampling=args.oversampling)

    print("Drawing with Fourier optics")
    with ProgressBar(args.exptime/args.time_step) as bar:
        fftPSF = fftAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime,
                                time_step=args.time_step, _bar=bar)
        fftImg = fftPSF.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)

    fftMom = galsim.hsm.FindAdaptiveMom(fftImg)

    vk = galsim.Convolve(
        galsim.VonKarman(lam=args.lam, r0=args.r0_500*(args.lam/500.0)**(6./5), L0=args.L0),
        galsim.Airy(lam=args.lam, diam=args.diam, obscuration=args.obscuration)
    )
    vkImg = vk.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)
    vkMom = galsim.hsm.FindAdaptiveMom(vkImg)

    # Start output at this point
    fig, axes = plt.subplots(nrows=5, ncols=4, figsize=(8, 8))
    FigureCanvasAgg(fig)
    for ax in axes.ravel():
        ax.set_xticks([])
        ax.set_yticks([])

    kcrits = np.logspace(np.log10(args.kmin), np.log10(args.kmax), 4)
    r0 = args.r0_500*(args.lam/500.0)**(6./5)
    for icol, kcrit in enumerate(kcrits):
        # reset atmRng
        atmRng = galsim.BaseDeviate(args.seed+1)
        print("Inflating atmosphere with kcrit={}".format(kcrit))
        atm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0,
                                speed=spd, direction=dirn, altitude=alts, rng=atmRng,
                                screen_size=args.screen_size, screen_scale=args.screen_scale)
        with ProgressBar(args.nlayers) as bar:
            atm.instantiate(kmax=kcrit/r0, _bar=bar)
        kick1 = atm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime,
                            time_step=args.time_step, second_kick=False)
        r0 = args.r0_500*(args.lam/500)**(6./5)
        kick2 = galsim.SecondKick(lam=args.lam, r0=r0, diam=args.diam, obscuration=args.obscuration,
                                  kcrit=kcrit)
        img1 = kick1.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot',
                               n_photons=args.nphot)
        try:
            mom1 = galsim.hsm.FindAdaptiveMom(img1)
        except RuntimeError:
            mom1 = None
        img2 = kick2.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot',
                               n_photons=args.nphot)
        try:
            mom2 = galsim.hsm.FindAdaptiveMom(img2)
        except RuntimeError:
            mom2 = None

        geom = galsim.Convolve(kick1, kick2)
        geomImg = geom.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot',
                                 n_photons=args.nphot)
        try:
            geomMom = galsim.hsm.FindAdaptiveMom(geomImg)
        except RuntimeError:
            geomMom = None

        axes[0,icol].imshow(fftImg.array)
        axes[0,icol].text(0.5, 0.9, "{:6.3f}".format(fftMom.moments_sigma),
                          transform=axes[0,icol].transAxes, color='w')
        axes[1,icol].imshow(img1.array)
        if mom1:
            axes[1,icol].text(0.5, 0.9, "{:6.3f}".format(mom1.moments_sigma),
                              transform=axes[1,icol].transAxes, color='w')
        axes[2,icol].imshow(img2.array)
        if mom2:
            axes[2,icol].text(0.5, 0.9, "{:6.3f}".format(mom2.moments_sigma),
                              transform=axes[2,icol].transAxes, color='w')
        axes[3,icol].imshow(geomImg.array)
        if geomMom:
            axes[3,icol].text(0.5, 0.9, "{:6.3f}".format(geomMom.moments_sigma),
                              transform=axes[3,icol].transAxes, color='w')

        axes[4,icol].imshow(vkImg.array)
        axes[4,icol].text(0.5, 0.9, "{:6.3f}".format(vkMom.moments_sigma),
                          transform=axes[4,icol].transAxes, color='w')


        axes[0,icol].set_title("{:6.3f}".format(kcrit))


    axes[0, 0].set_ylabel("DFT")
    axes[1, 0].set_ylabel("1st kick")
    axes[2, 0].set_ylabel("2nd kick")
    axes[3, 0].set_ylabel("Geom")
    axes[4, 0].set_ylabel("Von Karman")

    fig.tight_layout()

    dirname, filename = os.path.split(args.outfile)
    if not os.path.exists(dirname):
        os.mkdir(dirname)
    fig.savefig(args.outfile)
예제 #26
0
def make_plot(args):
    # Initiate some GalSim random number generators.
    rng = galsim.BaseDeviate(args.seed)
    u = galsim.UniformDeviate(rng)

    # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series
    # of 2D turbulent screens.  The galsim.Atmosphere() helper function is useful for constructing
    # this screen list.

    # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower
    # layers consistent with direct measurements.  The specific values we use are from SCIDAR
    # measurements on Cerro Pachon as part of the 1998 Gemini site selection process
    # (Ellerbroek 2002, JOSA Vol 19 No 9).

    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts,
                                           Ellerbroek_weights,
                                           interpolant='linear')

    # Use given number of uniformly spaced altitudes
    alts = np.max(Ellerbroek_alts) * np.arange(
        args.nlayers) / (args.nlayers - 1)
    weights = Ellerbroek_interp(alts)  # interpolate the weights
    weights /= sum(weights)  # and renormalize

    # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried
    # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that
    # the size of each screen is actually made infinite by "wrapping" the edges of the screen.)  The
    # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of
    # parameters for the different layers.

    spd = []  # Wind speed in m/s
    dirn = []  # Wind direction in radians
    r0_500 = []  # Fried parameter in m at a wavelength of 500 nm.
    for i in range(args.nlayers):
        spd.append(
            u() * args.max_speed)  # Use a random speed between 0 and max_speed
        dirn.append(
            u() * 360 *
            galsim.degrees)  # And an isotropically distributed wind direction.
        # The turbulence strength of each layer is specified by through its Fried parameter r0_500,
        # which can be thought of as the diameter of a telescope for which atmospheric turbulence
        # and unaberrated diffraction contribute equally to image resolution (at a wavelength of
        # 500nm).  The weights above are for the refractive index structure function (similar to a
        # variance or covariance), however, so we need to use an appropriate scaling relation to
        # distribute the input "net" Fried parameter into a Fried parameter for each layer.  For
        # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5):
        r0_500.append(args.r0_500 * weights[i]**(-3. / 5))
        print(
            "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, "
            "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(),
                                           spd[i] * dirn[i].sin(), r0_500[i]))

    # Generate atmosphere, set the initial screen size and scale.
    atmRng = galsim.BaseDeviate(args.seed + 1)
    fineAtm = galsim.Atmosphere(r0_500=r0_500,
                                L0=args.L0,
                                speed=spd,
                                direction=dirn,
                                altitude=alts,
                                rng=atmRng,
                                screen_size=args.screen_size,
                                screen_scale=args.screen_scale)
    with ProgressBar(args.nlayers) as bar:
        fineAtm.instantiate(_bar=bar)
    # `fineAtm` is now an instance of a galsim.PhaseScreenList object.

    # Construct an Aperture object for computing the PSF.  The Aperture object describes the
    # illumination pattern of the telescope pupil, and chooses good sampling size and resolution
    # for representing this pattern as an array.
    aper = galsim.Aperture(diam=args.diam,
                           lam=args.lam,
                           obscuration=args.obscuration,
                           screen_list=fineAtm,
                           pad_factor=args.pad_factor,
                           oversampling=args.oversampling)
    print(repr(aper))

    # Start output
    fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 7))
    FigureCanvasAgg(fig)
    for ax in axes.ravel():
        ax.set_xticks([])
        ax.set_yticks([])

    # Coarse
    print("Drawing with Fourier optics")
    with ProgressBar(args.exptime / args.time_step) as bar:
        psf = fineAtm.makePSF(lam=args.lam,
                              aper=aper,
                              exptime=args.exptime,
                              time_step=args.time_step,
                              _bar=bar)
        img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)

    try:
        mom = galsim.hsm.FindAdaptiveMom(img)
    except RuntimeError:
        mom = None

    axes[0, 0].imshow(img.array)
    axes[0, 0].set_title("{}".format(fineAtm[0].screen_scale))
    if mom is not None:
        axes[0, 0].text(0.5,
                        0.9,
                        "{:6.3f}".format(mom.moments_sigma),
                        transform=axes[0, 0].transAxes,
                        color='w')

    # Factor of 2
    shrunkenAtm = shrink_atm(fineAtm, 2)
    print("Drawing with shrink scale 2")
    with ProgressBar(args.exptime / args.time_step) as bar:
        psf = shrunkenAtm.makePSF(lam=args.lam,
                                  aper=aper,
                                  exptime=args.exptime,
                                  time_step=args.time_step,
                                  _bar=bar)
        img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)
    try:
        mom = galsim.hsm.FindAdaptiveMom(img)
    except RuntimeError:
        mom = None

    axes[0, 1].imshow(img.array)
    axes[0, 1].set_title("{}".format(shrunkenAtm[0].screen_scale))
    if mom is not None:
        axes[0, 1].text(0.5,
                        0.9,
                        "{:6.3f}".format(mom.moments_sigma),
                        transform=axes[0, 1].transAxes,
                        color='w')

    # Factor of 4
    shrunkenAtm = shrink_atm(fineAtm, 4)
    print("Drawing with shrink scale 4")
    with ProgressBar(args.exptime / args.time_step) as bar:
        psf = shrunkenAtm.makePSF(lam=args.lam,
                                  aper=aper,
                                  exptime=args.exptime,
                                  time_step=args.time_step,
                                  _bar=bar)
        img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)
    try:
        mom = galsim.hsm.FindAdaptiveMom(img)
    except RuntimeError:
        mom = None

    axes[1, 0].imshow(img.array)
    axes[1, 0].set_title("{}".format(shrunkenAtm[0].screen_scale))
    if mom is not None:
        axes[1, 0].text(0.5,
                        0.9,
                        "{:6.3f}".format(mom.moments_sigma),
                        transform=axes[1, 0].transAxes,
                        color='w')

    # Factor of 8
    shrunkenAtm = shrink_atm(fineAtm, 8)
    print("Drawing with shrink scale 8")
    with ProgressBar(args.exptime / args.time_step) as bar:
        psf = shrunkenAtm.makePSF(lam=args.lam,
                                  aper=aper,
                                  exptime=args.exptime,
                                  time_step=args.time_step,
                                  _bar=bar)
        img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale)
    try:
        mom = galsim.hsm.FindAdaptiveMom(img)
    except RuntimeError:
        mom = None

    axes[1, 1].imshow(img.array)
    axes[1, 1].set_title("{}".format(shrunkenAtm[0].screen_scale))
    if mom is not None:
        axes[1, 1].text(0.5,
                        0.9,
                        "{:6.3f}".format(mom.moments_sigma),
                        transform=axes[1, 1].transAxes,
                        color='w')

    fig.tight_layout()

    dirname, filename = os.path.split(args.outfile)
    if not os.path.exists(dirname):
        os.mkdir(dirname)
    fig.savefig(args.outfile)
예제 #27
0
def test_phase_gradient_shoot():
    # Make the atmosphere
    seed = 12345
    r0_500 = 0.2  # m
    nlayers = 6
    screen_size = 102.4  # m
    screen_scale = 0.1  # m
    max_speed = 20  # m/s

    rng = galsim.BaseDeviate(seed)
    u = galsim.UniformDeviate(rng)

    # Use atmospheric weights from 1998 Gemini site selection process as something reasonably
    # realistic.  (Ellerbroek 2002, JOSA Vol 19 No 9).
    Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
    Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
    Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts,
                                           Ellerbroek_weights,
                                           interpolant='linear')
    alts = np.max(Ellerbroek_alts) * np.arange(nlayers) / (nlayers - 1)
    weights = Ellerbroek_interp(alts)
    weights /= sum(weights)

    spd = []  # Wind speed in m/s
    dirn = []  # Wind direction in radians
    r0_500s = []  # Fried parameter in m at a wavelength of 500 nm.
    for i in range(nlayers):
        spd.append(u() * max_speed)
        dirn.append(u() * 360 * galsim.degrees)
        r0_500s.append(r0_500 * weights[i]**(-3. / 5))
    atm = galsim.Atmosphere(r0_500=r0_500,
                            speed=spd,
                            direction=dirn,
                            altitude=alts,
                            rng=rng,
                            screen_size=screen_size,
                            screen_scale=screen_scale)

    lam = 500.0
    diam = 4.0
    pad_factor = 1.0
    oversampling = 1.0

    aper = galsim.Aperture(diam=diam,
                           lam=lam,
                           screen_list=atm,
                           pad_factor=pad_factor,
                           oversampling=oversampling)

    xs = np.empty((10, ), dtype=float)
    ys = np.empty((10, ), dtype=float)
    u.generate(xs)
    u.generate(ys)
    thetas = [(x * galsim.degrees, y * galsim.degrees) for x, y in zip(xs, ys)]

    if __name__ == '__main__':
        exptime = 15.0
        centroid_tolerance = 0.05
        second_moment_tolerance = 0.5
    else:
        exptime = 0.2
        centroid_tolerance = 0.2
        second_moment_tolerance = 1.5

    psfs = [
        atm.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper)
        for th in thetas
    ]
    shoot_moments = []
    fft_moments = []

    # At the moment, Ixx and Iyy (but not Ixy) are systematically smaller in phase gradient shooting
    # mode than in FFT mode.  For now, I'm willing to accept this, but we should revisit it once we
    # get the "second kick" approximation implemented.
    offset = 0.5

    for psf in psfs:
        im_shoot = psf.drawImage(nx=48,
                                 ny=48,
                                 scale=0.05,
                                 method='phot',
                                 n_photons=100000,
                                 rng=rng)
        im_fft = psf.drawImage(nx=48, ny=48, scale=0.05)

        shoot_moment = galsim.utilities.unweighted_moments(im_shoot)
        fft_moment = galsim.utilities.unweighted_moments(im_fft)

        for key in ['Mx', 'My']:
            np.testing.assert_allclose(
                shoot_moment[key],
                fft_moment[key],
                rtol=0,
                atol=centroid_tolerance,
                err_msg='Phase gradient centroid {0} not close to fft centroid'
                .format(key))

        for key in ['Mxx', 'Myy']:
            np.testing.assert_allclose(
                shoot_moment[key] + offset,
                fft_moment[key],
                rtol=0,
                atol=second_moment_tolerance,
                err_msg='Phase gradient second moment {} not close to fft moment'
                .format(key))

        np.testing.assert_allclose(
            shoot_moment['Mxy'],
            fft_moment['Mxy'],
            rtol=0,
            atol=second_moment_tolerance,
            err_msg='Phase gradient second moment Mxy not close to fft moment')

        shoot_moments.append(shoot_moment)
        fft_moments.append(fft_moment)

    # Verify that shoot with rng=None runs
    psf.shoot(100, rng=None)

    # Constraints on the ensemble should be tighter than for individual PSFs.
    mean_shoot_moment = {}
    mean_fft_moment = {}
    for k in shoot_moments[0]:
        mean_shoot_moment[k] = np.mean([sm[k] for sm in shoot_moments])
        mean_fft_moment[k] = np.mean([fm[k] for fm in fft_moments])

    for key in ['Mx', 'My']:
        np.testing.assert_allclose(
            mean_shoot_moment[key],
            mean_fft_moment[key],
            rtol=0,
            atol=centroid_tolerance,
            err_msg=
            'Mean phase gradient centroid {0} not close to mean fft centroid'.
            format(key))

    for key in ['Mxx', 'Myy']:
        np.testing.assert_allclose(
            mean_shoot_moment[key] + offset,
            mean_fft_moment[key],
            rtol=0,
            atol=second_moment_tolerance,
            err_msg=
            'Mean phase gradient second moment {} not close to mean fft moment'
            .format(key))

    np.testing.assert_allclose(
        mean_shoot_moment['Mxy'],
        mean_fft_moment['Mxy'],
        rtol=0,
        atol=second_moment_tolerance,
        err_msg=
        'Mean phase gradient second moment Mxy not close to mean fft moment')
예제 #28
0
def test_ne():
    """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals."""
    import copy
    pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file))

    # Test galsim.Aperture __ne__
    objs = [galsim.Aperture(diam=1.0),
            galsim.Aperture(diam=1.1),
            galsim.Aperture(diam=1.0, oversampling=1.5),
            galsim.Aperture(diam=1.0, pad_factor=1.5),
            galsim.Aperture(diam=1.0, circular_pupil=False),
            galsim.Aperture(diam=1.0, obscuration=0.3),
            galsim.Aperture(diam=1.0, nstruts=3),
            galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2),
            galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees),
            galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im),
            galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im,
                            pupil_angle=10.0*galsim.degrees)]
    all_obj_diff(objs)

    # Test AtmosphericScreen __ne__
    rng = galsim.BaseDeviate(1)
    objs = [galsim.AtmosphericScreen(10.0, rng=rng),
            galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0),  # advance this one below
            galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999),
            galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0),
            galsim.AtmosphericScreen(10.0, rng=rng, time_step=0.1),
            galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1),
            galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0),
            galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0),
            ]
    objs[2].advance()
    all_obj_diff(objs)

    # Test OpticalScreen __ne__
    objs = [galsim.OpticalScreen(),
            galsim.OpticalScreen(tip=1.0),
            galsim.OpticalScreen(tilt=1.0),
            galsim.OpticalScreen(defocus=1.0),
            galsim.OpticalScreen(astig1=1.0),
            galsim.OpticalScreen(astig2=1.0),
            galsim.OpticalScreen(coma1=1.0),
            galsim.OpticalScreen(coma2=1.0),
            galsim.OpticalScreen(trefoil1=1.0),
            galsim.OpticalScreen(trefoil2=1.0),
            galsim.OpticalScreen(spher=1.0),
            galsim.OpticalScreen(spher=1.0, lam_0=100.0),
            galsim.OpticalScreen(aberrations=[0,0,1.1]), # tip=1.1
            ]
    all_obj_diff(objs)

    # Test PhaseScreenList __ne__
    atm = galsim.Atmosphere(10.0, vx=1.0)
    objs = [galsim.PhaseScreenList(atm),
            galsim.PhaseScreenList(copy.deepcopy(atm)),  # advance down below
            galsim.PhaseScreenList(objs),  # Reuse list of OpticalScreens above
            galsim.PhaseScreenList(objs[0:2])]
    objs[1].advance()
    all_obj_diff(objs)

    # Test PhaseScreenPSF __ne__
    objs[0].reset()
    psl = galsim.PhaseScreenList(atm)
    objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0),
            galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)] # advanced so differs
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)]
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)]
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)]
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')]
    stepk = objs[0].stepK()
    maxk = objs[0].maxK()
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)]
    psl.reset()
    objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)]
    all_obj_diff(objs)