Ejemplo n.º 1
0
def test_airy_radii():
    """Test Airy half light radius and FWHM correctly set and match image.
    """
    import math
    # Test constructor using lam_over_diam: (only option for Airy)
    test_gal = galsim.Airy(lam_over_diam= 1./0.8, flux=1.)
    # test half-light-radius getter
    got_hlr = test_gal.half_light_radius
    hlr_sum = radial_integrate(test_gal, 0., got_hlr)
    print('hlr_sum = ',hlr_sum)
    np.testing.assert_almost_equal(
            hlr_sum, 0.5, decimal=4,
            err_msg="Error in Airy half-light radius")

    # test FWHM getter
    center = test_gal.xValue(galsim.PositionD(0,0))
    got_fwhm = test_gal.fwhm
    ratio = test_gal.xValue(galsim.PositionD(0.5 * got_fwhm,0)) / center
    print('fwhm ratio = ',ratio)
    np.testing.assert_almost_equal(
            ratio, 0.5, decimal=4,
            err_msg="Error in fwhm for Airy.")

    # Check that the getters don't work after modifying the original.
    test_gal_shear = test_gal.shear(g1=0.3, g2=0.1)
    assert_raises(AttributeError, getattr, test_gal_shear, "fwhm")
    assert_raises(AttributeError, getattr, test_gal_shear, "half_light_radius")
    assert_raises(AttributeError, getattr, test_gal_shear, "lam_over_diam")

    # hlr and fwhm not implemented for obscuration != 0
    airy2 = galsim.Airy(lam_over_diam= 1./0.8, flux=1., obscuration=0.2)
    with assert_raises(galsim.GalSimNotImplementedError):
        airy2.half_light_radius
    with assert_raises(galsim.GalSimNotImplementedError):
        airy2.fwhm
Ejemplo n.º 2
0
def test_airy_shoot():
    """Test Airy with photon shooting.  Particularly the flux of the final image.
    """
    # Airy patterns have *very* extended wings, so make this really small and the image really
    # big to make sure we capture all the photons.
    rng = galsim.BaseDeviate(1234)
    obj = galsim.Airy(lam_over_diam=0.01, flux=1.e4)
    im = galsim.Image(1000, 1000, scale=1)
    im.setCenter(0, 0)
    added_flux, photons = obj.drawPhot(im,
                                       poisson_flux=False,
                                       rng=rng.duplicate())
    print('obj.flux = ', obj.flux)
    print('added_flux = ', added_flux)
    print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max())
    print('image flux = ', im.array.sum())
    assert np.isclose(added_flux, obj.flux)
    assert np.isclose(im.array.sum(), obj.flux)
    photons2 = obj.makePhot(poisson_flux=False, rng=rng)
    assert photons2 == photons, "Airy makePhot not equivalent to drawPhot"

    obj = galsim.Airy(lam_over_diam=0.01, flux=1.e4, obscuration=0.4)
    added_flux, photons = obj.drawPhot(im,
                                       poisson_flux=False,
                                       rng=rng.duplicate())
    print('obj.flux = ', obj.flux)
    print('added_flux = ', added_flux)
    print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max())
    print('image flux = ', im.array.sum())
    assert np.isclose(added_flux, obj.flux)
    assert np.isclose(im.array.sum(), obj.flux)
    photons2 = obj.makePhot(poisson_flux=False, rng=rng)
    assert photons2 == photons, "Airy makePhot not equivalent to drawPhot"
Ejemplo n.º 3
0
def make_psf(rng, iprof=None, scale=None, g1=None, g2=None, flux=10000):

    np_rng = np.random.default_rng(rng.raw())

    # Pick from one of several plausible PSF profiles.
    psf_profs = [
        galsim.Gaussian(fwhm=1.),
        galsim.Moffat(beta=1.5, fwhm=1.),
        galsim.Moffat(beta=4.5, fwhm=1.),
        galsim.Kolmogorov(fwhm=1.),
        galsim.Airy(lam=700, diam=4),
        galsim.Airy(lam=1200, diam=6.5, obscuration=0.6),
        galsim.OpticalPSF(lam=700,
                          diam=4,
                          obscuration=0.6,
                          defocus=0.2,
                          coma1=0.2,
                          coma2=-0.2,
                          astig1=-0.1,
                          astig2=0.1),
        galsim.OpticalPSF(lam=900,
                          diam=2.1,
                          obscuration=0.2,
                          aberrations=[
                              0, 0, 0, 0, -0.1, 0.2, -0.15, -0.1, 0.15, 0.1,
                              0.15, -0.2
                          ]),
        galsim.OpticalPSF(lam=1200,
                          diam=6.5,
                          obscuration=0.3,
                          aberrations=[
                              0, 0, 0, 0, 0.2, 0.1, 0.15, -0.1, -0.15, -0.2,
                              0.1, 0.15
                          ]),
    ]
    # The last 5 need an atmospheric part, or else they don't much resemble the kinds of
    # PSF profiles we actually care about.
    psf_profs[-5:] = [
        galsim.Convolve(galsim.Kolmogorov(fwhm=0.6), p) for p in psf_profs[-5:]
    ]

    if iprof is None:
        psf = np_rng.choice(psf_profs)
    else:
        psf = psf_profs[iprof]

    # Choose a random size and shape within reasonable ranges.
    if g1 is None:
        g1 = np_rng.uniform(-0.03, 0.03)
    if g2 is None:
        g2 = np_rng.uniform(-0.03, 0.03)
    if scale is None:
        # Note: Don't go too small, since hsm fails more often for size close to pixel_scale.
        scale = np.exp(np_rng.uniform(-0.3, 1.0))

    psf = psf.dilate(scale).shear(g1=g1, g2=g2).withFlux(flux)

    return psf
Ejemplo n.º 4
0
def test_limiting_cases():
    """SecondKick has some two interesting limiting cases.
    A) When kcrit = 0, SecondKick = Convolve(Airy, VonKarman).
    B) When kcrit = inf, SecondKick = Airy
    Test these.
    """
    lam = 500.0
    r0 = 0.2
    diam = 8.36
    obscuration = 0.61

    # First kcrit=0
    sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=0.0)
    limiting_case = galsim.Convolve(
        galsim.VonKarman(lam, r0, L0=1.e8),
        galsim.Airy(lam=lam, diam=diam, obscuration=obscuration)
    )
    print(sk.stepk, sk.maxk)
    print(limiting_case.stepk, limiting_case.maxk)

    for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]:
        print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real)
        np.testing.assert_allclose(
            sk.kValue(0, k).real,
            limiting_case.kValue(0, k).real,
            rtol=1e-3,
            atol=1e-4)

    # Normally, one wouldn't use SecondKick.xValue, since it does a real-space convolution,
    # so it's slow.  But we do allow it, so test it here.
    import time
    t0 = time.time()
    xv_2k = sk.xValue(0,0)
    print("xValue(0,0) = ",xv_2k)
    t1 = time.time()
    # The VonKarman * Airy xValue is much slower still, so don't do that.
    # Instead compare it to the 'sb' image.
    xv_image = limiting_case.drawImage(nx=1,ny=1,method='sb',scale=0.1)(1,1)
    print('from image ',xv_image)
    t2 = time.time()
    print('t = ',t1-t0, t2-t1)
    np.testing.assert_almost_equal(xv_2k, xv_image, decimal=3)

    # kcrit=inf
    sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=np.inf)
    limiting_case = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration)

    for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]:
        print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real)
        np.testing.assert_allclose(
            sk.kValue(0, k).real,
            limiting_case.kValue(0, k).real,
            rtol=1e-3,
            atol=1e-4)
Ejemplo n.º 5
0
def test_airy_flux_scaling():
    """Test flux scaling for Airy.
    """
    # decimal point to go to for parameter value comparisons
    param_decimal = 12
    test_loD = 1.9
    test_obscuration = 0.32
    test_flux = 17.9

    # init with lam_over_diam and flux only (should be ok given last tests)
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj *= 2.
    np.testing.assert_almost_equal(
        obj.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __imul__.")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj /= 2.
    np.testing.assert_almost_equal(
        obj.flux, test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __idiv__.")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = obj * 2.
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (result).")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = 2. * obj
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (result).")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = obj / 2.
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (result).")
Ejemplo n.º 6
0
def test_airy_flux_scaling():
    """Test flux scaling for Airy.
    """
    import time
    t1 = time.time()
    # init with lam_over_r0 and flux only (should be ok given last tests)
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj *= 2.
    np.testing.assert_almost_equal(
        obj.getFlux(), test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __imul__.")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj /= 2.
    np.testing.assert_almost_equal(
        obj.getFlux(), test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __idiv__.")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = obj * 2.
    # First test that original obj is unharmed... (also tests that .copy() is working)
    np.testing.assert_almost_equal(
        obj.getFlux(), test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.getFlux(), test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (result).")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = 2. * obj
    # First test that original obj is unharmed... (also tests that .copy() is working)
    np.testing.assert_almost_equal(
        obj.getFlux(), test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.getFlux(), test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (result).")
    obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration)
    obj2 = obj / 2.
    # First test that original obj is unharmed... (also tests that .copy() is working)
    np.testing.assert_almost_equal(
        obj.getFlux(), test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.getFlux(), test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (result).")
    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 7
0
def test_OpticalPSF_vs_Airy_with_obs():
    """Compare the array view on an unaberrated OpticalPSF with obscuration to that of an Airy.
    """
    import time
    t1 = time.time()
    lod = 7.5  # lambda/D value: don't choose unity in case symmetry hides something
    obses = (0.1, 0.3, 0.5)  # central obscuration radius ratios
    nlook = 100  # size of array region at the centre of each image to compare
    image = galsim.ImageF(nlook, nlook)
    for obs in obses:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=obs, flux=1.)
        optics_test = galsim.OpticalPSF(lam_over_diam=lod,
                                        pad_factor=1,
                                        obscuration=obs,
                                        suppress_warning=True)
        airy_array = airy_test.draw(scale=1., image=image).array
        optics_array = optics_test.draw(scale=1., image=image).array
        np.testing.assert_array_almost_equal(
            optics_array,
            airy_array,
            decimal_dft,
            err_msg=
            "Unaberrated Optical with obscuration not quite equal to Airy")
    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Ejemplo n.º 8
0
def test_OpticalPSF_vs_Airy_with_obs():
    """Compare the array view on an unaberrated OpticalPSF with obscuration to that of an Airy.
    """
    lod = 7.5  # lambda/D value: don't choose unity in case symmetry hides something
    obses = (0.1, 0.3, 0.5)  # central obscuration radius ratios
    nlook = 100  # size of array region at the centre of each image to compare
    image = galsim.ImageF(nlook, nlook)
    for obs in obses:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=obs, flux=1.)
        optics_test = galsim.OpticalPSF(lam_over_diam=lod,
                                        pad_factor=1,
                                        obscuration=obs,
                                        suppress_warning=True)
        airy_array = airy_test.drawImage(scale=1.,
                                         image=image,
                                         method='no_pixel').array
        optics_array = optics_test.drawImage(scale=1.,
                                             image=image,
                                             method='no_pixel').array
        np.testing.assert_array_almost_equal(
            optics_array,
            airy_array,
            decimal_dft,
            err_msg=
            "Unaberrated Optical with obscuration not quite equal to Airy")
    do_pickle(
        optics_test,
        lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics_test)
Ejemplo n.º 9
0
def test_cosmos_fluxnorm():
    """Check for flux normalization properties of COSMOSCatalog class."""
    # Check that if we make a RealGalaxy catalog, and a COSMOSCatalog, and draw the real object, the
    # fluxes should match very well.  These correspond to 1s exposures.
    test_ind = 54
    rand_seed = 12345
    cat = galsim.COSMOSCatalog(
        file_name='real_galaxy_catalog_23.5_example.fits',
        dir=datapath,
        exclusion_level='none')
    rgc = galsim.RealGalaxyCatalog(
        file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath)
    final_psf = galsim.Airy(diam=1.2,
                            lam=800.)  # PSF twice as big as HST in F814W.
    gal1 = cat.makeGalaxy(test_ind,
                          gal_type='real',
                          rng=galsim.BaseDeviate(rand_seed))
    gal2 = galsim.RealGalaxy(rgc,
                             index=test_ind,
                             rng=galsim.BaseDeviate(rand_seed))
    gal1 = galsim.Convolve(gal1, final_psf)
    gal2 = galsim.Convolve(gal2, final_psf)
    im1 = gal1.drawImage(scale=0.05)
    im2 = gal2.drawImage(scale=0.05)

    # Then check that if we draw a parametric representation that is achromatic, that the flux
    # matches reasonably well (won't be exact because model isn't perfect).
    gal1_param = cat.makeGalaxy(test_ind,
                                gal_type='parametric',
                                chromatic=False)
    gal1_param_final = galsim.Convolve(gal1_param, final_psf)
    im1_param = gal1_param_final.drawImage(scale=0.05)

    # Then check the same for a chromatic parametric representation that is drawn into the same
    # band.
    bp_file = os.path.join(galsim.meta_data.share_dir, 'bandpasses',
                           'ACS_wfc_F814W.dat')
    bandpass = galsim.Bandpass(bp_file,
                               wave_type='nm').withZeropoint(25.94)  #34.19)
    gal1_chrom = cat.makeGalaxy(test_ind,
                                gal_type='parametric',
                                chromatic=True)
    gal1_chrom = galsim.Convolve(gal1_chrom, final_psf)
    im1_chrom = gal1_chrom.drawImage(bandpass, scale=0.05)

    ref_val = [im1.array.sum(), im1.array.sum(), im1.array.sum()]
    test_val = [im2.array.sum(), im1_param.array.sum(), im1_chrom.array.sum()]
    np.testing.assert_allclose(
        ref_val,
        test_val,
        rtol=0.1,
        err_msg='Flux normalization problem in COSMOS galaxies')

    # Finally, check that the original COSMOS info is stored properly after transformations, for
    # both Sersic galaxies (like galaxy 0 in the catalog) and the one that is gal1_param above.
    gal0_param = cat.makeGalaxy(0, gal_type='parametric', chromatic=False)
    assert hasattr(gal0_param.shear(g1=0.05).original, 'index'), \
        'Sersic galaxy does not retain index information after transformation'
    assert hasattr(gal1_param.shear(g1=0.05).original, 'index'), \
        'Bulge+disk galaxy does not retain index information after transformation'
Ejemplo n.º 10
0
def test_OpticalPSF_vs_Airy():
    """Compare the array view on an unaberrated OpticalPSF to that of an Airy.
    """
    lods = (
        4.e-7, 9., 16.4
    )  # lambda/D values: don't choose unity in case symmetry hides something
    nlook = 100
    image = galsim.ImageF(nlook, nlook)
    for lod in lods:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=0., flux=1.)
        #pad same as an Airy, natch!
        optics_test = galsim.OpticalPSF(lam_over_diam=lod,
                                        pad_factor=1,
                                        suppress_warning=True)
        airy_array = airy_test.drawImage(scale=.25 * lod,
                                         image=image,
                                         method='no_pixel').array
        optics_array = optics_test.drawImage(scale=.25 * lod,
                                             image=image,
                                             method='no_pixel').array
        np.testing.assert_array_almost_equal(
            optics_array,
            airy_array,
            decimal_dft,
            err_msg="Unaberrated Optical not quite equal to Airy")
Ejemplo n.º 11
0
def test_integer_shift_photon():
    """Test if applyShift works correctly for integer shifts using drawShoot method.
    """
    import time
    t1 = time.time()

    n_photons_low = 10
    seed = 10

    gal = galsim.Gaussian(sigma=test_sigma)
    pix = galsim.Pixel(1.)
    psf = galsim.Airy(lam_over_diam=test_hlr)

    # shift galaxy only
 
    final=galsim.Convolve([gal, psf, pix])
    img_center = galsim.ImageD(n_pix_x,n_pix_y)
    test_deviate = galsim.BaseDeviate(seed)
    final.drawShoot(img_center,scale=1,rng=test_deviate,n_photons=n_photons_low)

    gal.applyShift(dx=int_shift_x,dy=int_shift_y)
    final=galsim.Convolve([gal, psf, pix])
    img_shift = galsim.ImageD(n_pix_x,n_pix_y)
    test_deviate = galsim.BaseDeviate(seed)
    final.drawShoot(img_shift,scale=1,rng=test_deviate,n_photons=n_photons_low)
    
    sub_center = img_center.array[
        (n_pix_y - delta_sub) / 2 : (n_pix_y + delta_sub) / 2,
        (n_pix_x - delta_sub) / 2 : (n_pix_x + delta_sub) / 2]
    sub_shift = img_shift.array[
        (n_pix_y - delta_sub) / 2  + int_shift_y : (n_pix_y + delta_sub) / 2  + int_shift_y,
        (n_pix_x - delta_sub) / 2  + int_shift_x : (n_pix_x + delta_sub) / 2  + int_shift_x]


    np.testing.assert_array_almost_equal(
        sub_center, sub_shift, decimal=image_decimal_precise,
        err_msg="Integer shift failed for FFT rendered Gaussian GSObject with shifted Galaxy only")

    # shift PSF only

    gal = galsim.Gaussian(sigma=test_sigma)
    psf.applyShift(dx=int_shift_x,dy=int_shift_y)
    final=galsim.Convolve([gal, psf, pix])
    img_shift = galsim.ImageD(n_pix_x,n_pix_y)
    test_deviate = galsim.BaseDeviate(seed)
    final.drawShoot(img_shift,scale=1,rng=test_deviate,n_photons=n_photons_low)

    sub_center = img_center.array[
        (n_pix_y - delta_sub) / 2 : (n_pix_y + delta_sub) / 2,
        (n_pix_x - delta_sub) / 2 : (n_pix_x + delta_sub) / 2]
    sub_shift = img_shift.array[
        (n_pix_y - delta_sub) / 2  + int_shift_y : (n_pix_y + delta_sub) / 2  + int_shift_y,
        (n_pix_x - delta_sub) / 2  + int_shift_x : (n_pix_x + delta_sub) / 2  + int_shift_x]
    np.testing.assert_array_almost_equal(
        sub_center, sub_shift,  decimal=image_decimal_precise,
        err_msg="Integer shift failed for FFT rendered Gaussian GSObject with only PSF shifted ")

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 12
0
def test_integer_shift_fft():
    """Test if shift works correctly for integer shifts using drawImage method.
    """
    gal = galsim.Gaussian(sigma=test_sigma)
    psf = galsim.Airy(lam_over_diam=test_hlr)

    # shift galaxy only

    final = galsim.Convolve([gal, psf])
    img_center = galsim.ImageD(n_pix_x, n_pix_y)
    final.drawImage(img_center, scale=1)

    gal = gal.shift(dx=int_shift_x, dy=int_shift_y)
    final = galsim.Convolve([gal, psf])
    img_shift = galsim.ImageD(n_pix_x, n_pix_y)
    final.drawImage(img_shift, scale=1)

    sub_center = img_center.array[(n_pix_y - delta_sub) //
                                  2:(n_pix_y + delta_sub) // 2,
                                  (n_pix_x - delta_sub) //
                                  2:(n_pix_x + delta_sub) // 2]
    sub_shift = img_shift.array[(n_pix_y - delta_sub) // 2 +
                                int_shift_y:(n_pix_y + delta_sub) // 2 +
                                int_shift_y, (n_pix_x - delta_sub) // 2 +
                                int_shift_x:(n_pix_x + delta_sub) // 2 +
                                int_shift_x]

    np.testing.assert_array_almost_equal(
        sub_center,
        sub_shift,
        decimal=image_decimal_precise,
        err_msg=
        "Integer shift failed for FFT rendered Gaussian GSObject with shifted Galaxy only"
    )

    # shift PSF only

    gal = galsim.Gaussian(sigma=test_sigma)
    psf = psf.shift(dx=int_shift_x, dy=int_shift_y)
    final = galsim.Convolve([gal, psf])
    img_shift = galsim.ImageD(n_pix_x, n_pix_y)
    final.drawImage(img_shift, scale=1)

    sub_center = img_center.array[(n_pix_y - delta_sub) //
                                  2:(n_pix_y + delta_sub) // 2,
                                  (n_pix_x - delta_sub) //
                                  2:(n_pix_x + delta_sub) // 2]
    sub_shift = img_shift.array[(n_pix_y - delta_sub) // 2 +
                                int_shift_y:(n_pix_y + delta_sub) // 2 +
                                int_shift_y, (n_pix_x - delta_sub) // 2 +
                                int_shift_x:(n_pix_x + delta_sub) // 2 +
                                int_shift_x]
    np.testing.assert_array_almost_equal(
        sub_center,
        sub_shift,
        decimal=image_decimal_precise,
        err_msg=
        "Integer shift failed for FFT rendered Gaussian GSObject with only PSF shifted "
    )
Ejemplo n.º 13
0
 def get_optical_psf(self, wavelength):
     #Convert dimensionless lam/D to arcsec units.
     lam_over_diam_arcsec = ((wavelength / self.diameter) * u.rad).to(
         u.arcsec)
     # Airy requires floats as inputs, not numpy scalars.
     return galsim.Airy(lam_over_diam=float(lam_over_diam_arcsec.value),
                        obscuration=float(
                            np.sqrt(self.obscuration_area_fraction)))
Ejemplo n.º 14
0
def make_a_star(ud, wcs, psf, affine):
    # Choose a random RA, Dec around the sky_center.
    dec = center_dec + (ud() - 0.5) * image_ysize_arcsec * galsim.arcsec
    ra = center_ra + (
        ud() - 0.5) * image_xsize_arcsec / numpy.cos(dec) * galsim.arcsec
    world_pos = galsim.CelestialCoord(ra, dec)

    # We will need the image position as well, so use the wcs to get that
    image_pos = wcs.toImage(world_pos)

    # We also need this in the tangent plane, which we call "world coordinates" here,
    # This is still an x/y corrdinate
    uv_pos = affine.toWorld(image_pos)

    # Draw star flux at random; based on distribution of star fluxes in real images
    # Generate PSF at location of star, convolve simple Airy with the PSF to make a star

    flux_dist = galsim.DistDeviate(ud,
                                   function=lambda x: x**-1,
                                   x_min=799.2114,
                                   x_max=890493.9)
    """
    flux_dist = galsim.DistDeviate(ud, function = lambda x:x**-1.5,
                                       x_min = 3000,
                                       x_max = 30000)
    """
    star_flux = flux_dist()
    shining_star = galsim.Airy(lam=lam,
                               obscuration=0.380,
                               diam=tel_diam,
                               scale_unit=galsim.arcsec,
                               flux=star_flux)
    this_psf = psf.getPSF(image_pos)
    star = galsim.Convolve([shining_star, optics, this_psf])
    #star=galsim.Convolve([shining_star,this_psf])
    # Account for the fractional part of the position
    # cf. demo9.py for an explanation of this nominal position stuff.
    x_nominal = image_pos.x + 0.5
    y_nominal = image_pos.y + 0.5
    ix_nominal = int(math.floor(x_nominal + 0.5))
    iy_nominal = int(math.floor(y_nominal + 0.5))
    dx = x_nominal - ix_nominal
    dy = y_nominal - iy_nominal
    offset = galsim.PositionD(dx, dy)
    #star_stamp = star.drawImage(wcs=wcs.local(image_pos), offset=offset, method='no_pixel')
    star_stamp = star.drawImage(wcs=wcs.local(image_pos),
                                offset=offset)  #,method='no_pixel')

    # Recenter the stamp at the desired position:
    star_stamp.setCenter(ix_nominal, iy_nominal)

    star_truth = truth()
    star_truth.ra = ra.deg
    star_truth.dec = dec.deg
    star_truth.x = ix_nominal
    star_truth.y = iy_nominal

    return star_stamp, star_truth
def make_a_star(ud, this_im_wcs, psf, affine):
    # Choose a random RA, Dec around the sky_center.
    center_dec = this_im_wcs.center.dec
    center_ra = this_im_wcs.center.ra
    dec = center_dec + (ud() - 0.5) * image_ysize_arcsec * galsim.arcsec
    ra = center_ra + (
        ud() - 0.5) * image_xsize_arcsec / numpy.cos(dec) * galsim.arcsec

    world_pos = galsim.CelestialCoord(ra, dec)

    # We will need the image position as well, so use the wcs to get that
    image_pos = this_im_wcs.posToImage(world_pos)

    # We also need this in the tangent plane, which we call "world coordinates" here,
    # since the PowerSpectrum class is really defined on that plane, not in (ra,dec).
    # This is still an x/y corrdinate
    uv_pos = affine.toWorld(image_pos)

    # Draw star flux at random; based on distribution of star fluxes in real images
    # Generate PSF at location of star, convolve simple Airy with the PSF to make a star

    flux_dist = galsim.DistDeviate(
        ud,
        function=
        '/Users/jemcclea/Research/GalSim/examples/output/empirical_psfs/v2/stars_flux300_prob.txt'
    )  #,interpolant='floor')
    star_flux = (flux_dist()) * (exp_time / 300.)
    shining_star = galsim.Airy(lam=lam,
                               obscuration=0.3840,
                               diam=tel_diam,
                               scale_unit=galsim.arcsec,
                               flux=star_flux)
    this_psf = psf.getPSF(image_pos)
    star = galsim.Convolve([shining_star, this_psf])

    # Account for the fractional part of the position
    # cf. demo9.py for an explanation of this nominal position stuff.
    x_nominal = image_pos.x + 0.5
    y_nominal = image_pos.y + 0.5
    ix_nominal = int(math.floor(x_nominal + 0.5))
    iy_nominal = int(math.floor(y_nominal + 0.5))
    dx = x_nominal - ix_nominal
    dy = y_nominal - iy_nominal
    offset = galsim.PositionD(dx, dy)
    star_stamp = star.drawImage(wcs=this_im_wcs.local(image_pos),
                                offset=offset,
                                method='no_pixel')

    # Recenter the stamp at the desired position:
    star_stamp.setCenter(ix_nominal, iy_nominal)

    star_truth = truth()
    star_truth.ra = ra.deg
    star_truth.dec = dec.deg
    star_truth.x = ix_nominal
    star_truth.y = iy_nominal

    return star_stamp, star_truth
Ejemplo n.º 16
0
def test_cosmos_fluxnorm():
    """Check for flux normalization properties of COSMOSCatalog class."""
    import time
    t1 = time.time()

    # Check that if we make a RealGalaxy catalog, and a COSMOSCatalog, and draw the real object, the
    # fluxes should match very well.  These correspond to 1s exposures.
    test_ind = 54
    rand_seed = 12345
    cat = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_example.fits',
                               dir=datapath,
                               exclude_fail=False,
                               exclude_bad=False)
    rgc = galsim.RealGalaxyCatalog(
        file_name='real_galaxy_catalog_example.fits', dir=datapath)
    final_psf = galsim.Airy(diam=1.2,
                            lam=800.)  # PSF twice as big as HST in F814W.
    gal1 = cat.makeGalaxy(test_ind,
                          gal_type='real',
                          rng=galsim.BaseDeviate(rand_seed))
    gal2 = galsim.RealGalaxy(rgc,
                             index=test_ind,
                             rng=galsim.BaseDeviate(rand_seed))
    gal1 = galsim.Convolve(gal1, final_psf)
    gal2 = galsim.Convolve(gal2, final_psf)
    im1 = gal1.drawImage(scale=0.05)
    im2 = gal2.drawImage(scale=0.05)

    # Then check that if we draw a parametric representation that is achromatic, that the flux
    # matches reasonably well (won't be exact because model isn't perfect).
    gal1_param = cat.makeGalaxy(test_ind,
                                gal_type='parametric',
                                chromatic=False)
    gal1_param = galsim.Convolve(gal1_param, final_psf)
    im1_param = gal1_param.drawImage(scale=0.05)

    # Then check the same for a chromatic parametric representation that is drawn into the same
    # band.
    bp_file = os.path.join(galsim.meta_data.share_dir, 'wfc_F814W.dat.gz')
    bandpass = galsim.Bandpass(bp_file, wave_type='ang').thin().withZeropoint(
        25.94)  #34.19)
    gal1_chrom = cat.makeGalaxy(test_ind,
                                gal_type='parametric',
                                chromatic=True)
    gal1_chrom = galsim.Convolve(gal1_chrom, final_psf)
    im1_chrom = gal1_chrom.drawImage(bandpass, scale=0.05)

    ref_val = [im1.array.sum(), im1.array.sum(), im1.array.sum()]
    test_val = [im2.array.sum(), im1_param.array.sum(), im1_chrom.array.sum()]
    np.testing.assert_allclose(
        ref_val,
        test_val,
        rtol=0.1,
        err_msg='Flux normalization problem in COSMOS galaxies')

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Ejemplo n.º 17
0
def test_airy():
    """Test various ways to build a Airy
    """
    config = {
        'gal1' : { 'type' : 'Airy' , 'lam_over_diam' : 2 },
        'gal2' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'obscuration' : 0.3, 'flux' : 100 },
        'gal3' : { 'type' : 'Airy' , 'lam_over_diam' : 1.3, 'obscuration' : 0, 'flux' : 1.e6,
                   'ellip' : { 'type' : 'QBeta' , 'q' : 0.6, 'beta' : 0.39 * galsim.radians }
                 },
        'gal4' : { 'type' : 'Airy' , 'lam_over_diam' : 1, 'flux' : 50,
                   'dilate' : 3, 'ellip' : galsim.Shear(e1=0.3),
                   'rotate' : 12 * galsim.degrees,
                   'magnify' : 1.03, 'shear' : galsim.Shear(g1=0.03, g2=-0.05),
                   'shift' : { 'type' : 'XY', 'x' : 0.7, 'y' : -1.2 }
                 },
        'gal5' : { 'type' : 'Airy' , 'lam_over_diam' : 45,
                   'gsparams' : { 'xvalue_accuracy' : 1.e-2 }
                 },
        'gal6' : { 'type' : 'Airy' , 'lam' : 400., 'diam' : 4.0, 'scale_unit' : 'arcmin' }
    }

    gal1a = galsim.config.BuildGSObject(config, 'gal1')[0]
    gal1b = galsim.Airy(lam_over_diam = 2)
    gsobject_compare(gal1a, gal1b)

    gal2a = galsim.config.BuildGSObject(config, 'gal2')[0]
    gal2b = galsim.Airy(lam_over_diam = 0.4, obscuration = 0.3, flux = 100)
    gsobject_compare(gal2a, gal2b)

    gal3a = galsim.config.BuildGSObject(config, 'gal3')[0]
    gal3b = galsim.Airy(lam_over_diam = 1.3, flux = 1.e6)
    gal3b = gal3b.shear(q = 0.6, beta = 0.39 * galsim.radians)
    gsobject_compare(gal3a, gal3b)

    gal4a = galsim.config.BuildGSObject(config, 'gal4')[0]
    gal4b = galsim.Airy(lam_over_diam = 1, flux = 50)
    gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees).magnify(1.03)
    gal4b = gal4b.shear(g1 = 0.03, g2 = -0.05).shift(dx = 0.7, dy = -1.2)
    gsobject_compare(gal4a, gal4b)

    # The approximation from xvalue_accuracy here happens at the core, so you need a very
    # large size to notice.  (Which tells me this isn't that useful an approximation, but
    # so be it.)
    gal5a = galsim.config.BuildGSObject(config, 'gal5')[0]
    gsparams = galsim.GSParams(xvalue_accuracy=1.e-2)
    gal5b = galsim.Airy(lam_over_diam=45, gsparams=gsparams)
    gsobject_compare(gal5a, gal5b)

    gal6a = galsim.config.BuildGSObject(config, 'gal6')[0]
    gal6b = galsim.Airy(lam=400., diam=4., scale_unit=galsim.arcmin)
    gsobject_compare(gal6a, gal6b)

    try:
        # Make sure they don't match when using the default GSParams
        gal5c = galsim.Airy(lam_over_diam=45)
        np.testing.assert_raises(AssertionError,gsobject_compare, gal5a, gal5c)
    except ImportError:
        print('The assert_raises tests require nose')
Ejemplo n.º 18
0
def test_ne():
    """Test base.py GSObjects for not-equals."""
    # Define some universal gsps
    gsp = galsim.GSParams(maxk_threshold=1.1e-3, folding_threshold=5.1e-3)

    # Airy.  Params include lam_over_diam, lam, diam, obscuration, flux, and gsparams.
    # The following should all test unequal:
    gals = [galsim.Airy(lam_over_diam=1.0),
            galsim.Airy(lam_over_diam=1.1),
            galsim.Airy(lam=1.0, diam=1.2),
            galsim.Airy(lam=1.0, diam=1.2, scale_unit=galsim.arcmin),
            galsim.Airy(lam=1.0, diam=1.2, scale_unit='degrees'),
            galsim.Airy(lam=1.0, diam=1.0, obscuration=0.1),
            galsim.Airy(lam_over_diam=1.0, flux=1.1),
            galsim.Airy(lam_over_diam=1.0, gsparams=gsp)]
    all_obj_diff(gals)
Ejemplo n.º 19
0
    def _stepK(self, **kwargs):
        """Return an appropriate stepk for this phase screen.

        @param lam         Wavelength in nanometers.
        @param diam        Aperture diameter in meters.
        @param obscuration Fractional linear aperture obscuration. [default: 0.0]
        @param gsparams    An optional GSParams argument.  See the docstring for GSParams for
                           details. [default: None]
        @returns stepk in inverse arcsec.
        """
        lam = kwargs['lam']
        diam = kwargs['diam']
        obscuration = kwargs.get('obscuration', 0.0)
        gsparams = kwargs.get('gsparams', None)
        # Use an Airy for get appropriate stepk.
        obj = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration, gsparams=gsparams)
        return obj.stepk
Ejemplo n.º 20
0
    def __init__(self, rng, wavelength, gsparams=None):
        u = galsim.UniformDeviate(rng)
        # Fudge factor below comes from an attempt to force the PSF ellipticity distribution to
        # match more closely the targets in the SRD (not be too round).  (See the discussion at
        # https://github.com/LSSTDESC/DC2-production/issues/259).  Since the values in the
        # mock_deviations function currently rely on a small set of simulations (7), this was deemed
        # reasonable.
        deviationsFudgeFactor = 3.0
        self.deviations = deviationsFudgeFactor * mock_deviations(
            seed=int(u() * 2**31))
        self.oz = OpticalZernikes(self.deviations)
        self.dynamic = False
        self.reversible = True

        # Compute stepk once and store
        obj = galsim.Airy(lam=wavelength,
                          diam=8.36,
                          obscuration=0.61,
                          gsparams=gsparams)
        self.stepk = obj.stepk
Ejemplo n.º 21
0
def get_psf(Args):
    atmospheric_psf_fwhm = Args.zenith_psf_fwhm * Args.airmass**0.6
    if Args.atmospheric_psf_beta > 0:
        atmospheric_psf_model = galsim.Moffat(beta=Args.atmospheric_psf_beta,
                                              fwhm=atmospheric_psf_fwhm)
    else:
        atmospheric_psf_model = galsim.Kolmogorov(fwhm=atmospheric_psf_fwhm)
    lambda_over_diameter = 3600 * math.degrees(
        1e-10 * Args.central_wavelength / Args.mirror_diameter)
    area_ratio = Args.effective_area / (math.pi *
                                        (0.5 * Args.mirror_diameter)**2)
    obscuration_fraction = math.sqrt(1 - area_ratio)
    optical_psf_model = galsim.Airy(lam_over_diam=lambda_over_diameter,
                                    obscuration=obscuration_fraction)
    psf_model = galsim.Convolve(atmospheric_psf_model, optical_psf_model)
    psf_size_pixels = 2 * int(
        math.ceil(10 * atmospheric_psf_fwhm / Args.pixel_scale))
    psf_image = galsim.Image(psf_size_pixels,
                             psf_size_pixels,
                             scale=Args.pixel_scale)
    psf_model.drawImage(image=psf_image)
    return psf_image.array
Ejemplo n.º 22
0
def test_OpticalPSF_vs_Airy():
    """Compare the array view on an unaberrated OpticalPSF to that of an Airy.
    """
    import time
    t1 = time.time()
    lods = (
        4.e-7, 9., 16.4
    )  # lambda/D values: don't choose unity in case symmetry hides something
    nlook = 100
    image = galsim.ImageF(nlook, nlook)
    for lod in lods:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=0., flux=1.)
        optics_test = galsim.OpticalPSF(
            lam_over_diam=lod, pad_factor=1)  #pad same as an Airy, natch!
        airy_array = airy_test.draw(dx=.25 * lod, image=image).array
        optics_array = optics_test.draw(dx=.25 * lod, image=image).array
        np.testing.assert_array_almost_equal(
            optics_array,
            airy_array,
            decimal_dft,
            err_msg="Unaberrated Optical not quite equal to Airy")
    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Ejemplo n.º 23
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)
Ejemplo n.º 24
0
 def __init__(self, no_analysis=False, **args):
     if set(args.keys()) != set(Survey._parameter_names):
         raise RuntimeError('Missing or extra arguments provided to Survey constructor.')
     self.args = args
     self.__dict__.update(args)
     # Build our atmospheric PSF model.
     atmospheric_psf_fwhm = self.zenith_psf_fwhm*self.airmass**0.6
     if self.atmospheric_psf_beta > 0:
         atmospheric_psf_model = galsim.Moffat(
             beta = self.atmospheric_psf_beta, fwhm = atmospheric_psf_fwhm)
     else:
         atmospheric_psf_model = galsim.Kolmogorov(fwhm = atmospheric_psf_fwhm)
     # Shear the atmospheric PSF, if necessary. Note that GalSim uses g1,g2 for the
     # |g| = (a-b)/(a+b) ellipticity spinor and e1,e2 for |e| = (a^2-b^2)/(a^2+b^2).
     if self.atmospheric_psf_e1 != 0 or self.atmospheric_psf_e2 != 0:
         atmospheric_psf_model = atmospheric_psf_model.shear(
             g1 = self.atmospheric_psf_e1, g2 = self.atmospheric_psf_e2)
     # Combine with our optical PSF model, if any.
     if self.mirror_diameter > 0:
         lambda_over_diameter = 3600*math.degrees(
             1e-10*Survey._central_wavelength[self.filter_band]/self.mirror_diameter)
         area_ratio = self.effective_area/(math.pi*(0.5*self.mirror_diameter)**2)
         if area_ratio <= 0 or area_ratio > 1:
             raise RuntimeError('Incompatible effective-area and mirror-diameter values.')
         self.obscuration_fraction = math.sqrt(1 - area_ratio)
         optical_psf_model = galsim.Airy(lam_over_diam = lambda_over_diameter,
             obscuration = self.obscuration_fraction)
         self.psf_model = galsim.Convolve(atmospheric_psf_model,optical_psf_model)
     else:
         self.psf_model = atmospheric_psf_model
         self.obscuration_fraction = 0.
     # Draw a centered PSF image covering 10x the atmospheric PSF FWHM.
     psf_size_pixels = 2*int(math.ceil(10*atmospheric_psf_fwhm/self.pixel_scale))
     self.psf_image = galsim.Image(psf_size_pixels,psf_size_pixels,scale  = self.pixel_scale)
     self.psf_model.drawImage(image = self.psf_image)
     if not no_analysis:
         # Draw a (temporary) high-resolution (10x) image covering the same area.
         zoom = 10
         hires_psf_image = galsim.Image(zoom*psf_size_pixels,zoom*psf_size_pixels,scale = self.pixel_scale/zoom)
         self.psf_model.drawImage(image = hires_psf_image)
         # Calculate the unweighted second moments in arcsecs**2 of the hi-res PSF image.
         hires_sum = np.sum(hires_psf_image.array)
         hires_grid = (self.pixel_scale/zoom)*(np.arange(zoom*psf_size_pixels) - 0.5*zoom*psf_size_pixels + 0.5)
         hires_x,hires_y = np.meshgrid(hires_grid,hires_grid)
         psf_x = np.sum(hires_psf_image.array*hires_x)/hires_sum
         psf_y = np.sum(hires_psf_image.array*hires_x)/hires_sum
         hires_x -= psf_x
         hires_y -= psf_y
         psf_xx = np.sum(hires_psf_image.array*hires_x**2)/hires_sum
         psf_xy = np.sum(hires_psf_image.array*hires_x*hires_y)/hires_sum
         psf_yy = np.sum(hires_psf_image.array*hires_y**2)/hires_sum
         self.psf_second_moments = np.array(((psf_xx,psf_xy),(psf_xy,psf_yy)))
         # Calculate the corresponding PSF sizes |Q|**0.25 and (0.5*trQ)**0.5
         self.psf_sigma_m = np.power(np.linalg.det(self.psf_second_moments),0.25)
         self.psf_sigma_p = np.sqrt(0.5*np.trace(self.psf_second_moments))
         # Also calculate the PSF size as |Q|**0.25 using adaptive weighted second moments
         # of the non-hires PSF image.
         try:
             hsm_results = galsim.hsm.FindAdaptiveMom(self.psf_image)
             self.psf_size_hsm = hsm_results.moments_sigma*self.pixel_scale
         except RuntimeError as e:
             raise RuntimeError('Unable to calculate adaptive moments of PSF image.')
     # Calculate the mean sky background level in detected electrons per pixel.
     self.mean_sky_level = self.get_flux(self.sky_brightness)*self.pixel_scale**2
     # Create an empty image using (0,0) to index the lower-left corner pixel.
     self.image_bounds = galsim.BoundsI(0,self.image_width-1,0,self.image_height-1)
     self.image = galsim.Image(bounds = self.image_bounds,scale=self.pixel_scale,
         dtype = np.float32)
Ejemplo n.º 25
0
sersic_n = 6.
print 'Creating Sersic profile with n=', sersic_n, ' and hlr=', test_hlr
s = galsim.Sersic(sersic_n, half_light_radius=test_hlr)
print 'Checking radial integration of profile....'
hlr_sum = radial_integrate(s, 0., test_hlr, 1.e-4)
print 'Sum of profile to half-light-radius: ', hlr_sum

# make Sersic convolved with some ground-based PSF (Kolmogorov x optical PSF)
print "Testing sersic ground-based sim..."
ground_psf_fwhm = 0.7
ground_pix_scale = 0.2
space_psf_fwhm = 0.1
imsize = 512
n_photons = 1000000
psf = galsim.Kolmogorov(fwhm=ground_psf_fwhm)
opt_psf = galsim.Airy(space_psf_fwhm)
pix = galsim.Pixel(ground_pix_scale)
epsf = galsim.Convolve(psf, opt_psf, pix)
obj_epsf = galsim.Convolve(s, psf, opt_psf, pix)
obj_psf = galsim.Convolve(s, psf, opt_psf)
# compare photon-shot vs. Fourier draw image, directly and with moments
im_draw = galsim.ImageF(imsize, imsize)
im_shoot = galsim.ImageF(imsize, imsize)
im_epsf = galsim.ImageF(imsize, imsize)
im_draw = obj_epsf.draw(image=im_draw, dx=ground_pix_scale, wmult=4.)
im_shoot, _ = obj_psf.drawShoot(image=im_shoot,
                                dx=ground_pix_scale,
                                n_photons=n_photons)
im_epsf = epsf.draw(image=im_epsf, dx=ground_pix_scale)
res_draw = im_draw.FindAdaptiveMom()
res_shoot = im_shoot.FindAdaptiveMom()
Ejemplo n.º 26
0
def test_flip():
    """Test several ways to flip a profile
    """
    # The Shapelet profile has the advantage of being fast and not circularly symmetric, so
    # it is a good test of the actual code for doing the flips (in SBTransform).
    # But since the bug Rachel reported in #645 was actually in SBInterpolatedImage
    # (one calculation implicitly assumed dx > 0), it seems worthwhile to run through all the
    # classes to make sure we hit everything with negative steps for dx and dy.
    prof_list = [
        galsim.Shapelet(sigma=0.17, order=2,
                        bvec=[1.7, 0.01,0.03, 0.29, 0.33, -0.18]),
    ]
    if __name__ == "__main__":
        image_dir = './real_comparison_images'
        catalog_file = 'test_catalog.fits'
        rgc = galsim.RealGalaxyCatalog(catalog_file, dir=image_dir)
        # Some of these are slow, so only do the Shapelet test as part of the normal unit tests.
        prof_list += [
            galsim.Airy(lam_over_diam=0.17, flux=1.7),
            galsim.Airy(lam_over_diam=0.17, obscuration=0.2, flux=1.7),
            # Box gets rendered with real-space convolution.  The default accuracy isn't quite
            # enough to get the flip to match at 6 decimal places.
            galsim.Box(0.17, 0.23, flux=1.7,
                       gsparams=galsim.GSParams(realspace_relerr=1.e-6)),
            # Without being convolved by anything with a reasonable k cutoff, this needs
            # a very large fft.
            galsim.DeVaucouleurs(half_light_radius=0.17, flux=1.7),
            # I don't really understand why this needs a lower maxk_threshold to work, but
            # without it, the k-space tests fail.
            galsim.Exponential(scale_radius=0.17, flux=1.7,
                               gsparams=galsim.GSParams(maxk_threshold=1.e-4)),
            galsim.Gaussian(sigma=0.17, flux=1.7),
            galsim.Kolmogorov(fwhm=0.17, flux=1.7),
            galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7),
            galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7, trunc=0.82),
            galsim.OpticalPSF(lam_over_diam=0.17, obscuration=0.2, nstruts=6,
                              coma1=0.2, coma2=0.5, defocus=-0.1, flux=1.7),
            # Like with Box, we need to increase the real-space convolution accuracy.
            # This time lowering both relerr and abserr.
            galsim.Pixel(0.23, flux=1.7,
                         gsparams=galsim.GSParams(realspace_relerr=1.e-6,
                                                  realspace_abserr=1.e-8)),
            # Note: RealGalaxy should not be rendered directly because of the deconvolution.
            # Here we convolve it by a Gaussian that is slightly larger than the original PSF.
            galsim.Convolve([ galsim.RealGalaxy(rgc, index=0, flux=1.7),  # "Real" RealGalaxy
                              galsim.Gaussian(sigma=0.08) ]),
            galsim.Convolve([ galsim.RealGalaxy(rgc, index=1, flux=1.7),  # "Fake" RealGalaxy
                              galsim.Gaussian(sigma=0.08) ]),             # (cf. test_real.py)
            galsim.Spergel(nu=-0.19, half_light_radius=0.17, flux=1.7),
            galsim.Spergel(nu=0., half_light_radius=0.17, flux=1.7),
            galsim.Spergel(nu=0.8, half_light_radius=0.17, flux=1.7),
            galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7),
            galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7, trunc=0.82),
            # The shifts here caught a bug in how SBTransform handled the recentering.
            # Two of the shifts (0.125 and 0.375) lead back to 0.0 happening on an integer
            # index, which now works correctly.
            galsim.Sum([ galsim.Gaussian(sigma=0.17, flux=1.7).shift(-0.2,0.125),
                         galsim.Exponential(scale_radius=0.23, flux=3.1).shift(0.375,0.23)]),
            galsim.TopHat(0.23, flux=1.7),
            # Box and Pixel use real-space convolution.  Convolve with a Gaussian to get fft.
            galsim.Convolve([ galsim.Box(0.17, 0.23, flux=1.7).shift(-0.2,0.1),
                              galsim.Gaussian(sigma=0.09) ]),
            galsim.Convolve([ galsim.TopHat(0.17, flux=1.7).shift(-0.275,0.125),
                              galsim.Gaussian(sigma=0.09) ]),
            # Test something really crazy with several layers worth of transformations
            galsim.Convolve([
                galsim.Sum([
                    galsim.Gaussian(sigma=0.17, flux=1.7).shear(g1=0.1,g2=0.2).shift(2,3),
                    galsim.Kolmogorov(fwhm=0.33, flux=3.9).transform(0.31,0.19,-0.23,0.33) * 88.,
                    galsim.Box(0.11, 0.44, flux=4).rotate(33 * galsim.degrees) / 1.9
                ]).shift(-0.3,1),
                galsim.AutoConvolve(galsim.TopHat(0.5).shear(g1=0.3,g2=0)).rotate(3*galsim.degrees),
                (galsim.AutoCorrelate(galsim.Box(0.2, 0.3)) * 11).shift(3,2).shift(2,-3) * 0.31
            ]).shift(0,0).transform(0,-1,-1,0).shift(-1,1)
        ]

    s = galsim.Shear(g1=0.11, g2=-0.21)
    s1 = galsim.Shear(g1=0.11, g2=0.21)  # Appropriate for the flips around x and y axes
    s2 = galsim.Shear(g1=-0.11, g2=-0.21)  # Appropriate for the flip around x=y

    # Also use shears with just a g1 to get dx != dy, but dxy, dyx = 0.
    q = galsim.Shear(g1=0.11, g2=0.)
    q1 = galsim.Shear(g1=0.11, g2=0.)  # Appropriate for the flips around x and y axes
    q2 = galsim.Shear(g1=-0.11, g2=0.)  # Appropriate for the flip around x=y

    decimal=6  # Oddly, these aren't as precise as I would have expected.
               # Even when we only go to this many digits of accuracy, the Exponential needed
               # a lower than default value for maxk_threshold.
    im = galsim.ImageD(16,16, scale=0.05)

    for prof in prof_list:
        print('prof = ',prof)

        # Not all profiles are expected to have a max_sb value close to the maximum pixel value,
        # so mark the ones where we don't want to require this to be true.
        close_maxsb = True
        name = str(prof)
        if ('DeVauc' in name or 'Sersic' in name or 'Spergel' in name or
            'Optical' in name or 'shift' in name):
            close_maxsb = False

        # Make sure we hit all 4 fill functions.
        # image_x uses fillXValue with izero, jzero
        # image_x1 uses fillXValue with izero, jzero, and unequal dx,dy
        # image_x2 uses fillXValue with dxy, dyx
        # image_k uses fillKValue with izero, jzero
        # image_k1 uses fillKValue with izero, jzero, and unequal dx,dy
        # image_k2 uses fillKValue with dxy, dyx
        image_x = prof.drawImage(image=im.copy(), method='no_pixel')
        image_x1 = prof.shear(q).drawImage(image=im.copy(), method='no_pixel')
        image_x2 = prof.shear(s).drawImage(image=im.copy(), method='no_pixel')
        image_k = prof.drawImage(image=im.copy())
        image_k1 = prof.shear(q).drawImage(image=im.copy())
        image_k2 = prof.shear(s).drawImage(image=im.copy())

        if close_maxsb:
            np.testing.assert_allclose(
                    image_x.array.max(), prof.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image_x1.array.max(), prof.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image_x2.array.max(), prof.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around y axis (i.e. x -> -x)
        flip1 = prof.transform(-1, 0, 0, 1)
        image2_x = flip1.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, image2_x.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x test")
        image2_x1 = flip1.shear(q1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, image2_x1.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x1 test")
        image2_x2 = flip1.shear(s1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, image2_x2.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x2 test")
        image2_k = flip1.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, image2_k.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k test")
        image2_k1 = flip1.shear(q1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, image2_k1.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k1 test")
        image2_k2 = flip1.shear(s1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, image2_k2.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip1.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip1.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip1.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around x axis (i.e. y -> -y)
        flip2 = prof.transform(1, 0, 0, -1)
        image2_x = flip2.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, image2_x.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x test")
        image2_x1 = flip2.shear(q1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, image2_x1.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x1 test")
        image2_x2 = flip2.shear(s1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, image2_x2.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x2 test")
        image2_k = flip2.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, image2_k.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k test")
        image2_k1 = flip2.shear(q1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, image2_k1.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k1 test")
        image2_k2 = flip2.shear(s1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, image2_k2.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip2.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip2.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip2.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around x=y (i.e. y -> x, x -> y)
        flip3 = prof.transform(0, 1, 1, 0)
        image2_x = flip3.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, np.transpose(image2_x.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x test")
        image2_x1 = flip3.shear(q2).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, np.transpose(image2_x1.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x1 test")
        image2_x2 = flip3.shear(s2).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, np.transpose(image2_x2.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x2 test")
        image2_k = flip3.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, np.transpose(image2_k.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k test")
        image2_k1 = flip3.shear(q2).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, np.transpose(image2_k1.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k1 test")
        image2_k2 = flip3.shear(s2).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, np.transpose(image2_k2.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip3.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip3.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip3.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        do_pickle(prof, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip1, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip2, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip3, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(prof)
        do_pickle(flip1)
        do_pickle(flip2)
        do_pickle(flip3)
Ejemplo n.º 27
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

    We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random)
    fluxes, sizes, and shapes.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo7")

    # To turn off logging:
    #logger.propagate = False

    # Define some parameters we'll use below.

    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')

    file_name = os.path.join('output', 'cube_phot.fits.gz')

    random_seed = 553728
    sky_level = 1.e4  # ADU / arcsec^2
    pixel_scale = 0.28  # arcsec
    nx = 64
    ny = 64

    gal_flux_min = 1.e4  # Range for galaxy flux
    gal_flux_max = 1.e5
    gal_hlr_min = 0.3  # arcsec
    gal_hlr_max = 1.3  # arcsec
    gal_e_min = 0.  # Range for ellipticity
    gal_e_max = 0.8

    psf_fwhm = 0.65  # arcsec

    # This script is set up as a comparison between using FFTs for doing the convolutions and
    # shooting photons.  The two methods have trade-offs in speed and accuracy which vary
    # with the kind of profile being drawn and the S/N of the object, among other factors.
    # In addition, for each method, there are a number of parameters GalSim uses that control
    # aspects of the calculation that further affect the speed and accuracy.
    #
    # We encapsulate these parameters with an object called GSParams.  The default values
    # are intended to be accurate enough for normal precision shear tests, without sacrificing
    # too much speed.
    #
    # Any PSF or galaxy object can be given a gsparams argument on construction that can
    # have different values to make the calculation more or less accurate (typically trading
    # off for speed or memory).
    #
    # In this script, we adjust some of the values slightly, just to show you how it works.
    # You could play around with these values and see what effect they have on the drawn images.
    # Usually, it requires a pretty drastic change in these parameters for you to be able to
    # notice the difference by eye.  But subtle effects that may impact the shapes of galaxies
    # can happen well before then.

    # Type help(galsim.GSParams) for the complete list of parameters and more detailed
    # documentation, including the default values for each parameter.
    gsparams = galsim.GSParams(
        alias_threshold=
        1.e-2,  # maximum fractional flux that may be aliased around edge of FFT
        maxk_threshold=
        2.e-3,  # k-values less than this may be excluded off edge of FFT
        xvalue_accuracy=
        1.e-4,  # approximations in real space aim to be this accurate
        kvalue_accuracy=
        1.e-4,  # approximations in fourier space aim to be this accurate
        shoot_accuracy=
        1.e-4,  # approximations in photon shooting aim to be this accurate
        minimum_fft_size=64)  # minimum size of ffts

    logger.info('Starting demo script 7')

    # Make the pixel:
    pix = galsim.Pixel(xw=pixel_scale)

    # Make the PSF profiles:
    psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams)
    psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams)
    psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm,
                                 flux=0.2,
                                 gsparams=gsparams)
    psf3 = psf3_inner + psf3_outer
    atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm,
                               obscuration=0.4,
                               defocus=0.1,
                               astig1=0.3,
                               astig2=-0.2,
                               coma1=0.2,
                               coma2=0.1,
                               spher=-0.3,
                               gsparams=gsparams)
    psf4 = galsim.Convolve([atmos, optics
                            ])  # Convolve inherits the gsparams from the first
    # item in the list.  (Or you can supply a gsparams
    # argument explicitly if you want to override this.)
    atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams)
    psf5 = galsim.Convolve([atmos, optics])
    psfs = [psf1, psf2, psf3, psf4, psf5]
    psf_names = [
        "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF",
        "Kolmogorov * Airy"
    ]
    psf_times = [0, 0, 0, 0, 0]
    psf_fft_times = [0, 0, 0, 0, 0]
    psf_phot_times = [0, 0, 0, 0, 0]

    # Make the galaxy profiles:
    gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams)
    gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams)
    gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams)
    gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams)
    bulge = galsim.Sersic(half_light_radius=0.7, n=3.2, gsparams=gsparams)
    disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams)
    gal5 = 0.4 * bulge + 0.6 * disk  # Net half-light radius is only approximate for this one.
    gals = [gal1, gal2, gal3, gal4, gal5]
    gal_names = [
        "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic",
        "Bulge + Disk"
    ]
    gal_times = [0, 0, 0, 0, 0]
    gal_fft_times = [0, 0, 0, 0, 0]
    gal_phot_times = [0, 0, 0, 0, 0]

    # Other times to keep track of:
    setup_times = 0
    fft_times = 0
    phot_times = 0
    noise_times = 0

    # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape.
    all_images = []
    k = 0
    for ipsf in range(len(psfs)):
        psf = psfs[ipsf]
        psf_name = psf_names[ipsf]
        for igal in range(len(gals)):
            gal = gals[igal]
            gal_name = gal_names[igal]
            for i in range(4):
                logger.debug('Start work on image %d', i)
                t1 = time.time()

                # Initialize the random number generator we will be using.
                rng = galsim.UniformDeviate(random_seed + k)

                # Get a new copy, we'll want to keep the original unmodified.
                gal1 = gal.copy()

                # Generate random variates:
                flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min
                gal1.setFlux(flux)

                hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
                gal1.applyDilation(hlr)

                beta_ellip = rng() * 2 * math.pi * galsim.radians
                ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min
                gal_shape = galsim.Shear(e=ellip, beta=beta_ellip)
                gal1.applyShear(gal_shape)

                # Build the final object by convolving the galaxy, PSF and pixel response.
                final = galsim.Convolve([psf, pix, gal1])
                # For photon shooting, need a version without the pixel (see below).
                final_nopix = galsim.Convolve([psf, gal1])

                # Create the large, double width output image
                image = galsim.ImageF(2 * nx + 2, ny)

                # Rather than provide a dx= argument to the draw commands, we can also
                # set the pixel scale in the image itself with setScale.
                image.setScale(pixel_scale)

                # Assign the following two "ImageViews", fft_image and phot_image.
                # Using the syntax below, these are views into the larger image.
                # Changes/additions to the sub-images referenced by the views are automatically
                # reflected in the original image.
                fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
                phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]

                logger.debug(
                    '   Read in training sample galaxy and PSF from file')
                t2 = time.time()

                # Draw the profile
                final.draw(fft_image)

                logger.debug(
                    '   Drew fft image.  Total drawn flux = %f.  .flux = %f',
                    fft_image.array.sum(), final.getFlux())
                t3 = time.time()

                # Add Poisson noise
                sky_level_pixel = sky_level * pixel_scale**2
                fft_image.addNoise(
                    galsim.PoissonNoise(rng, sky_level=sky_level_pixel))

                t4 = time.time()

                # The next two lines are just to get the output from this demo script
                # to match the output from the parsing of demo7.yaml.
                rng = galsim.UniformDeviate(random_seed + k)
                rng()
                rng()
                rng()
                rng()

                # Repeat for photon shooting image.
                # Photon shooting automatically convolves by the pixel, so we've made sure not
                # to include it in the profile!
                final_nopix.drawShoot(phot_image,
                                      max_extra_noise=sky_level_pixel / 100,
                                      rng=rng)
                t5 = time.time()

                # For photon shooting, galaxy already has Poisson noise, so we want to make
                # sure not to add that noise again!  Thus, we just add sky noise, which
                # is Poisson with the mean = sky_level_pixel
                pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)
                # DeviateNoise just adds the action of the given deviate to every pixel.
                phot_image.addNoise(galsim.DeviateNoise(pd))
                # For PoissonDeviate, the mean is not zero, so for a background-subtracted
                # image, we need to subtract the mean back off when we are done.
                phot_image -= sky_level_pixel

                logger.debug(
                    '   Added Poisson noise.  Image fluxes are now %f and %f',
                    fft_image.array.sum(), phot_image.array.sum())
                t6 = time.time()

                # Store that into the list of all images
                all_images += [image]

                k = k + 1
                logger.info(
                    '%d: %s * %s, flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)',
                    k, gal_name, psf_name, flux, hlr, gal_shape.getE1(),
                    gal_shape.getE2())
                logger.debug('   Times: %f, %f, %f, %f, %f', t2 - t1, t3 - t2,
                             t4 - t3, t5 - t4, t6 - t5)

                psf_times[ipsf] += t6 - t1
                psf_fft_times[ipsf] += t3 - t2
                psf_phot_times[ipsf] += t5 - t4
                gal_times[igal] += t6 - t1
                gal_fft_times[igal] += t3 - t2
                gal_phot_times[igal] += t5 - t4
                setup_times += t2 - t1
                fft_times += t3 - t2
                phot_times += t5 - t4
                noise_times += t4 - t3 + t6 - t5

    logger.info('Done making images of galaxies')
    logger.info('')
    logger.info('Some timing statistics:')
    logger.info('   Total time for setup steps = %f', setup_times)
    logger.info('   Total time for regular fft drawing = %f', fft_times)
    logger.info('   Total time for photon shooting = %f', phot_times)
    logger.info('   Total time for adding noise = %f', noise_times)
    logger.info('')
    logger.info('Breakdown by PSF type:')
    for ipsf in range(len(psfs)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf],
                    psf_phot_times[ipsf])
    logger.info('')
    logger.info('Breakdown by Galaxy type:')
    for igal in range(len(gals)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    gal_names[igal], gal_times[igal], gal_fft_times[igal],
                    gal_phot_times[igal])
    logger.info('')

    # Now write the image to disk.
    # With any write command, you can optionally compress the file using several compression
    # schemes:
    #   'gzip' uses gzip on the full output file.
    #   'bzip2' uses bzip2 on the full output file.
    #   'rice' uses rice compression on the image, leaving the fits headers readable.
    #   'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable.
    #   'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it
    #               doesn't work for writeCube.
    #   'plio' uses plio on the image, but it is only valid for positive integer data.
    # Furthermore, the first three have standard filename extensions associated with them,
    # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz',
    # the corresponding compression will be selected automatically.
    # In other words, the `compression='gzip'` specification is actually optional here:
    galsim.fits.writeCube(all_images, file_name, compression='gzip')
    logger.info('Wrote fft image to fits data cube %r', file_name)
Ejemplo n.º 28
0
def run_tests(random_seed,
              outfile,
              config=None,
              gsparams=None,
              wmult=None,
              logger=None,
              fail_value=-666.):
    """Run a full set of tests, writing pickled tuple output to outfile.
    """
    import sys
    import cPickle
    import numpy as np
    import galsim
    import galaxy_sample
    # Load up the comparison_utilities module from the parent directory
    sys.path.append('..')
    import comparison_utilities

    if config is None:
        use_config = False
        if gsparams is None:
            import warnings
            warnings.warn("No gsparams provided to run_tests?")
        if wmult is None:
            raise ValueError("wmult must be set if config=None.")
    else:
        use_config = True
        if gsparams is not None:
            import warnings
            warnings.warn(
                "gsparams is provided as a kwarg but the config['image']['gsparams'] will take "
                + "precedence.")
        if wmult is not None:
            import warnings
            warnings.warn(
                "wmult is provided as a kwarg but the config['image']['wmult'] will take "
                + "precedence.")
    # Get galaxy sample
    n_cosmos, hlr_cosmos, gabs_cosmos = galaxy_sample.get()
    # Only take the first NOBS objects
    n_cosmos = n_cosmos[0:NOBS]
    hlr_cosmos = hlr_cosmos[0:NOBS]
    gabs_cosmos = gabs_cosmos[0:NOBS]
    ntest = len(SERSIC_N_TEST)
    # Setup a UniformDeviate
    ud = galsim.UniformDeviate(random_seed)
    # Open the output file and write a header:
    fout = open(outfile, 'wb')
    fout.write(
        '#  g1obs_draw g2obs_draw sigma_draw delta_g1obs delta_g2obs delta_sigma '
        + 'err_g1obs err_g2obs err_sigma\n')
    # Start looping through the sample objects and collect the results
    for i, hlr, gabs in zip(range(NOBS), hlr_cosmos, gabs_cosmos):
        print "Testing galaxy #"+str(i+1)+"/"+str(NOBS)+\
              " with (hlr, |g|) = "+str(hlr)+", "+str(gabs)
        random_theta = 2. * np.pi * ud()
        g1 = gabs * np.cos(2. * random_theta)
        g2 = gabs * np.sin(2. * random_theta)
        for j, sersic_n in zip(range(ntest), SERSIC_N_TEST):
            print "Exploring Sersic n = " + str(sersic_n)
            if use_config:
                # Increment the random seed so that each test gets a unique one
                config['image'][
                    'random_seed'] = random_seed + i * NOBS * ntest + j * ntest + 1
                config['gal'] = {
                    "type": "Sersic",
                    "n": sersic_n,
                    "half_light_radius": hlr,
                    "ellip": {
                        "type": "G1G2",
                        "g1": g1,
                        "g2": g2
                    }
                }
                config['psf'] = {
                    "type": "Airy",
                    "lam_over_diam": PSF_LAM_OVER_DIAM
                }
                try:
                    results = comparison_utilities.compare_dft_vs_photon_config(
                        config,
                        abs_tol_ellip=TOL_ELLIP,
                        abs_tol_size=TOL_SIZE,
                        logger=logger)
                    test_ran = True
                except RuntimeError as err:
                    test_ran = False
                    pass
                # Uncomment lines below to ouput a check image
                #import copy
                #checkimage = galsim.config.BuildImage(copy.deepcopy(config))[0] #im = first element
                #checkimage.write('junk_'+str(i + 1)+'_'+str(j + 1)+'.fits')
            else:
                test_gsparams = galsim.GSParams(maximum_fft_size=MAX_FFT_SIZE)
                galaxy = galsim.Sersic(sersic_n,
                                       half_light_radius=hlr,
                                       gsparams=test_gsparams)
                galaxy.applyShear(g1=g1, g2=g2)
                psf = galsim.Airy(lam_over_diam=PSF_LAM_OVER_DIAM,
                                  gsparams=test_gsparams)
                try:
                    results = comparison_utilities.compare_dft_vs_photon_object(
                        galaxy,
                        psf_object=psf,
                        rng=ud,
                        pixel_scale=PIXEL_SCALE,
                        size=IMAGE_SIZE,
                        abs_tol_ellip=TOL_ELLIP,
                        abs_tol_size=TOL_SIZE,
                        n_photons_per_trial=NPHOTONS,
                        wmult=wmult)
                    test_ran = True
                except RuntimeError, err:
                    test_ran = False
                    pass

            if not test_ran:
                import warnings
                warnings.warn('RuntimeError encountered for galaxy ' +
                              str(i + 1) + '/' + str(NOBS) + ' with ' +
                              'Sersic n = ' + str(sersic_n) + ': ' + str(err))
                fout.write('%e %e %e %e %e %e %e %e %e %e %e %e %e\n' %
                           (fail_value, fail_value, fail_value, fail_value,
                            fail_value, fail_value, fail_value, fail_value,
                            fail_value, fail_value, fail_value, fail_value,
                            fail_value))
                fout.flush()
            else:
                fout.write('%e %e %e %e %e %e %e %e %e %e %e %e %e\n' %
                           (results.g1obs_draw, results.g2obs_draw,
                            results.sigma_draw, results.delta_g1obs,
                            results.delta_g2obs, results.delta_sigma,
                            results.err_g1obs, results.err_g2obs,
                            results.err_sigma, sersic_n, hlr, g1, g2))
                fout.flush()
Ejemplo n.º 29
0
 t1 = time.time()
 gd = galsim.GaussianDeviate(rseed)
 dx_cosmos = 0.03  # Non-unity, non-default value to be used below
 cn = galsim.getCOSMOSNoise(
     gd,
     '../../../examples/data/acs_I_unrot_sci_20_cf.fits',
     dx_cosmos=dx_cosmos)
 cn.setVariance(1000.)  # Again chosen to be non-unity
 # Define a PSF with which to convolve the noise field, one WITHOUT 2-fold rotational symmetry
 # (see test_autocorrelate in test_SBProfile.py for more info as to why this is relevant)
 # Make a relatively realistic mockup of a GREAT3 target image
 lam_over_diam_cosmos = (814.e-9 /
                         2.4) * (180. / np.pi) * 3600.  # ~lamda/D in arcsec
 lam_over_diam_ground = lam_over_diam_cosmos * 2.4 / 4.  # Generic 4m at same lambda
 psf_cosmos = galsim.Convolve([
     galsim.Airy(lam_over_diam=lam_over_diam_cosmos, obscuration=0.4),
     galsim.Pixel(0.05)
 ])
 psf_ground = galsim.Convolve([
     galsim.Kolmogorov(fwhm=0.8),
     galsim.Pixel(0.18),
     galsim.OpticalPSF(lam_over_diam=lam_over_diam_ground,
                       coma2=0.4,
                       defocus=-0.6)
 ])
 psf_shera = galsim.Convolve([
     psf_ground, (galsim.Deconvolve(psf_cosmos)).createSheared(g1=0.03,
                                                               g2=-0.01)
 ])
 # Then define the convolved cosmos correlated noise model
 conv_cn = cn.copy()
Ejemplo n.º 30
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

    We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random)
    fluxes, sizes, and shapes.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo7")

    # To turn off logging:
    #logger.propagate = False

    # To turn on the debugging messages:
    #logger.setLevel(logging.DEBUG)

    # Define some parameters we'll use below.

    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')

    file_name = os.path.join('output', 'cube_phot.fits.gz')

    random_seed = 553728
    sky_level = 1.e4  # ADU / arcsec^2
    pixel_scale = 0.28  # arcsec
    nx = 64
    ny = 64

    gal_flux_min = 1.e4  # Range for galaxy flux
    gal_flux_max = 1.e5
    gal_hlr_min = 0.3  # arcsec
    gal_hlr_max = 1.3  # arcsec
    gal_e_min = 0.  # Range for ellipticity
    gal_e_max = 0.8

    psf_fwhm = 0.65  # arcsec

    # This script is set up as a comparison between using FFTs for doing the convolutions and
    # shooting photons.  The two methods have trade-offs in speed and accuracy which vary
    # with the kind of profile being drawn and the S/N of the object, among other factors.
    # In addition, for each method, there are a number of parameters GalSim uses that control
    # aspects of the calculation that further affect the speed and accuracy.
    #
    # We encapsulate these parameters with an object called GSParams.  The default values
    # are intended to be accurate enough for normal precision shear tests, without sacrificing
    # too much speed.
    #
    # Any PSF or galaxy object can be given a gsparams argument on construction that can
    # have different values to make the calculation more or less accurate (typically trading
    # off for speed or memory).
    #
    # In this script, we adjust some of the values slightly, just to show you how it works.
    # You could play around with these values and see what effect they have on the drawn images.
    # Usually, it requires a pretty drastic change in these parameters for you to be able to
    # notice the difference by eye.  But subtle effects that may impact the shapes of galaxies
    # can happen well before then.

    # Type help(galsim.GSParams) for the complete list of parameters and more detailed
    # documentation, including the default values for each parameter.
    gsparams = galsim.GSParams(
        folding_threshold=
        1.e-2,  # maximum fractional flux that may be folded around edge of FFT
        maxk_threshold=
        2.e-3,  # k-values less than this may be excluded off edge of FFT
        xvalue_accuracy=
        1.e-4,  # approximations in real space aim to be this accurate
        kvalue_accuracy=
        1.e-4,  # approximations in fourier space aim to be this accurate
        shoot_accuracy=
        1.e-4,  # approximations in photon shooting aim to be this accurate
        minimum_fft_size=64)  # minimum size of ffts

    logger.info('Starting demo script 7')

    # Make the PSF profiles:
    psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams)
    psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams)
    psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm,
                                 flux=0.2,
                                 gsparams=gsparams)
    psf3 = psf3_inner + psf3_outer
    atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    # The OpticalPSF and set of Zernike values chosen below correspond to a reasonably well aligned,
    # smallish ~0.3m / 12 inch diameter telescope with a central obscuration of ~0.12m or 5 inches
    # diameter, being used in optical wavebands.
    # In the Noll convention, the value of the Zernike coefficient also gives the RMS optical path
    # difference across a circular pupil.  An RMS difference of ~0.5 or larger indicates that parts
    # of the wavefront are in fully destructive interference, and so we might expect aberrations to
    # become strong when Zernike aberrations summed in quadrature approach 0.5 wave.
    # The aberrations chosen in this case correspond to operating close to a 0.25 wave RMS optical
    # path difference.  Unlike in demo3, we specify the aberrations by making a list that we pass
    # in using the 'aberrations' kwarg.  The order of aberrations starting from index 4 is defocus,
    # astig1, astig2, coma1, coma2, trefoil1, trefoil2, spher as in the Noll convention.
    # We ignore the first 4 values so that the index number corresponds to the Zernike index
    # in the Noll convention. This will be particularly convenient once we start allowing
    # coefficients beyond spherical (index 11).  c.f. The Wikipedia page about the Noll indices:
    #
    #     http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials

    aberrations = [0.0] * 12  # Set the initial size.
    aberrations[4] = 0.06  # Noll index 4 = Defocus
    aberrations[5:7] = [0.12, -0.08]  # Noll index 5,6 = Astigmatism
    aberrations[7:9] = [0.07, 0.04]  # Noll index 7,8 = Coma
    aberrations[11] = -0.13  # Noll index 11 = Spherical
    # You could also define these all at once if that is more convenient:
    #aberrations = [0.0, 0.0, 0.0, 0.0, 0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13]

    optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm,
                               obscuration=0.4,
                               aberrations=aberrations,
                               gsparams=gsparams)
    psf4 = galsim.Convolve([atmos, optics
                            ])  # Convolve inherits the gsparams from the first
    # item in the list.  (Or you can supply a gsparams
    # argument explicitly if you want to override this.)
    atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams)
    psf5 = galsim.Convolve([atmos, optics])
    psfs = [psf1, psf2, psf3, psf4, psf5]
    psf_names = [
        "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF",
        "Kolmogorov * Airy"
    ]
    psf_times = [0, 0, 0, 0, 0]
    psf_fft_times = [0, 0, 0, 0, 0]
    psf_phot_times = [0, 0, 0, 0, 0]

    # Make the galaxy profiles:
    gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams)
    gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams)
    gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams)
    gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams)
    # A Sersic profile may be truncated if desired.
    # The units for this are expected to be arcsec (or specifically -- whatever units
    # you are using for all the size values as defined by the pixel_scale).
    bulge = galsim.Sersic(half_light_radius=0.7,
                          n=3.2,
                          trunc=8.5,
                          gsparams=gsparams)
    disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams)
    gal5 = 0.4 * bulge + 0.6 * disk  # Net half-light radius is only approximate for this one.
    gals = [gal1, gal2, gal3, gal4, gal5]
    gal_names = [
        "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic",
        "Bulge + Disk"
    ]
    gal_times = [0, 0, 0, 0, 0]
    gal_fft_times = [0, 0, 0, 0, 0]
    gal_phot_times = [0, 0, 0, 0, 0]

    # Other times to keep track of:
    setup_times = 0
    fft_times = 0
    phot_times = 0
    noise_times = 0

    # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape.
    all_images = []
    k = 0
    for ipsf in range(len(psfs)):
        psf = psfs[ipsf]
        psf_name = psf_names[ipsf]
        logger.info('psf %d: %s', ipsf + 1, psf)
        # Note that this implicitly calls str(psf).  We've made an effort to give all GalSim
        # objects an informative but relatively succinct str representation.  Some details may
        # be missing, but it should look essentially like how you would create the object.
        logger.debug('repr = %r', psf)
        # The repr() version are a bit more pedantic in form and should be completely informative,
        # to the point where two objects that are not identical should never have equal repr
        # strings. As such the repr strings may in some cases be somewhat unwieldy.  For instance,
        # since we set non-default gsparams in these, the repr includes that information, but
        # it is omitted from the str for brevity.
        for igal in range(len(gals)):
            gal = gals[igal]
            gal_name = gal_names[igal]
            logger.info('   galaxy %d: %s', igal + 1, gal)
            logger.debug('   repr = %r', gal)
            for i in range(4):
                logger.debug('      Start work on image %d', i)
                t1 = time.time()

                # Initialize the random number generator we will be using.
                rng = galsim.UniformDeviate(random_seed + k)

                # Generate random variates:
                flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min

                # Use a new variable name, since we'll want to keep the original unmodified.
                this_gal = gal.withFlux(flux)

                hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
                this_gal = this_gal.dilate(hlr)

                beta_ellip = rng() * 2 * math.pi * galsim.radians
                ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min
                gal_shape = galsim.Shear(e=ellip, beta=beta_ellip)
                this_gal = this_gal.shear(gal_shape)

                # Build the final object by convolving the galaxy and PSF.
                final = galsim.Convolve([this_gal, psf])

                # Create the large, double width output image
                # Rather than provide a scale= argument to the drawImage commands, we can also
                # set the pixel scale in the image constructor.
                # Note: You can also change it after the construction with im.scale=pixel_scale
                image = galsim.ImageF(2 * nx + 2, ny, scale=pixel_scale)

                # Assign the following two Image "views", fft_image and phot_image.
                # Using the syntax below, these are views into the larger image.
                # Changes/additions to the sub-images referenced by the views are automatically
                # reflected in the original image.
                fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
                phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]

                logger.debug(
                    '      Read in training sample galaxy and PSF from file')
                t2 = time.time()

                # Draw the profile
                # This default rendering method (method='auto') usually defaults to FFT, since
                # that is normally the most efficient method.  However, we can also set method
                # to 'fft' explicitly to force it to always use FFTs for the convolution
                # by the pixel response.  (In this case, it doesn't have any effect, since
                # the 'auto' method would have always chosen 'fft' anyway, so this is just
                # for illustrative purposes.)
                final.drawImage(fft_image, method='fft')

                logger.debug(
                    '      Drew fft image.  Total drawn flux = %f.  .flux = %f',
                    fft_image.array.sum(), final.getFlux())
                t3 = time.time()

                # Add Poisson noise
                sky_level_pixel = sky_level * pixel_scale**2
                fft_image.addNoise(
                    galsim.PoissonNoise(rng, sky_level=sky_level_pixel))

                t4 = time.time()

                # The next two lines are just to get the output from this demo script
                # to match the output from the parsing of demo7.yaml.
                rng = galsim.UniformDeviate(random_seed + k)
                rng()
                rng()
                rng()
                rng()

                # Repeat for photon shooting image.
                # The max_extra_noise parameter indicates how much extra noise per pixel we are
                # willing to tolerate.  The sky noise will be adding a variance of sky_level_pixel,
                # so we allow up to 1% of that extra.
                final.drawImage(phot_image,
                                method='phot',
                                max_extra_noise=sky_level_pixel / 100,
                                rng=rng)
                t5 = time.time()

                # For photon shooting, galaxy already has Poisson noise, so we want to make
                # sure not to add that noise again!  Thus, we just add sky noise, which
                # is Poisson with the mean = sky_level_pixel
                pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)
                # DeviateNoise just adds the action of the given deviate to every pixel.
                phot_image.addNoise(galsim.DeviateNoise(pd))
                # For PoissonDeviate, the mean is not zero, so for a background-subtracted
                # image, we need to subtract the mean back off when we are done.
                phot_image -= sky_level_pixel

                logger.debug(
                    '      Added Poisson noise.  Image fluxes are now %f and %f',
                    fft_image.array.sum(), phot_image.array.sum())
                t6 = time.time()

                # Store that into the list of all images
                all_images += [image]

                k = k + 1
                logger.info(
                    '      %d: flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)',
                    k, flux, hlr, gal_shape.getE1(), gal_shape.getE2())
                logger.debug('      Times: %f, %f, %f, %f, %f', t2 - t1,
                             t3 - t2, t4 - t3, t5 - t4, t6 - t5)

                psf_times[ipsf] += t6 - t1
                psf_fft_times[ipsf] += t3 - t2
                psf_phot_times[ipsf] += t5 - t4
                gal_times[igal] += t6 - t1
                gal_fft_times[igal] += t3 - t2
                gal_phot_times[igal] += t5 - t4
                setup_times += t2 - t1
                fft_times += t3 - t2
                phot_times += t5 - t4
                noise_times += t4 - t3 + t6 - t5

    logger.info('Done making images of galaxies')
    logger.info('')
    logger.info('Some timing statistics:')
    logger.info('   Total time for setup steps = %f', setup_times)
    logger.info('   Total time for regular fft drawing = %f', fft_times)
    logger.info('   Total time for photon shooting = %f', phot_times)
    logger.info('   Total time for adding noise = %f', noise_times)
    logger.info('')
    logger.info('Breakdown by PSF type:')
    for ipsf in range(len(psfs)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf],
                    psf_phot_times[ipsf])
    logger.info('')
    logger.info('Breakdown by Galaxy type:')
    for igal in range(len(gals)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    gal_names[igal], gal_times[igal], gal_fft_times[igal],
                    gal_phot_times[igal])
    logger.info('')

    # Now write the image to disk.
    # With any write command, you can optionally compress the file using several compression
    # schemes:
    #   'gzip' uses gzip on the full output file.
    #   'bzip2' uses bzip2 on the full output file.
    #   'rice' uses rice compression on the image, leaving the fits headers readable.
    #   'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable.
    #   'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it
    #               doesn't work for writeCube.
    #   'plio' uses plio on the image, but it is only valid for positive integer data.
    # Furthermore, the first three have standard filename extensions associated with them,
    # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz',
    # the corresponding compression will be selected automatically.
    # In other words, the `compression='gzip'` specification is actually optional here:
    galsim.fits.writeCube(all_images, file_name, compression='gzip')
    logger.info('Wrote fft image to fits data cube %r', file_name)