Пример #1
0
def test_shear_units():
    """Test that the shears we get out do not depend on the input PS and grid units."""
    import time
    t1 = time.time()

    rand_seed = 123456

    grid_size = 10.  # degrees
    ngrid = 100

    # Define a PS with some normalization value P(k=1/arcsec)=1 arcsec^2.
    # For this case we are getting the shears using units of arcsec for everything.
    ps = galsim.PowerSpectrum(lambda k: k)
    g1, g2 = ps.buildGrid(grid_spacing=3600. * grid_size / ngrid,
                          ngrid=ngrid,
                          rng=galsim.BaseDeviate(rand_seed))
    # The above was done with all inputs given in arcsec.  Now, redo it, inputting the PS
    # information in degrees and the grid info in arcsec.
    # We know that if k=1/arcsec, then when expressed as 1/degrees, it is
    # k=3600/degree.  So define the PS as P(k=3600/degree)=(1/3600.)^2 degree^2.
    ps = galsim.PowerSpectrum(lambda k: (1. / 3600.**2) * (k / 3600.),
                              units=galsim.degrees)
    g1_2, g2_2 = ps.buildGrid(grid_spacing=3600. * grid_size / ngrid,
                              ngrid=ngrid,
                              rng=galsim.BaseDeviate(rand_seed))
    # Finally redo it, inputting the PS and grid info in degrees.
    ps = galsim.PowerSpectrum(lambda k: (1. / 3600.**2) * (k / 3600.),
                              units=galsim.degrees)
    g1_3, g2_3 = ps.buildGrid(grid_spacing=grid_size / ngrid,
                              ngrid=ngrid,
                              units=galsim.degrees,
                              rng=galsim.BaseDeviate(rand_seed))

    # Since same random seed was used, require complete equality of shears, which would show that
    # all unit conversions were properly handled.
    np.testing.assert_array_almost_equal(
        g1,
        g1_2,
        decimal=9,
        err_msg='Incorrect unit handling in lensing engine')
    np.testing.assert_array_almost_equal(
        g1,
        g1_3,
        decimal=9,
        err_msg='Incorrect unit handling in lensing engine')
    np.testing.assert_array_almost_equal(
        g2,
        g2_2,
        decimal=9,
        err_msg='Incorrect unit handling in lensing engine')
    np.testing.assert_array_almost_equal(
        g2,
        g2_3,
        decimal=9,
        err_msg='Incorrect unit handling in lensing engine')
    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Пример #2
0
def test_shear_seeds():
    """Test that shears from lensing engine behave appropriate when given same/different seeds"""
    import time
    t1 = time.time()

    # make a power spectrum for some E, B power function
    test_ps = galsim.PowerSpectrum(e_power_function=pk2, b_power_function=pk2)

    # get shears on a grid w/o specifying seed
    g1, g2 = test_ps.buildGrid(grid_spacing=1.0, ngrid = 10)
    # do it again, w/o specifying seed: should differ
    g1new, g2new = test_ps.buildGrid(grid_spacing=1.0, ngrid = 10)
    assert not ((g1[0,0]==g1new[0,0]) or (g2[0,0]==g2new[0,0]))

    # get shears on a grid w/ specified seed
    g1, g2 = test_ps.buildGrid(grid_spacing=1.0, ngrid = 10, rng=galsim.BaseDeviate(13796))
    # get shears on a grid w/ same specified seed: should be same
    g1new, g2new = test_ps.buildGrid(grid_spacing=1.0, ngrid = 10, rng=galsim.BaseDeviate(13796))
    np.testing.assert_array_equal(g1, g1new,
                                  err_msg="New shear field differs from previous (same seed)!")
    np.testing.assert_array_equal(g2, g2new,
                                  err_msg="New shear field differs from previous (same seed)!")
    # get shears on a grid w/ diff't specified seed: should differ
    g1new, g2new = test_ps.buildGrid(grid_spacing=1.0, ngrid = 10, rng=galsim.BaseDeviate(1379))
    assert not ((g1[0,0]==g1new[0,0]) or (g2[0,0]==g2new[0,0]))

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #3
0
def read_ps(galsim_dir=None, scale=1.):
    """Read in a Power Spectrum stored in the GalSim repository.

    Returns a galsim.PowerSpectrum object.
    """
    import os
    if galsim_dir is None:
        raise ValueError(
            "You must supply a directory for your GalSim install via the `galsim_dir` kwarg."
        )
    file = os.path.join(galsim_dir, 'examples', 'data',
                        'cosmo-fid.zmed1.00_smoothed.out')
    if scale == 1.:
        tab_ps = galsim.LookupTable(file=file, interpolant='linear')
    else:
        data = np.loadtxt(file).transpose()
        if data.shape[0] != 2:
            raise ValueError(
                "File %s provided for LookupTable does not have 2 columns" %
                file)
        x = data[0]
        f = data[1]
        f *= scale
        tab_ps = galsim.LookupTable(x=x, f=f, interpolant='linear')

    # Put this table into an E-mode power spectrum and return
    ret = galsim.PowerSpectrum(tab_ps, None, units=galsim.radians)
    return ret
Пример #4
0
def test_shear_reference():
    """Test shears from lensing engine compared to stored reference values"""
    import time
    t1 = time.time()

    # read input data
    ref = np.loadtxt(refdir + '/shearfield_reference.dat')
    g1_in = ref[:,0]
    g2_in = ref[:,1]
    kappa_in = ref[:,2]

    # set up params
    rng = galsim.BaseDeviate(14136)
    n = 10
    dx = 1.

    # define power spectrum
    ps = galsim.PowerSpectrum(e_power_function=lambda k : k**0.5, b_power_function=lambda k : k)
    # get shears
    g1, g2, kappa = ps.buildGrid(grid_spacing = dx, ngrid = n, rng=rng, get_convergence=True)

    # put in same format as data that got read in
    g1vec = g1.reshape(n*n)
    g2vec = g2.reshape(n*n)
    kappavec = kappa.reshape(n*n)
    # compare input vs. calculated values
    np.testing.assert_almost_equal(g1_in, g1vec, 9,
                                   err_msg = "Shear field differs from reference shear field!")
    np.testing.assert_almost_equal(g2_in, g2vec, 9,
                                   err_msg = "Shear field differs from reference shear field!")
    np.testing.assert_almost_equal(kappa_in, kappavec, 9,
                                   err_msg = "Convergence differences from references!")

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #5
0
def simulate_shear(constants, redshift, nbins, noise_sd=0.0, seed=0):
    """Takes cosmological parameters, generates a shear map, and adds
    noise.

    Inputs:
        constants: Constants, object for cosmological constants
        redshift: float, the redshift value for the sample
        nbins: int, the number of bins for the correlation function
        noise_sd: float, the standard deviation for IID Gaussian noise.
        seed: integer, seed for the RNG; if 0 it uses a randomly chosen seed.

    Returns:
        A pair of shear spectrum grids.
    """

    grid_nx = 100  # length of grid in one dimension (degrees)
    theta = 10.0  # grid spacing
    dtheta = theta / grid_nx

    # wavenumbers at which to evaluate power spectra
    ell = np.logspace(-2.0, 4.0, num=50)

    nicaea_obj = constants.nicaea_object()
    psObs_nicaea = nicaea_obj.convergencePowerSpectrum(ell=ell, z=redshift)
    psObs_tabulated = galsim.LookupTable(ell,
                                         psObs_nicaea,
                                         interpolant='linear')
    ps_galsim = galsim.PowerSpectrum(psObs_tabulated,
                                     delta2=False,
                                     units=galsim.radians)

    grid_deviate = galsim.BaseDeviate(seed)
    g1, g2, kappa = ps_galsim.buildGrid(grid_spacing=dtheta,
                                        ngrid=grid_nx,
                                        rng=grid_deviate,
                                        units='degrees',
                                        kmin_factor=2,
                                        kmax_factor=2,
                                        get_convergence=True)
    g1_r, g2_r, _ = galsim.lensing_ps.theoryToObserved(g1, g2, kappa)
    rng = galsim.GaussianDeviate(seed=seed)

    g1_noise_grid = galsim.ImageD(grid_nx, grid_nx)
    g2_noise_grid = galsim.ImageD(grid_nx, grid_nx)

    g1_noise_grid.addNoise(galsim.GaussianNoise(rng=rng, sigma=noise_sd))
    g2_noise_grid.addNoise(galsim.GaussianNoise(rng=rng, sigma=noise_sd))

    g1_noisy = np.add(g1_r, g1_noise_grid.array)
    g2_noisy = np.add(g2_r, g2_noise_grid.array)

    min_sep = dtheta
    max_sep = grid_nx * np.sqrt(2) * dtheta
    grid_range = dtheta * np.arange(grid_nx)
    x, y = np.meshgrid(grid_range, grid_range)
    stats = run_treecorr(x, y, g1, g2, min_sep, max_sep, nbins=nbins)

    return g1_noisy, g2_noisy, stats
Пример #6
0
def test_shear_get():
    """Check that using gridded outputs and the various getFoo methods gives consistent results"""
    import time
    t1 = time.time()

    # choose a power spectrum and grid setup
    my_ps = galsim.PowerSpectrum(lambda k : k**0.5)
    # build the grid
    grid_spacing = 1.
    ngrid = 100
    g1, g2, kappa = my_ps.buildGrid(grid_spacing = grid_spacing, ngrid = ngrid,
                                    get_convergence = True)
    min = (-ngrid/2 + 0.5) * grid_spacing
    max = (ngrid/2 - 0.5) * grid_spacing
    x, y = np.meshgrid(np.arange(min,max+grid_spacing,grid_spacing),
                       np.arange(min,max+grid_spacing,grid_spacing))

    # convert theoretical to observed quantities for grid
    g1_r, g2_r, mu = galsim.lensing_ps.theoryToObserved(g1, g2, kappa)

    # use getShear, getConvergence, getMagnification, getLensing do appropriate consistency checks
    test_g1_r, test_g2_r = my_ps.getShear((x.flatten(), y.flatten()))
    test_g1, test_g2 = my_ps.getShear((x.flatten(), y.flatten()), reduced = False)
    test_kappa = my_ps.getConvergence((x.flatten(), y.flatten()))
    test_mu = my_ps.getMagnification((x.flatten(), y.flatten()))
    test_g1_r_2, test_g2_r_2, test_mu_2 = my_ps.getLensing((x.flatten(), y.flatten()))
    np.testing.assert_almost_equal(g1.flatten(), test_g1, 9,
                                   err_msg="Shears from grid and getShear disagree!")
    np.testing.assert_almost_equal(g2.flatten(), test_g2, 9,
                                   err_msg="Shears from grid and getShear disagree!")
    np.testing.assert_almost_equal(g1_r.flatten(), test_g1_r, 9,
                                   err_msg="Reduced shears from grid and getShear disagree!")
    np.testing.assert_almost_equal(g2_r.flatten(), test_g2_r, 9,
                                   err_msg="Reduced shears from grid and getShear disagree!")
    np.testing.assert_almost_equal(g1_r.flatten(), test_g1_r_2, 9,
                                   err_msg="Reduced shears from grid and getLensing disagree!")
    np.testing.assert_almost_equal(g2_r.flatten(), test_g2_r_2, 9,
                                   err_msg="Reduced shears from grid and getLensing disagree!")
    np.testing.assert_almost_equal(kappa.flatten(), test_kappa, 9,
                                   err_msg="Convergences from grid and getConvergence disagree!")
    np.testing.assert_almost_equal(mu.flatten(), test_mu, 9,
                                   err_msg="Magnifications from grid and getMagnification disagree!")
    np.testing.assert_almost_equal(mu.flatten(), test_mu_2, 9,
                                   err_msg="Magnifications from grid and getLensing disagree!")

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #7
0
def generate_bmode_shears(var, ngrid, rng=None, kmax_factor=16, kmin_factor=1):
    """Generate b-mode shears, returns: g1, g2, gmag, gphi
    """
    ps = galsim.PowerSpectrum(b_power_function=lambda karr: var * np.ones_like(
        karr) / float(kmax_factor)**2)
    g1, g2 = ps.buildGrid(grid_spacing=1.,
                          ngrid=ngrid,
                          rng=rng,
                          kmax_factor=kmax_factor,
                          kmin_factor=kmin_factor)
    # These g1, g2 *shears* do not have to be on the unit disc, so we have to convert them to a |g|
    # like ellipticity via the result for a circle following such a shear, using Schneider (2006)
    # eq. 12.  Note, this itself will mean that the ellips are not pure B-mode!!
    gmag = np.sqrt(g1 * g1 + g2 * g2)
    for i in range(g1.shape[0]):  # ugly but my arm is broken
        for j in range(g2.shape[1]):
            if gmag[i, j] > 1.:
                g1[i, j] = g1[i, j] / gmag[i, j]**2
                g2[i, j] = g2[i, j] / gmag[i, j]**2
                gmag[i, j] = np.sqrt(g1[i, j]**2 + g2[i, j]**2)
            else:
                pass
    gphi = .5 * np.arctan2(g2, g1)
    return g1, g2, gmag, gphi
Пример #8
0
    def __init__(self,
                 *,
                 rng,
                 im_width,
                 buff,
                 scale,
                 trunc=1,
                 variation_factor=10,
                 fwhm=0.8):
        self._rng = rng
        self._im_cen = (im_width - 1) / 2
        self._scale = scale
        self._tot_width = im_width + 2 * buff
        self._x_scale = 2.0 / self._tot_width / scale
        self._buff = buff
        self._variation_factor = variation_factor
        self._median_seeing = fwhm

        # set the power spectrum and PSF params
        # Heymans et al, 2012 found L0 ~= 3 arcmin, given as 180 arcsec here.
        def _pf(k):
            return (k**2 +
                    (1. / 180)**2)**(-11. / 6.) * np.exp(-(k * trunc)**2)

        self._ps = galsim.PowerSpectrum(e_power_function=_pf,
                                        b_power_function=_pf)
        ng = 128
        gs = max(self._tot_width * self._scale / ng, 1)
        self.ng = ng
        self.gs = gs
        seed = self._rng.randint(1, 2**30)
        self._ps.buildGrid(grid_spacing=gs,
                           ngrid=ng,
                           get_convergence=True,
                           variance=(0.01 * variation_factor)**2,
                           rng=galsim.BaseDeviate(seed))

        # cache the galsim LookupTable2D objects by hand to speed computations
        g1_grid, g2_grid, mu_grid = galsim.lensing_ps.theoryToObserved(
            self._ps.im_g1.array, self._ps.im_g2.array,
            self._ps.im_kappa.array)

        self._lut_g1 = galsim.table.LookupTable2D(
            self._ps.x_grid,
            self._ps.y_grid,
            g1_grid.T,
            edge_mode='wrap',
            interpolant=galsim.Lanczos(5))
        self._lut_g2 = galsim.table.LookupTable2D(
            self._ps.x_grid,
            self._ps.y_grid,
            g2_grid.T,
            edge_mode='wrap',
            interpolant=galsim.Lanczos(5))
        self._lut_mu = galsim.table.LookupTable2D(
            self._ps.x_grid,
            self._ps.y_grid,
            mu_grid.T - 1,
            edge_mode='wrap',
            interpolant=galsim.Lanczos(5))

        self._g1_mean = self._rng.normal() * 0.01 * variation_factor
        self._g2_mean = self._rng.normal() * 0.01 * variation_factor

        def _getlogmnsigma(mean, sigma):
            logmean = np.log(mean) - 0.5 * np.log(1 + sigma**2 / mean**2)
            logvar = np.log(1 + sigma**2 / mean**2)
            logsigma = np.sqrt(logvar)
            return logmean, logsigma

        lm, ls = _getlogmnsigma(self._median_seeing, 0.1)
        self._fwhm_central = np.exp(self._rng.normal() * ls + lm)
Пример #9
0
def main(argv):
    """
    Make images using constant PSF and variable shear:
      - The main image is 2048 x 2048 pixels.
      - Pixel scale is 0.2 arcsec/pixel, hence the image is about 0.11 degrees on a side.
      - Applied shear is from a cosmological power spectrum read in from file.
      - The PSF is a real one from SDSS, and corresponds to a convolution of atmospheric PSF,
        optical PSF, and pixel response, which has been sampled at pixel centers.  We used a PSF
        from SDSS in order to have a PSF profile that could correspond to what you see with a real
        telescope. However, in order that the galaxy resolution not be too poor, we tell GalSim that
        the pixel scale for that PSF image is 0.2" rather than 0.396".  We are simultaneously lying
        about the intrinsic size of the PSF and about the pixel scale when we do this.
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles.  We choose 30% of the galaxies
        to use the images, and the other 60% to use the parametric fits
      - The real galaxy images include some initial correlated noise from the original HST
        observation.  However, we whiten the noise of the final image so the final image has
        stationary Gaussian noise, rather than correlated noise.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo11")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    pixel_scale = 0.2  # arcsec/pixel
    image_size = 2048  # size of image in pixels
    image_size_arcsec = image_size * pixel_scale  # size of big image in each dimension (arcsec)
    noise_variance = 5.e4  # ADU^2  (Just use simple Gaussian noise here.)
    nobj = 288  # number of galaxies in entire field
    # (This corresponds to 8 galaxies / arcmin^2)
    grid_spacing = 90.0  # The spacing between the samples for the power spectrum
    # realization (arcsec)
    tel_diam = 4  # Let's figure out the flux for a 4 m class telescope
    exp_time = 300  # exposing for 300 seconds.
    center_ra = 19.3 * galsim.hours  # The RA, Dec of the center of the image on the sky
    center_dec = -33.1 * galsim.degrees

    # The catalog returns objects that are appropriate for HST in 1 second exposures.  So for our
    # telescope we scale up by the relative area and exposure time.  Note that what is important is
    # the *effective* area after taking into account obscuration.  For HST, the telescope diameter
    # is 2.4 but there is obscuration (a linear factor of 0.33).  Here, we assume that the telescope
    # we're simulating effectively has no obscuration factor.  We're also ignoring the pi/4 factor
    # since it appears in the numerator and denominator, so we use area = diam^2.
    hst_eff_area = 2.4**2 * (1. - 0.33**2)
    flux_scaling = (tel_diam**2 / hst_eff_area) * exp_time

    # random_seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 24783923

    file_name = os.path.join('output', 'tabulated_power_spectrum.fits.fz')

    logger.info('Starting demo script 11')

    # Read in galaxy catalog
    # The COSMOSCatalog uses the same input file as we have been using for RealGalaxyCatalogs
    # along with a second file called real_galaxy_catalog_23.5_examples_fits.fits, which stores
    # the information about the parameteric fits.  There is no need to specify the second file
    # name, since the name is derivable from the name of the main catalog.
    if True:
        # The catalog we distribute with the GalSim code only has 100 galaxies.
        # The galaxies will typically be reused several times here.
        cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
        dir = 'data'
        cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir)
    else:
        # If you've run galsim_download_cosmos, you can leave out the cat_file_name and dir
        # to use the full COSMOS catalog with 56,000 galaxies in it.
        cosmos_cat = galsim.COSMOSCatalog()
    logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects)

    # Setup the PowerSpectrum object we'll be using:
    # To do this, we first have to read in the tabulated shear power spectrum, often denoted
    # C_ell(ell), where ell has units of inverse angle and C_ell has units of angle^2.  However,
    # GalSim works in the flat-sky approximation, so we use this notation interchangeably with
    # P(k).  GalSim does not calculate shear power spectra for users, who must be able to provide
    # their own (or use the examples in the repository).
    #
    # Here we use a tabulated power spectrum from iCosmo (http://icosmo.org), with the following
    # cosmological parameters and survey design:
    # H_0 = 70 km/s/Mpc
    # Omega_m = 0.25
    # Omega_Lambda = 0.75
    # w_0 = -1.0
    # w_a = 0.0
    # n_s = 0.96
    # sigma_8 = 0.8
    # Smith et al. prescription for the non-linear power spectrum.
    # Eisenstein & Hu transfer function with wiggles.
    # Default dN/dz with z_med = 1.0
    # The file has, as required, just two columns which are k and P(k).  However, iCosmo works in
    # terms of ell and C_ell; ell is inverse radians and C_ell in radians^2.  Since GalSim tends to
    # work in terms of arcsec, we have to tell it that the inputs are radians^-1 so it can convert
    # to store in terms of arcsec^-1.
    pk_file = os.path.join('data', 'cosmo-fid.zmed1.00.out')
    ps = galsim.PowerSpectrum(pk_file, units=galsim.radians)
    # The argument here is "e_power_function" which defines the E-mode power to use.
    logger.info('Set up power spectrum from tabulated P(k)')

    # Now let's read in the PSF.  It's a real SDSS PSF, which means pixel scale of 0.396".  However,
    # the typical seeing is 1.2" and we want to simulate better seeing, so we will just tell GalSim
    # that the pixel scale is 0.2".  We have to be careful with SDSS PSF images, as they have an
    # added 'soft bias' of 1000 which has been removed before creation of this file, so that the sky
    # level is properly zero.  Also, the file is bzipped, to demonstrate the ability of GalSim
    # handle this kind of compressed file (among others).  We read the image directly into an
    # InterpolatedImage GSObject, so we can manipulate it as needed (here, the only manipulation
    # needed is convolution).  The flux is 1 as needed for a PSF.
    psf_file = os.path.join('data', 'example_sdss_psf_sky0.fits.bz2')
    psf = galsim.InterpolatedImage(psf_file, scale=pixel_scale, flux=1.)
    logger.info('Read in PSF image from bzipped FITS file')

    # Setup the image:
    full_image = galsim.ImageF(image_size, image_size)

    # The default convention for indexing an image is to follow the FITS standard where the
    # lower-left pixel is called (1,1).  However, this can be counter-intuitive to people more
    # used to C or python indexing, where indices start at 0.  It is possible to change the
    # coordinates of the lower-left pixel with the methods `setOrigin`.  For this demo, we
    # switch to 0-based indexing, so the lower-left pixel will be called (0,0).
    full_image.setOrigin(0, 0)

    # As for demo10, we use random_seed for the random numbers required for the
    # whole image.  In this case, both the power spectrum realization and the noise on the
    # full image we apply later.
    rng = galsim.BaseDeviate(random_seed)

    # We want to make random positions within our image.  However, currently for shears from a power
    # spectrum we first have to get shears on a grid of positions, and then we can choose random
    # positions within that.  So, let's make the grid.  We're going to make it as large as the
    # image, with grid points spaced by 90 arcsec (hence interpolation only happens below 90"
    # scales, below the interesting scales on which we want the shear power spectrum to be
    # represented exactly).  The lensing engine wants positions in arcsec, so calculate that:
    ps.buildGrid(grid_spacing=grid_spacing,
                 ngrid=int(math.ceil(image_size_arcsec / grid_spacing)),
                 rng=rng)
    logger.info('Made gridded shears')

    # We keep track of how much noise is already in the image from the RealGalaxies.
    # The default initial value is all pixels = 0.
    noise_image = galsim.ImageF(image_size, image_size)
    noise_image.setOrigin(0, 0)

    # Make a slightly non-trivial WCS.  We'll use a slightly rotated coordinate system
    # and center it at the image center.
    theta = 0.17 * galsim.degrees
    # ( dudx  dudy ) = ( cos(theta)  -sin(theta) ) * pixel_scale
    # ( dvdx  dvdy )   ( sin(theta)   cos(theta) )
    # Aside: You can call numpy trig functions on Angle objects directly, rather than getting
    #        their values in radians first.  Or, if you prefer, you can write things like
    #        theta.sin() or theta.cos(), which are equivalent.
    dudx = numpy.cos(theta) * pixel_scale
    dudy = -numpy.sin(theta) * pixel_scale
    dvdx = numpy.sin(theta) * pixel_scale
    dvdy = numpy.cos(theta) * pixel_scale
    image_center = full_image.true_center
    affine = galsim.AffineTransform(dudx,
                                    dudy,
                                    dvdx,
                                    dvdy,
                                    origin=full_image.true_center)

    # We can also put it on the celestial sphere to give it a bit more realism.
    # The TAN projection takes a (u,v) coordinate system on a tangent plane and projects
    # that plane onto the sky using a given point as the tangent point.  The tangent
    # point should be given as a CelestialCoord.
    sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec)

    # The third parameter, units, defaults to arcsec, but we make it explicit here.
    # It sets the angular units of the (u,v) intermediate coordinate system.
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    full_image.wcs = wcs

    # Now we need to loop over our objects:
    for k in range(nobj):
        time1 = time.time()
        # The usual random number generator using a different seed for each galaxy.
        ud = galsim.UniformDeviate(random_seed + k + 1)

        # Choose a random RA, Dec around the sky_center.
        # Note that for this to come out close to a square shape, we need to account for the
        # cos(dec) part of the metric: ds^2 = dr^2 + r^2 d(dec)^2 + r^2 cos^2(dec) d(ra)^2
        # So need to calculate dec first.
        dec = center_dec + (ud() - 0.5) * image_size_arcsec * galsim.arcsec
        ra = center_ra + (
            ud() - 0.5) * image_size_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,
        # since the PowerSpectrum class is really defined on that plane, not in (ra,dec).
        uv_pos = affine.toWorld(image_pos)

        # Get the reduced shears and magnification at this point
        g1, g2, mu = ps.getLensing(pos=uv_pos)

        # Now we will have the COSMOSCatalog make a galaxy profile for us.  It can make either
        # a RealGalaxy using the original HST image and PSF, or a parametric model based on
        # parametric fits to the light distribution of the HST observation.  The parametric
        # models are either a Sersic fit to the data or a bulge + disk fit according to which
        # one gave the better chisq value.  We will select a galaxy at random from the catalog.
        # One could easily do this by choosing an index = int(ud() * cosmos_cat.nobjects), but
        # we will instead allow the catalog to choose a random galaxy for us.  It will remove any
        # selection effects involved in postage stamp creation using weights that are stored in
        # the catalog.  (If for some reason you prefer not to do that, you can always choose a
        # purely random index yourself using int(ud() * cosmos_cat.nobjects).)  We employ this
        # random selection by simply failing to specify an index or identifier for a galaxy, in
        # which case it chooses a random one.

        # First determine whether we will make a real galaxy (`gal_type = 'real'`) or a parametric
        # galaxy (`gal_type = 'parametric'`).  The real galaxies take longer to render, so for this
        # script, we just use them 30% of the time and use parametric galaxies the other 70%.

        # We could just use `ud()<0.3` for this, but instead we introduce another Deviate type
        # available in GalSim that we haven't used yet: BinomialDeviate.
        # It takes an N and p value and returns integers according to a binomial distribution.
        # i.e. How many heads you get after N flips if each flip has a chance, p, of being heads.
        binom = galsim.BinomialDeviate(ud, N=1, p=0.3)
        real = binom()

        if real:
            # For real galaxies, we will want to whiten the noise in the image (below).
            # When whitening the image, we need to make sure the original correlated noise is
            # present throughout the whole image, otherwise the whitening will do the wrong thing
            # to the parts of the image that don't include the original image.  The RealGalaxy
            # stores the correct noise profile to use as the gal.noise attribute.  This noise
            # profile is automatically updated as we shear, dilate, convolve, etc.  But we need to
            # tell it how large to pad with this noise by hand.  This is a bit complicated for the
            # code to figure out on its own, so we have to supply the size for noise padding
            # with the noise_pad_size parameter.

            # The large galaxies will render fine without any noise padding, but the postage stamp
            # for the smaller galaxies will be sized appropriately for the PSF, which may make the
            # stamp larger than the original galaxy image.  The psf image is 40 x 40, although
            # the bright part is much more concentrated than that.  If we pad out the galaxy image
            # to at least 40 x sqrt(2), we should be safe even if the galaxy image is rotated
            # with respect to the psf image.
            #     noise_pad_size = 40 * sqrt(2) * 0.2 arcsec/pixel = 11.3 arcsec
            gal = cosmos_cat.makeGalaxy(gal_type='real',
                                        rng=ud,
                                        noise_pad_size=11.3)
        else:
            gal = cosmos_cat.makeGalaxy(gal_type='parametric', rng=ud)

        # Apply a random rotation
        theta = ud() * 2.0 * numpy.pi * galsim.radians
        gal = gal.rotate(theta)

        # Rescale the flux to match our telescope configuration.
        # This automatically scales up the noise variance by flux_scaling**2.
        gal *= flux_scaling

        # Apply the cosmological (reduced) shear and magnification at this position using a single
        # GSObject method.
        gal = gal.lens(g1, g2, mu)

        # Convolve with the PSF.
        final = galsim.Convolve(psf, gal)

        # 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)

        # We use method='no_pixel' here because the SDSS PSF image that we are using includes the
        # pixel response already.
        stamp = final.drawImage(wcs=wcs.local(image_pos),
                                offset=offset,
                                method='no_pixel')

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

        # Find the overlapping bounds:
        bounds = stamp.bounds & full_image.bounds

        # Now, if we are using a real galaxy, we want to ether whiten or at least symmetrize the
        # noise on the postage stamp to avoid having to deal with correlated noise in any kind of
        # image processing you would want to do on the final image.  (Like measure galaxy shapes.)

        # Galsim automatically propagates the noise correctly from the initial RealGalaxy object
        # through the applied shear, distortion, rotation, and convolution into the final object's
        # noise attribute.  To make the noise fully white, use the image.whitenNoise() method.
        # The returned value is the variance of the Gaussian noise that is present after the
        # whitening process.

        # However, this is often overkill for many applications.  If it is acceptable to merely end
        # up with noise with some degree of symmetry (say 4-fold or 8-fold symmetry), then you can
        # instead have GalSim just add enough noise to make the resulting noise have this kind of
        # symmetry.  Usually this requires adding significantly less additional noise, which means
        # you can have the resulting total variance be somewhat smaller.  The returned variance
        # corresponds to the zero-lag value of the noise correlation function, which will still have
        # off-diagonal elements.  We can do this step using the image.symmetrizeNoise() method.
        if real:
            if True:
                # We use the symmetrizing option here.
                new_variance = stamp.symmetrizeNoise(final.noise, 8)
            else:
                # Here is how you would do it if you wanted to fully whiten the image.
                new_variance = stamp.whitenNoise(final.noise)

            # We need to keep track of how much variance we have currently in the image, so when
            # we add more noise, we can omit what is already there.
            noise_image[bounds] += new_variance

        # Finally, add the stamp to the full image.
        full_image[bounds] += stamp[bounds]

        time2 = time.time()
        tot_time = time2 - time1
        logger.info('Galaxy %d: position relative to center = %s, t=%f s', k,
                    str(uv_pos), tot_time)

    # We already have some noise in the image, but it isn't uniform.  So the first thing to do is
    # to make the Gaussian noise uniform across the whole image.  We have a special noise class
    # that can do this.  VariableGaussianNoise takes an image of variance values and applies
    # Gaussian noise with the corresponding variance to each pixel.
    # So all we need to do is build an image with how much noise to add to each pixel to get us
    # up to the maximum value that we already have in the image.
    max_current_variance = numpy.max(noise_image.array)
    noise_image = max_current_variance - noise_image
    vn = galsim.VariableGaussianNoise(rng, noise_image)
    full_image.addNoise(vn)

    # Now max_current_variance is the noise level across the full image.  We don't want to add that
    # twice, so subtract off this much from the intended noise that we want to end up in the image.
    noise_variance -= max_current_variance

    # Now add Gaussian noise with this variance to the final image.  We have to do this step
    # at the end, rather than adding to individual postage stamps, in order to get the noise
    # level right in the overlap regions between postage stamps.
    noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance))
    full_image.addNoise(noise)
    logger.info('Added noise to final large image')

    # Now write the image to disk.  It is automatically compressed with Rice compression,
    # since the filename we provide ends in .fz.
    full_image.write(file_name)
    logger.info('Wrote image to %r', file_name)

    # Compute some sky positions of some of the pixels to compare with the values of RA, Dec
    # that ds9 reports.  ds9 always uses (1,1) for the lower left pixel, so the pixel coordinates
    # of these pixels are different by 1, but you can check that the RA and Dec values are
    # the same as what GalSim calculates.
    ra_str = center_ra.hms()
    dec_str = center_dec.dms()
    logger.info('Center of image    is at RA %sh %sm %ss, DEC %sd %sm %ss',
                ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                dec_str[3:5], dec_str[5:])
    for (x, y) in [(0, 0), (0, image_size - 1), (image_size - 1, 0),
                   (image_size - 1, image_size - 1)]:
        world_pos = wcs.toWorld(galsim.PositionD(x, y))
        ra_str = world_pos.ra.hms()
        dec_str = world_pos.dec.dms()
        logger.info('Pixel (%4d, %4d) is at RA %sh %sm %ss, DEC %sd %sm %ss',
                    x, y, ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                    dec_str[3:5], dec_str[5:])
    logger.info(
        'ds9 reports these pixels as (1,1), (1,2048), etc. with the same RA, Dec.'
    )
Пример #10
0
def test_PSE_basic():
    """Basic test of power spectrum estimation.
    """

    # Here are some parameters that define array sizes and other such things.
    array_size = 300
    e_tolerance = 0.10     # 10% error allowed because of finite grid effects, noise fluctuations,
                           # and other things.  This unit test is just for a basic sanity test.
    b_tolerance = 0.15     # B-mode is slightly less accurate.
    zero_tolerance = 0.03  # For power that should be zero

    n_ell = 8
    grid_spacing = 0.1 # degrees
    ps_file = os.path.join(datapath, 'cosmo-fid.zmed1.00.out')
    rand_seed = 2718

    # Begin by setting up the PowerSpectrum and generating shears.
    tab = galsim.LookupTable.from_file(ps_file)
    ps = galsim.PowerSpectrum(tab, units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))

    # Then initialize the PSE object.
    pse = galsim.pse.PowerSpectrumEstimator(N=array_size,
                                            sky_size_deg=array_size*grid_spacing,
                                            nbin=n_ell)

    do_pickle(pse)

    # Estimate the power spectrum using the PSE, without weighting.
    ell, P_e1, P_b1, P_eb1 = pse.estimate(g1, g2)

    # To check: P_E is right (to within the desired tolerance); P_B and P_EB are <1% of P_E.
    P_theory = np.zeros_like(ell)
    for ind in range(len(ell)):
        P_theory[ind] = tab(ell[ind])
    # Note: we don't check the first element because at low ell the tests can fail more
    # spectacularly for reasons that are well understood.
    np.testing.assert_allclose(P_e1[1:], P_theory[1:], rtol=e_tolerance,
                               err_msg='PSE returned wrong E power')
    np.testing.assert_allclose(P_b1[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='PSE found B power')
    np.testing.assert_allclose(P_eb1[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='PSE found EB cross-power')

    # Test theory_func
    ell, P_e1, P_b1, P_eb1, t = pse.estimate(g1, g2, theory_func=tab)
    # This isn't super accurate.  I think just because of binning.  But I'm not sure.
    np.testing.assert_allclose(t, P_theory, rtol=0.3,
                               err_msg='PSE returned wrong theory binning')

    # Also check the case where P_e=P_b.
    ps = galsim.PowerSpectrum(tab, tab, units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))
    ell, P_e2, P_b2, P_eb2 = pse.estimate(g1, g2)
    np.testing.assert_allclose(P_e2[1:], P_theory[1:], rtol=e_tolerance,
                               err_msg='PSE returned wrong E power')
    np.testing.assert_allclose(P_b2[1:], P_theory[1:], rtol=b_tolerance,
                               err_msg='PSE returned wrong B power')
    np.testing.assert_allclose(P_eb2[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='PSE found EB cross-power')

    # And check the case where P_b is nonzero and P_e is zero.
    ps = galsim.PowerSpectrum(e_power_function=None, b_power_function=tab,
                              units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))
    ell, P_e3, P_b3, P_eb3 = pse.estimate(g1, g2)
    np.testing.assert_allclose(P_e3[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='PSE found E power when it should be zero')
    np.testing.assert_allclose(P_b3[1:], P_theory[1:], rtol=b_tolerance,
                               err_msg='PSE returned wrong B power')
    np.testing.assert_allclose(P_eb3[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='PSE found EB cross-power')

    assert_raises(ValueError, pse.estimate, g1[:3,:3], g2)
    assert_raises(ValueError, pse.estimate, g1[:3,:8], g2[:3,:8])
    assert_raises(ValueError, pse.estimate, g1[:8,:8], g2[:8,:8])
Пример #11
0
def test_PSE_weight():
    """Test of power spectrum estimation with weights.
    """
    array_size = 300
    n_ell = 8
    grid_spacing = 0.1
    ps_file = os.path.join(datapath, 'cosmo-fid.zmed1.00.out')
    rand_seed = 2718

    tab = galsim.LookupTable.from_file(ps_file)
    ps = galsim.PowerSpectrum(tab, units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))

    pse = galsim.pse.PowerSpectrumEstimator(N=array_size,
                                            sky_size_deg=array_size*grid_spacing,
                                            nbin=n_ell)

    ell, P_e1, P_b1, P_eb1, P_theory = pse.estimate(g1, g2, weight_EE=True, weight_BB=True,
                                                    theory_func=tab)
    print('P_e1 = ',P_e1)
    print('rel_diff = ',(P_e1-P_theory)/P_theory)
    print('rel_diff using P[1] = ',(P_e1-P_theory)/P_theory[1])
    # The agreement here seems really bad.  Should I not expect these to be closer than this?
    eb_tolerance = 0.4
    zero_tolerance = 0.03

    np.testing.assert_allclose(P_e1[1:], P_theory[1:], rtol=eb_tolerance,
                               err_msg='Weighted PSE returned wrong E power')

    np.testing.assert_allclose(P_b1/P_theory, 0., atol=zero_tolerance,
                               err_msg='Weighted PSE found B power')
    print(P_eb1/P_theory)
    np.testing.assert_allclose(P_eb1/P_theory, 0., atol=zero_tolerance,
                               err_msg='Weighted PSE found EB cross-power')

    # Also check the case where P_e=P_b.
    ps = galsim.PowerSpectrum(tab, tab, units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))
    ell, P_e2, P_b2, P_eb2 = pse.estimate(g1, g2, weight_EE=True, weight_BB=True)
    np.testing.assert_allclose(P_e2[1:], P_theory[1:], rtol=eb_tolerance,
                               err_msg='Weighted PSE returned wrong E power')
    np.testing.assert_allclose(P_b2[1:], P_theory[1:], rtol=eb_tolerance,
                               err_msg='Weighted PSE returned wrong B power')
    np.testing.assert_allclose(P_eb2[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='Weighted PSE found EB cross-power')

    # And check the case where P_b is nonzero and P_e is zero.
    ps = galsim.PowerSpectrum(e_power_function=None, b_power_function=tab,
                              units=galsim.radians)
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))
    ell, P_e3, P_b3, P_eb3 = pse.estimate(g1, g2, weight_EE=True, weight_BB=True)
    np.testing.assert_allclose(P_e3[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='Weighted PSE found E power when it should be zero')
    np.testing.assert_allclose(P_b3[1:], P_theory[1:], rtol=eb_tolerance,
                               err_msg='Weighted PSE returned wrong B power')
    np.testing.assert_allclose(P_eb3[1:]/P_theory[1:], 0., atol=zero_tolerance,
                               err_msg='Weighted PSE found EB cross-power')

    assert_raises(TypeError, pse.estimate, g1, g2, weight_EE=8)
    assert_raises(TypeError, pse.estimate, g1, g2, weight_BB='yes')

    # If N is fairly small, then can get zeros in the counts, which raises an error
    array_size = 5
    g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees,
                          rng=galsim.BaseDeviate(rand_seed))
    pse = galsim.pse.PowerSpectrumEstimator(N=array_size,
                                            sky_size_deg=array_size*grid_spacing,
                                            nbin=n_ell)
    with assert_raises(galsim.GalSimError):
        pse.estimate(g1,g2)
Пример #12
0
def test_PSE_basic():
    """Basic test of power spectrum estimation.
    """
    t1 = time.time()

    # Begin by setting up the PowerSpectrum and generating shears.
    my_tab = galsim.LookupTable(file=ps_file)
    my_ps = galsim.PowerSpectrum(my_tab, units=galsim.radians)
    g1, g2 = my_ps.buildGrid(grid_spacing=grid_spacing,
                             ngrid=array_size,
                             units=galsim.degrees,
                             rng=galsim.BaseDeviate(rand_seed))

    # Then initialize the PSE object.
    my_pse = galsim.pse.PowerSpectrumEstimator(N=array_size,
                                               sky_size_deg=array_size *
                                               grid_spacing,
                                               nbin=n_ell)

    # Estimate the power spectrum using the PSE, without weighting.
    ell, P_e1, P_b1, P_eb1 = my_pse.estimate(g1, g2)

    # To check: P_E is right (to within the desired tolerance); P_B and P_EB are <1% of P_E.
    P_e_theory = np.zeros_like(ell)
    for ind in range(len(ell)):
        P_e_theory[ind] = my_tab(ell[ind])
    # Note: we don't check the first element because at low ell the tests can fail more
    # spectacularly for reasons that are well understood.
    np.testing.assert_array_almost_equal(
        (P_e1[1:] / P_e_theory[1:] - 1.) / (2 * tolerance),
        0.,
        decimal=0,
        err_msg='PSE returned wrong E power')
    np.testing.assert_array_almost_equal(
        (P_b1[1:] / P_e_theory[1:]) / (2 * zero_tolerance),
        0.,
        decimal=0,
        err_msg='PSE found B power')
    np.testing.assert_array_almost_equal(
        (P_eb1[1:] / P_e_theory[1:]) / (2 * zero_tolerance),
        0.,
        decimal=0,
        err_msg='PSE found EB cross-power')

    # Also check the case where P_e=P_b.
    my_ps = galsim.PowerSpectrum(my_tab, my_tab, units=galsim.radians)
    g1, g2 = my_ps.buildGrid(grid_spacing=grid_spacing,
                             ngrid=array_size,
                             units=galsim.degrees,
                             rng=galsim.BaseDeviate(rand_seed))
    ell, P_e2, P_b2, P_eb2 = my_pse.estimate(g1, g2)
    np.testing.assert_array_almost_equal(
        (P_e2[1:] / P_e_theory[1:] - 1.) / (2 * tolerance),
        0.,
        decimal=0,
        err_msg='PSE returned wrong E power')
    np.testing.assert_array_almost_equal(
        (P_b2[1:] / P_e_theory[1:] - 1.) / (2 * tolerance),
        0.,
        decimal=0,
        err_msg='PSE returned wrong B power')
    np.testing.assert_array_almost_equal(
        (P_eb2[1:] / P_e_theory[1:]) / (2 * zero_tolerance),
        0.,
        decimal=0,
        err_msg='PSE found EB cross-power')

    # And check the case where P_b is nonzero and P_e is zero.
    my_ps = galsim.PowerSpectrum(e_power_function=None,
                                 b_power_function=my_tab,
                                 units=galsim.radians)
    g1, g2 = my_ps.buildGrid(grid_spacing=grid_spacing,
                             ngrid=array_size,
                             units=galsim.degrees,
                             rng=galsim.BaseDeviate(rand_seed))
    ell, P_e3, P_b3, P_eb3 = my_pse.estimate(g1, g2)
    np.testing.assert_array_almost_equal(
        (P_e3[1:] / P_e_theory[1:]) / (2 * zero_tolerance),
        0.,
        decimal=0,
        err_msg='PSE found E power when it should be zero')
    np.testing.assert_array_almost_equal(
        (P_b3[1:] / P_e_theory[1:] - 1.) / (2 * tolerance),
        0.,
        decimal=0,
        err_msg='PSE returned wrong B power')
    np.testing.assert_array_almost_equal(
        (P_eb3[1:] / P_e_theory[1:]) / (2 * zero_tolerance),
        0.,
        decimal=0,
        err_msg='PSE found EB cross-power')

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Пример #13
0
def test_tabulated():
    """Test using a LookupTable to interpolate a P(k) that is known at certain k"""
    import time
    t1 = time.time()

    # make PowerSpectrum with some obvious analytic form, P(k)=k^2
    ps_analytic = galsim.PowerSpectrum(pk2)

    # now tabulate that analytic form at a range of k
    k_arr = 0.01*np.arange(10000.)+0.01
    p_arr = k_arr**(2.)

    # make a LookupTable to initialize another PowerSpectrum
    tab = galsim.LookupTable(k_arr, p_arr)
    ps_tab = galsim.PowerSpectrum(tab)

    # draw shears on a grid from both PowerSpectrum objects, with same random seed
    seed = 12345
    g1_analytic, g2_analytic = ps_analytic.buildGrid(grid_spacing = 1., ngrid = 10,
                                                     rng = galsim.BaseDeviate(seed))
    g1_tab, g2_tab = ps_tab.buildGrid(grid_spacing = 1., ngrid = 10, rng = galsim.BaseDeviate(seed))

    # make sure that shears that are drawn are essentially identical
    np.testing.assert_almost_equal(g1_analytic, g1_tab, 6,
        err_msg = "g1 of shear field from tabulated P(k) differs from expectation!")
    np.testing.assert_almost_equal(g2_analytic, g2_tab, 6,
        err_msg = "g2 of shear field from tabulated P(k) differs from expectation!")
    # now check that we get the same answer if we use file readin: write k and P(k) to a file then
    # initialize LookupTable from that file
    data_all = (k_arr, p_arr)
    data = np.column_stack(data_all)
    filename = 'lensing_reference_data/tmp.txt'
    np.savetxt(filename, data)
    tab2 = galsim.LookupTable(file = filename)
    ps_tab2 = galsim.PowerSpectrum(tab2)
    g1_tab2, g2_tab2 = ps_tab2.buildGrid(grid_spacing = 1., ngrid = 10,
                                         rng = galsim.BaseDeviate(seed))
    np.testing.assert_almost_equal(g1_analytic, g1_tab2, 6,
        err_msg = "g1 from file-based tabulated P(k) differs from expectation!")
    np.testing.assert_almost_equal(g2_analytic, g2_tab2, 6,
        err_msg = "g2 from file-based tabulated P(k) differs from expectation!")
    # check that we get the same answer whether we use interpolation in log for k, P, or both
    tab = galsim.LookupTable(k_arr, p_arr, x_log = True)
    ps_tab = galsim.PowerSpectrum(tab)
    g1_tab, g2_tab = ps_tab.buildGrid(grid_spacing = 1., ngrid = 10, rng = galsim.BaseDeviate(seed))
    np.testing.assert_almost_equal(g1_analytic, g1_tab, 6,
        err_msg = "g1 of shear field from tabulated P(k) with x_log differs from expectation!")
    np.testing.assert_almost_equal(g2_analytic, g2_tab, 6,
        err_msg = "g2 of shear field from tabulated P(k) with x_log differs from expectation!")
    tab = galsim.LookupTable(k_arr, p_arr, f_log = True)
    ps_tab = galsim.PowerSpectrum(tab)
    g1_tab, g2_tab = ps_tab.buildGrid(grid_spacing = 1., ngrid = 10, rng = galsim.BaseDeviate(seed))
    np.testing.assert_almost_equal(g1_analytic, g1_tab, 6,
        err_msg = "g1 of shear field from tabulated P(k) with f_log differs from expectation!")
    np.testing.assert_almost_equal(g2_analytic, g2_tab, 6,
        err_msg = "g2 of shear field from tabulated P(k) with f_log differs from expectation!")
    tab = galsim.LookupTable(k_arr, p_arr, x_log = True, f_log = True)
    ps_tab = galsim.PowerSpectrum(tab)
    g1_tab, g2_tab = ps_tab.buildGrid(grid_spacing = 1., ngrid = 10, rng = galsim.BaseDeviate(seed))
    np.testing.assert_almost_equal(g1_analytic, g1_tab, 6,
        err_msg="g1 of shear field from tabulated P(k) with x_log, f_log differs from expectation!")
    np.testing.assert_almost_equal(g2_analytic, g2_tab, 6,
        err_msg="g2 of shear field from tabulated P(k) with x_log, f_log differs from expectation!")

    # check for appropriate response to inputs when making/using LookupTable
    try:
        ## mistaken interpolant choice
        np.testing.assert_raises(ValueError, galsim.LookupTable,
                                 k_arr, p_arr, interpolant='splin')
        ## k, P arrays not the same size
        np.testing.assert_raises(ValueError, galsim.LookupTable,
                                 0.01*np.arange(100.), p_arr)
        ## arrays too small
        np.testing.assert_raises(RuntimeError, galsim.LookupTable,
                                 (1.,2.), (1., 2.))
        ## try to make shears, but grid includes k values that were not part of the originally
        ## tabulated P(k) (for this test we make a stupidly limited k grid just to ensure that an
        ## exception should be raised)
        t = galsim.LookupTable((0.99,1.,1.01),(0.99,1.,1.01))
        ps = galsim.PowerSpectrum(t)
        np.testing.assert_raises(ValueError, ps.buildGrid, grid_spacing=1., ngrid=100)
        ## try to interpolate in log, but with zero values included
        np.testing.assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.),
                                 x_log=True)
        np.testing.assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.),
                                 f_log=True)
        np.testing.assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.),
                                 x_log=True, f_log=True)
    except ImportError:
        pass

    # check that when calling LookupTable, the outputs have the same form as inputs
    tab = galsim.LookupTable(k_arr, p_arr)
    k = 0.5
    assert type(tab(k)) == float
    k = (0.5, 1.5)
    result = tab(k)
    assert type(result) == tuple and len(result) == len(k)
    k = list(k)
    result = tab(k)
    assert type(result) == list and len(result) == len(k)
    k = np.array(k)
    result = tab(k)
    assert type(result) == np.ndarray and len(result) == len(k)
    k = 0.01+np.zeros((2,2))
    result = tab(k)
    assert type(result) == np.ndarray and result.shape == k.shape

    # check for expected behavior with log interpolation
    k = (1., 2., 3.)
    p = (1., 4., 9.)
    t = galsim.LookupTable(k, p, interpolant = 'linear')
    ## a linear interpolant should fail here because P(k) is a power-law, so make sure we get the
    ## expected result with linear interpolation
    np.testing.assert_almost_equal(t(2.5), 13./2., decimal = 6,
        err_msg = 'Unexpected result for linear interpolation of power-law')
    ## but a linear interpolant works if you work in log space, so check against real result
    t = galsim.LookupTable(k, p, interpolant = 'linear', x_log = True, f_log = True)
    np.testing.assert_almost_equal(t(2.5), 2.5**2, decimal = 6,
        err_msg = 'Unexpected result for linear interpolation of power-law in log space')

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #14
0
    def generateCatalog(self, rng, catalog, parameters, offsets, subfield_index):
        """For a galaxy catalog with positions included, determine the lensing shear and
        magnification to assign to each galaxy in the catalog."""
        # We need a cache for a grid of shear values covering the entire field, i.e., including all
        # possible positions in all subfields (modulo sub-pixel offsets from the subfield grid -
        # we're not trying to take those into account).  If there is nothing in the cache for this
        # field, then make a new grid and save it in the cache.
        #
        ps_tab = parameters["ps_tab"]
        ps_nuisance = parameters["ps_nuisance"]
        if ps_tab != self.cached_ps_tab:
            # If nothing is cached for this power spectrum, then first we have to define the power
            # spectrum in a way that the galsim lensing engine can use it.
            # Begin by identifying and reading in the proper files for the cosmological part of the
            # power spectrum.
            file_index = np.floor(ps_tab)
            residual = ps_tab - file_index
            import os
            infile1 = os.path.join(self.ps_dir , 
                                   self.infile_pref + self.zmed_str[int(file_index)]+'.out')
            data1 = np.loadtxt(infile1).transpose()
            ell = data1[0]
            p1 = data1[1]
            infile2 = os.path.join(self.ps_dir , 
                                   self.infile_pref + self.zmed_str[int(file_index)+1]+'.out')
            data2 = np.loadtxt(infile2).transpose()
            p2 = data2[1]
            # Now make a geometric mean to get the cosmological power spectrum.
            p_cos = (p1**(1.-residual))*(p2**residual)
            p_cos *= self.mult_factor
            # Construct the shapelets nuisance functions
            x = np.log10(ell/self.ell_piv)
            n_ell = len(ell)
            b_values = np.zeros((self.max_order, n_ell))
            for order in range(0, self.max_order):
                b_values[order,:] = self._bn(order, x, self.beta)
            nuisance_func = np.zeros(n_ell)
            for order in range(0, self.max_order):
                nuisance_func += ps_nuisance[order]*b_values[order,:]
            p_use = p_cos*(1.0+nuisance_func)
            # Note: units for ell, p_use are 1/radians and radians^2, respectively.

            # Now, we have arrays we can use to make a power spectrum object with E-mode power
            # only.  While we are at it, we cache it and its parameters.
            ps_lookup = galsim.LookupTable(ell, p_use, x_log=True, f_log=True)
            self.cached_ps = galsim.PowerSpectrum(ps_lookup, units = galsim.radians)
            self.cached_ps_tab = ps_tab
            self.cached_ps_nuisance = ps_nuisance

            # Define the grid on which we want to get shears.
            # This is a little tricky: we have a setup for subfield locations within the field that
            # is defined in builder.py function generateSubfieldOffsets().  The first subfield is
            # located at the origin, and to represent it alone, we would need a constants.nrows x
            # constants.ncols grid of shears.  But since we subsample by a parameter given as
            # constants.subfield_grid_subsampling, each grid dimension must be larger by that
            # amount.
            if constants.nrows != constants.ncols:
                raise NotImplementedError("Currently variable shear grids require nrows=ncols")
            n_grid = constants.subfield_grid_subsampling * constants.nrows
            grid_spacing = constants.image_size_deg / n_grid

            # Run buildGrid() to get the shears and convergences on this grid.  However, we also
            # want to effectively change the value of k_min that is used for the calculation, to get
            # a reasonable shear correlation function on large scales without excessive truncation.
            # We also define a grid center such that the position of the first pixel is (0,0).
            grid_center = 0.5 * (constants.image_size_deg - grid_spacing)
            self.cached_ps.buildGrid(grid_spacing = grid_spacing,
                                     ngrid = n_grid,
                                     units = galsim.degrees,
                                     rng = rng,
                                     center = (grid_center, grid_center),
                                     kmin_factor=3)
            # Now that our cached PS has a grid of shears / convergences, we can use getLensing() to
            # get the quantities we need for a lensing measurement at any position, so this part of
            # the calculation is done.

        # Now get the shears/convergences for each galaxy position in the
        # catalog.  This is fastest if done all at once, with one call to getLensing().  And this is
        # actually slightly tricky, because we have to take into account: 
        #    (1) The position of the galaxy within the subfield.
        #    (2) The offset of the subfield with respect to the field.
        # And make sure we've gotten the units right for both of these.  We are ignoring centroid
        # shifts of order 1 pixel (max 0.2" for ground data) which can occur within an image.
        #
        # We can define object indices in x, y directions - i.e., make indices that range
        # from 0 to constants.nrows-1.
        xsize = constants.xsize[self.obs_type][self.multiepoch]
        ysize = constants.ysize[self.obs_type][self.multiepoch]
        x_ind = (catalog["x"]+1+0.5*xsize)/xsize-1
        y_ind = (catalog["y"]+1+0.5*ysize)/ysize-1
        # Turn this into (x, y) positions within the subfield, in degrees.
        x_pos = x_ind * constants.image_size_deg / constants.nrows
        y_pos = y_ind * constants.image_size_deg / constants.ncols
        # But now we have to add the subfield offset.  These are calculated as a fraction of the
        # separation between galaxies, so we have to convert to degrees.
        x_pos += offsets[0] * constants.image_size_deg / constants.nrows
        y_pos += offsets[1] * constants.image_size_deg / constants.ncols
        catalog["g1"], catalog["g2"], catalog["mu"] = \
            self.cached_ps.getLensing(pos=(x_pos, y_pos), units=galsim.degrees)
        # Previous numbers were in degrees.  But now we need to save some numbers for ID generation,
        # which have to be ints.  So we will save them in units of subfield grid spacing, i.e.,
        # within a given subfield, galaxies are spaced by constants.subfield_grid_subsampling.
        # Right now x_ind, y_ind are integers (spaced by 1) and offsets[0] and offsets[1] span the
        # range (0, 1/constants.subfield_grid_subsampling), so the line below has
        # constants.subfield_grid_subsampling multiplying both.
        catalog["x_field_pos"] = np.round(
            constants.subfield_grid_subsampling * (offsets[0] + x_ind)).astype(int)
        catalog["y_field_pos"] = np.round(
            constants.subfield_grid_subsampling * (offsets[1] + y_ind)).astype(int)
        for record in catalog:
            record["ID"] = 1e6*subfield_index + 1e3*record["x_field_pos"] + record["y_field_pos"]
Пример #15
0
n_realization = 1000
pkfile = 'ps.wmap7lcdm.2000.append0.dat'
n_ell = 15
grid_nx = 50
theta = 10. # degrees
dtheta = theta/grid_nx # degrees
outfile = 'output/ps.results.input_pe.pse.dat'

ellvals = np.zeros(n_ell)
p_e = np.zeros((n_ell, n_realization))
p_b = np.zeros((n_ell, n_realization))
p_eb = np.zeros((n_ell, n_realization))

tab = galsim.LookupTable(file=pkfile, interpolant='linear', x_log=True, f_log=True)
test_ps_e=galsim.PowerSpectrum(e_power_function = tab, units='radians')
test_ps_b=galsim.PowerSpectrum(b_power_function = tab, units='radians')
test_ps_eb=galsim.PowerSpectrum(e_power_function = tab, b_power_function = tab, units='radians')

my_pse = pse.PowerSpectrumEstimator(grid_nx, theta, n_ell)

for ireal in range(n_realization):
    print "Iteration ",ireal

    print "Getting shears on a grid"
    g1, g2 = test_ps_e.buildGrid(grid_spacing=dtheta, ngrid=grid_nx, units=galsim.degrees)
    this_ell, this_pe, this_pb, this_peb, this_theory = my_pse.estimate(g1, g2, theory_func=tab)

    ellvals = this_ell
    p_e[:,ireal] = this_pe
    p_b[:,ireal] = this_pb
Пример #16
0
    dudx = np.cos(pos_th.rad()) * pixel_scale
    dudy = -np.sin(pos_th.rad()) * pixel_scale
    dvdx = np.sin(pos_th.rad()) * pixel_scale
    dvdy = np.cos(pos_th.rad()) * pixel_scale
    affine = galsim.AffineTransform(dudx,
                                    dudy,
                                    dvdx,
                                    dvdy,
                                    origin=ps_image.trueCenter())
    sky_center = galsim.CelestialCoord(ra=ra_cen_ps * galsim.degrees,
                                       dec=dec_cen_ps * galsim.degrees)
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    ps_image.wcs = wcs

    rng_ps = galsim.BaseDeviate(ps_seed)
    ps = galsim.PowerSpectrum(ps_file, units=galsim.radians)
    ps.buildGrid(
        grid_spacing=grid_spacing,
        ngrid=int(1 + np.ceil(ps_image_size * pixel_scale / grid_spacing)),
        rng=rng_ps.duplicate())
elif shear_method == 'constant':
    reduced_shear = np.sqrt(reduced_g1**2 + reduced_g2**2)
    logger.info(
        "    Background shear field is assumed to be constant |g|=%.4f" %
        reduced_shear)
    g1[:ngal], g2[:ngal] = reduced_g1, reduced_g2
    #gt[:ngal] = reduced_shear
    #beta[:ngal] = imk.theta(ngal,seed=beta_seed)
else:  # shear_method=="extra":
    logger.info("    Background shear is from extra catalog %s." %
                shear_catn.split("/")[-1])
Пример #17
0
def getAtmosPSFGrid(k, Pk, ngrid=20, dtheta_arcsec=360., kmin_factor=15,
                    subsample=10, rng=None, return_basic=True):
    """A routine to build an anisotropy and size fluctuation grid for the atmospheric PSF P(k).

    This routine takes NumPy arrays of k and P(k) values to be used for the E and B power for the
    atmospheric PSF (same P(k) for E and B), and returns grids of atmospheric e1, e2, and fractional
    fluctuations in size.

    Note that the input power spectra are for the ellipticity (distortion) fluctuations.  The
    lensing engine works in terms of shear, and for nearly round objects, shear~distortion/2.  Since
    we gave the lensing engine the PS of distortion fluctuations, the return values can be used
    directly for PSF ellipticity (distortion), but the kappa values are too high by a factor of 2.

    The routine requires k and Pk, and has a number of optional parameters that default to what will
    be used for GREAT3:

       ngrid           Number of grid points for galaxies in one dimension (default 20, for the 2x2
                       degree subfields used for PSF estimation).
       dtheta_arcsec   Spacing between grid points for the galaxies in arcsec (default 360, i.e.,
                       0.1 degree).
       kmin_factor     Factor by which to spatially extend the grids to get a smaller kmin value,
                       growing them larger by this factor in each dimension so as to properly
                       represent the large-scale shear correlations (default 20).
       subsample          Factor by which to subsample the grid, so as to get several offset
                          realizations of the same atmosphere (default 10).  [Note, this is like
                          kmax_factor for the lensing engine work on GalSim issue #377, but there,
                          the idea is to represent smaller kmax without actually getting back a more
                          densely packed grid.  Here, we actually do want a more densely packed
                          grid, so I'm calling it subsample to suggest the explicit subsampling of
                          the grid.
       rng                RNG to use for the generation of these fields.
       return_basic       Return the basic grid that is not enlarged by kmin_factor (if True), or
                          actually return the huge grid (if False).  [Default = True]

    The results are returned as three NumPy arrays for the PSF e1, PSF e2, and fractional change in
    size of the PSF.
    """
    # Set up the PowerSpectrum object.
    tab_pk = galsim.LookupTable(k, Pk, x_log=True, f_log=True)
    ps = galsim.PowerSpectrum(tab_pk, tab_pk, units=galsim.arcsec)

    # Use buildGrid() to get the grid.  Note that with the code in GalSim issue #377, this code could
    # be simplified.  It automatically takes care of the `kmin_factor` expansion of the grid.  This
    # would also lead to simplification of the code below where the return values are selected based
    # on return_basic.
    e1, e2, kappa = ps.buildGrid(grid_spacing = dtheta_arcsec/subsample,
                                 ngrid = ngrid*kmin_factor*subsample,
                                 get_convergence = True,
                                 rng = rng)
    # Redefine the kappa's
    kappa /= 2.

    # Take subset of the overly huge grid.  Since the grid is periodic, can just take one corner, don't
    # have to try to be in the middle.
    if return_basic is False:
        # make grid positions
        ntot = ngrid*kmin_factor*subsample
        grid_spacing = dtheta_arcsec/subsample/60.
        min = (-ntot/2 + 0.5) * grid_spacing
        max = (ntot/2 - 0.5) * grid_spacing
        x, y = np.meshgrid(np.arange(min,max+grid_spacing,grid_spacing),
                           np.arange(min,max+grid_spacing,grid_spacing))

        return e1, e2, kappa, x, y
    else:
        n_use = ngrid*subsample
        grid_spacing = dtheta_arcsec/subsample/60.
        min = (-n_use/2 + 0.5) * grid_spacing
        max = (n_use/2 - 0.5) * grid_spacing
        x, y = np.meshgrid(np.arange(min,max+grid_spacing,grid_spacing),
                           np.arange(min,max+grid_spacing,grid_spacing))
        return e1[0:n_use, 0:n_use], e2[0:n_use, 0:n_use], kappa[0:n_use, 0:n_use], x-min, y-min
Пример #18
0
def test_shear_variance():
    """Test that shears from several toy power spectra have the expected variances."""
    import time
    t1 = time.time()

    # setup the random number generator to use for these tests
    rng = galsim.BaseDeviate(512342)

    # set up grid parameters
    grid_size = 50. # degrees
    ngrid = 500 # grid points
    klim = klim_test
    # now get derived grid parameters
    kmin = 2.*np.pi/grid_size/3600. # arcsec^-1

    # Make a flat power spectrum for E, B modes with P=1 arcsec^2, truncated above some limiting
    # value of k.
    # Given our grid size of 50 degrees [which is silly to do for a flat-sky approximation, but
    # we're just doing it anyway to beat down the noise], the minimum k we can probe is 2pi/50
    # deg^{-1} = 3.49e-5 arcsec^-1.  With 500 grid points, the maximum k in one dimension is 250
    # times as large, 0.00873 arcsec^-1.  The function pk_flat_lim is 0 for k>klim_test=0.00175 which
    # is a factor of 5 below our maximum k, a factor of ~50 above our minimum k.  For k<=0.00175,
    # pk_flat_lim returns 1.
    test_ps = galsim.PowerSpectrum(e_power_function=pk_flat_lim, b_power_function=pk_flat_lim)
    # get shears on 500x500 grid with spacing 0.1 degree
    g1, g2 = test_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, rng=rng,
                               units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    # Now we should compare the variance with the predictions.  We use
    # ../devel/modules/lensing_engine.pdf section 5.3 to get
    # Var(g1) + Var(g2) = (1/pi^2) [(pi klim^2 / 4) - kmin^2]
    # Here the 1 on top of the pi^2 is actually P0 which has units of arcsec^2.
    # A final point for this test is that the result should be twice as large than that prediction
    # since we have both E and B power.  And we know from before that due to various effects, the
    # results should actually be ~1.5% too low.
    predicted_variance = (1./np.pi**2)*(0.25*np.pi*(klim**2) - kmin**2)
    predicted_variance *= 2
    var1 = np.var(g1)
    var2 = np.var(g2)
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from flat power spectrum!"
        
    # check: are g1, g2 uncorrelated with each other?
    top= np.sum((g1-np.mean(g1))*(g2-np.mean(g2)))
    bottom1 = np.sum((g1-np.mean(g1))**2)
    bottom2 = np.sum((g2-np.mean(g2))**2)
    corr = top / np.sqrt(bottom1*bottom2)
    np.testing.assert_almost_equal(
        corr, 0., decimal=1,
        err_msg="Shear components should be uncorrelated with each other! (flat power spectrum)")

    # Now do the same test as previously, but with E-mode power only.
    test_ps = galsim.PowerSpectrum(e_power_function=pk_flat_lim)
    g1, g2 = test_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, rng=rng,
                               units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    predicted_variance = (1./np.pi**2)*(0.25*np.pi*(klim**2) - kmin**2)
    var1 = np.var(g1)
    var2 = np.var(g2)
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from flat E-mode power spectrum!"

    # check: are g1, g2 uncorrelated with each other?
    top= np.sum((g1-np.mean(g1))*(g2-np.mean(g2)))
    bottom1 = np.sum((g1-np.mean(g1))**2)
    bottom2 = np.sum((g2-np.mean(g2))**2)
    corr = top / np.sqrt(bottom1*bottom2)
    np.testing.assert_almost_equal(
        corr, 0., decimal=1,
        err_msg="Shear components should be uncorrelated with each other! (flat E-mode power spec.)")

    # check for proper scaling with grid spacing, for fixed number of grid points
    grid_size = 25. # degrees
    ngrid = 500 # grid points
    klim = klim_test
    kmin = 2.*np.pi/grid_size/3600. # arcsec^-1
    test_ps = galsim.PowerSpectrum(e_power_function=pk_flat_lim, b_power_function=pk_flat_lim)
    g1, g2 = test_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, rng=rng,
                               units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    predicted_variance = (1./np.pi**2)*(0.25*np.pi*(klim**2) - kmin**2)
    predicted_variance *= 2
    var1 = np.var(g1)
    var2 = np.var(g2)
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from flat power spectrum with smaller grid_size"

    # check for proper scaling with number of grid points, for fixed grid spacing
    grid_size = 25. # degrees
    ngrid = 250 # grid points
    klim = klim_test 
    kmin = 2.*np.pi/grid_size/3600. # arcsec^-1
    test_ps = galsim.PowerSpectrum(e_power_function=pk_flat_lim, b_power_function=pk_flat_lim)
    g1, g2 = test_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, rng=rng,
                               units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    predicted_variance = (1./np.pi**2)*(0.25*np.pi*(klim**2) - kmin**2)
    predicted_variance *= 2
    var1 = np.var(g1)
    var2 = np.var(g2)
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from flat power spectrum with smaller ngrid"

    # Test one other theoretical PS: the Gaussian P(k).
    # We define it as P(k) = exp(-s^2 k^2 / 2).
    # First set up the grid.
    grid_size = 50. # degrees
    ngrid = 500 # grid points
    kmin = 2.*np.pi/grid_size/3600.
    kmax = np.pi/(grid_size/ngrid)/3600.
    # For explanation of these two variables, see below, the comment starting "Note: the next..."
    # These numbers are, however, hard-coded up here with the grid parameters because if the grid is
    # changed, the erfmax and erfmin must change.
    erfmax = 0.9875806693484477
    erfmin = 0.007978712629263206
    # Now choose s such that s*kmax=2.5, i.e., very little power at kmax.
    s = 2.5/kmax
    test_ps = galsim.PowerSpectrum(lambda k : np.exp(-0.5*((s*k)**2)))
    g1, g2 = test_ps.buildGrid(grid_spacing = grid_size/ngrid, ngrid=ngrid, rng=rng,
                               units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    # For this case, the prediction for the variance is:
    # Var(g1) + Var(g2) = [1/(2 pi s^2)] * ( (Erf(s*kmax/sqrt(2)))^2 - (Erf(s*kmin/sqrt(2)))^2 )
    # Note: the next two lines of code are commented out because math.erf is not available in python
    # v2.6, with which we must be compatible.  So instead the values of erf are hard-coded (above)
    # based on the calculations from a machine that has python v2.7.  The implications here are that
    # if one changes the grid parameters for this test in a way that also changes the values of
    # these Erf[...] calculations, then the hard-coded erfmax and erfmin must be changed.
    # erfmax = math.erf(s*kmax/math.sqrt(2.))
    # erfmin = math.erf(s*kmin/math.sqrt(2.))
    var1 = np.var(g1)
    var2 = np.var(g2)
    predicted_variance = (erfmax**2 - erfmin**2) / (2.*np.pi*(s**2))
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from Gaussian power spectrum"

    # check for proper scaling with grid spacing, for fixed number of grid points
    grid_size = 25. # degrees
    ngrid = 500 # grid points
    kmin = 2.*np.pi/grid_size/3600.
    kmax = np.pi/(grid_size/ngrid)/3600.
    s = 2.5/kmax
    # Note that because of how s, kmin, and kmax change, the Erf[...] quantities do not change.  So
    # we don't have to reset the values here.
    test_ps = galsim.PowerSpectrum(lambda k : np.exp(-0.5*((s*k)**2)))
    g1, g2 = test_ps.buildGrid(grid_spacing = grid_size/ngrid, ngrid=ngrid,
                               rng=rng, units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    var1 = np.var(g1)
    var2 = np.var(g2)
    predicted_variance = (erfmax**2 - erfmin**2) / (2.*np.pi*(s**2))
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from Gaussian power spectrum with smaller grid_size"

    # check for proper scaling with number of grid points, for fixed grid spacing
    grid_size = 25. # degrees
    ngrid = 250 # grid points
    kmin = 2.*np.pi/grid_size/3600.
    kmax = np.pi/(grid_size/ngrid)/3600.
    # Here one of the Erf[...] values does change.
    erfmin = 0.01595662743380396
    s = 2.5/kmax
    test_ps = galsim.PowerSpectrum(lambda k : np.exp(-0.5*((s*k)**2)))
    g1, g2 = test_ps.buildGrid(grid_spacing = grid_size/ngrid, ngrid=ngrid,
                               rng=rng, units=galsim.degrees)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    var1 = np.var(g1)
    var2 = np.var(g2)
    predicted_variance = (erfmax**2 - erfmin**2) / (2.*np.pi*(s**2))
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.015 * predicted_variance, \
            "Incorrect shear variance from Gaussian power spectrum with smaller ngrid"

    # change grid spacing implicitly via kmax_factor
    # This and the next test can be made at higher precision (0.5% rather than 1.5%), since the
    # grids actually used to make the shears have more points, so they are more accurate.
    grid_size = 50. # degrees
    ngrid = 500 # grid points
    kmax_factor = 2
    kmin = 2.*np.pi/grid_size/3600.
    kmax = np.pi/(grid_size/ngrid)/3600.*kmax_factor
    # Back to the original erfmin value.
    erfmin = 0.007978712629263206
    s = 2.5/kmax
    test_ps = galsim.PowerSpectrum(lambda k : np.exp(-0.5*((s*k)**2)))
    g1, g2 = test_ps.buildGrid(grid_spacing = grid_size/ngrid, ngrid=ngrid,
                               rng=rng, units=galsim.degrees, kmax_factor=kmax_factor)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    var1 = np.var(g1)
    var2 = np.var(g2)
    predicted_variance = (erfmax**2 - erfmin**2) / (2.*np.pi*(s**2))
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.005 * predicted_variance, \
            "Incorrect shear variance from Gaussian power spectrum with kmax_factor=2"

    # change ngrid implicitly with kmin_factor
    grid_size = 50. # degrees
    ngrid = 500 # grid points
    kmin_factor = 2
    kmin = 2.*np.pi/grid_size/3600./kmin_factor
    kmax = np.pi/(grid_size/ngrid)/3600.
    s = 2.5/kmax
    # This time, erfmin is smaller.
    erfmin = 0.003989406181481644
    test_ps = galsim.PowerSpectrum(lambda k : np.exp(-0.5*((s*k)**2)))
    g1, g2 = test_ps.buildGrid(grid_spacing = grid_size/ngrid, ngrid=ngrid,
                               rng=rng, units=galsim.degrees, kmin_factor=kmin_factor)
    assert g1.shape == (ngrid, ngrid)
    assert g2.shape == (ngrid, ngrid)
    var1 = np.var(g1)
    var2 = np.var(g2)
    predicted_variance = (erfmax**2 - erfmin**2) / (2.*np.pi*(s**2))
    print 'predicted variance = ',predicted_variance
    print 'actual variance = ',var1+var2
    print 'fractional diff = ',((var1+var2)/predicted_variance-1)
    assert np.abs((var1+var2) - predicted_variance) < 0.005 * predicted_variance, \
            "Incorrect shear variance from Gaussian power spectrum with kmin_factor=2"

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #19
0
do_shear = True
# Also we'll make an option to just use some of the grid, to save time.
use_subgrid = True
if do_shear:
    for t0 in theta0:
        print "Getting shears using GalSim for theta0=",t0
        strval = str(int(round(t0)))
        infile = 'pk_math/Pk'+strval+'.dat'
        pk_dat = np.loadtxt(infile).transpose()
        k = pk_dat[0]
        pk = 1.e-4*2.*np.pi*pk_dat[1] # put in the normalization here

        tab_pk = galsim.LookupTable(k, 0.5*pk, x_log=True, f_log=True)
        # Take the outputs and run corr2 to check that the outputs correspond to inputs.  Use 0.5*P
        # but give that to both P_E and P_B.
        ps = galsim.PowerSpectrum(tab_pk, tab_pk, units=galsim.arcsec)

        # Note, typically the buildGrid() method returns shear, not distortion e.  For nearly round
        # things, e~2*shear.  But for the atmospheric PSF stuff, I gave the code amplitudes
        # corresponding to some ellipticity variance, so the results should correspond to e1/e2 in
        # amplitude as well.  The alternative approach would have been to use shear variances
        # (factor of 4 lower) and then to take the e that come out and multiply by 2.  This seemed
        # silly so I didn't do it.  However, it does mean that the kappa fluctuations are too high
        # by a factor of 4, so I must reduce them.
        e1, e2, kappa = ps.buildGrid(grid_spacing=dtheta_arcsec,
                                     ngrid=ngrid,
                                     get_convergence=True)
        kappa /= 4.
        grid_range = (dtheta_arcsec) * np.arange(ngrid)
        x, y = np.meshgrid(grid_range, grid_range)
        if not use_subgrid:
Пример #20
0
def main(argv):
    """
    Make images using variable PSF and shear:
      - The main image is 10 x 10 postage stamps.
      - Each postage stamp is 48 x 48 pixels.
      - The second HDU has the corresponding PSF image.
      - Applied shear is from a power spectrum P(k) ~ k^1.8.
      - Galaxies are real galaxies oriented in a ring test of 20 each.
      - The PSF is Gaussian with FWHM, ellipticity and position angle functions of (x,y)
      - Noise is Poisson using a nominal sky value of 1.e6.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo10")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    n_tiles = 10  # number of tiles in each direction.
    stamp_size = 48  # pixels

    pixel_scale = 0.44  # arcsec / pixel
    sky_level = 1.e6  # ADU / arcsec^2

    # The random seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 3339201

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

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

    # These will be created for each object below.  The values we'll use will be functions
    # of (x,y) relative to the center of the image.  (r = sqrt(x^2+y^2))
    # psf_fwhm = 0.9 + 0.5 * (r/100)^2  -- arcsec
    # psf_e = 0.4 * (r/100)^1.5         -- large value at the edge, so visible by eye.
    # psf_beta = atan2(y/x) + pi/2      -- tangential pattern

    gal_dilation = 3  # Make the galaxies a bit larger than their original size.
    gal_signal_to_noise = 100  # Pretty high.
    psf_signal_to_noise = 1000  # Even higher.

    logger.info('Starting demo script 10')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_example.fits'
    dir = 'data'
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # List of IDs to use.  We select 5 particularly irregular galaxies for this demo.
    # Then we'll choose randomly from this list.
    id_list = [106416, 106731, 108402, 116045, 116448]

    # Make the 5 galaxies we're going to use here rather than remake them each time.
    # This means the Fourier transforms of the real galaxy images don't need to be recalculated
    # each time, so it's a bit more efficient.
    gal_list = [
        galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list
    ]

    # Make the galaxies a bit larger than their original observed size.
    gal_list = [gal.dilate(gal_dilation) for gal in gal_list]

    # Setup the PowerSpectrum object we'll be using:
    ps = galsim.PowerSpectrum(lambda k: k**1.8)
    # The argument here is "e_power_function" which defines the E-mode power to use.

    # There is also a b_power_function if you want to include any B-mode power:
    #     ps = galsim.PowerSpectrum(e_power_function, b_power_function)

    # You may even omit the e_power_function argument and have a pure B-mode power spectrum.
    #     ps = galsim.PowerSpectrum(b_power_function = b_power_function)

    # All the random number generator classes derive from BaseDeviate.
    # When we construct another kind of deviate class from any other
    # kind of deviate class, the two share the same underlying random number
    # generator.  Sometimes it can be clearer to just construct a BaseDeviate
    # explicitly and then construct anything else you need from that.
    # Note: A BaseDeviate cannot be used to generate any values.  It can
    # only be used in the constructor for other kinds of deviates.
    # The seeds for the objects are random_seed..random_seed+nobj-1 (which comes later),
    # so use the next one.
    nobj = n_tiles * n_tiles
    rng = galsim.BaseDeviate(random_seed + nobj)

    # Setup the images:
    gal_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)
    psf_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)

    # Update the image WCS to use the image center as the origin of the WCS.
    # The class that acts like a PixelScale except for this offset is called OffsetWCS.
    im_center = gal_image.bounds.trueCenter()
    wcs = galsim.OffsetWCS(scale=pixel_scale, origin=im_center)
    gal_image.wcs = wcs
    psf_image.wcs = wcs

    # We will place the tiles in a random order.  To do this, we make two lists for the
    # ix and iy values.  Then we apply a random permutation to the lists (in tandem).
    ix_list = []
    iy_list = []
    for ix in range(n_tiles):
        for iy in range(n_tiles):
            ix_list.append(ix)
            iy_list.append(iy)
    # This next function will use the given random number generator, rng, and use it to
    # randomly permute any number of lists.  All lists will have the same random permutation
    # applied.
    galsim.random.permute(rng, ix_list, iy_list)

    # Now have the PowerSpectrum object build a grid of shear values for us to use.
    # Also, because of some technical details about how the config stuff handles the random
    # number generator here, we need to duplicate the rng object if we want to have the
    # two output files match.  This means that technically, the same sequence of random numbers
    # will be used in building the grid as will be used by the other uses of rng (permuting the
    # postage stamps and adding noise).  But since they are used in such completely different
    # ways, it is hard to imagine how this could lead to any kind of bias in the images.
    grid_g1, grid_g2 = ps.buildGrid(grid_spacing=stamp_size * pixel_scale,
                                    ngrid=n_tiles,
                                    rng=rng.duplicate())

    # Build each postage stamp:
    for k in range(nobj):
        # The usual random number generator using a different seed for each galaxy.
        rng = galsim.BaseDeviate(random_seed + k)

        # Determine the bounds for this stamp and its center position.
        ix = ix_list[k]
        iy = iy_list[k]
        b = galsim.BoundsI(ix * stamp_size + 1, (ix + 1) * stamp_size,
                           iy * stamp_size + 1, (iy + 1) * stamp_size)
        sub_gal_image = gal_image[b]
        sub_psf_image = psf_image[b]

        pos = wcs.toWorld(b.trueCenter())
        # The image comes out as about 211 arcsec across, so we define our variable
        # parameters in terms of (r/100 arcsec), so roughly the scale size of the image.
        r = math.sqrt(pos.x**2 + pos.y**2) / 100
        psf_fwhm = 0.9 + 0.5 * r**2  # arcsec
        psf_e = 0.4 * r**1.5
        psf_beta = (math.atan2(pos.y, pos.x) + math.pi / 2) * galsim.radians

        # Define the PSF profile
        psf = galsim.Gaussian(fwhm=psf_fwhm)
        psf = psf.shear(e=psf_e, beta=psf_beta)

        # Define the galaxy profile:

        # For this demo, we are doing a ring test where the same galaxy profile is drawn at many
        # orientations stepped uniformly in angle, making a ring in e1-e2 space.
        # We're drawing each profile at 20 different orientations and then skipping to the
        # next galaxy in the list.  So theta steps by 1/20 * 360 degrees:
        theta = k / 20. * 360. * galsim.degrees

        # The index needs to increment every 20 objects so we use k/20 using integer math.
        index = k / 20
        gal = gal_list[index]

        # This makes a new copy so we're not changing the object in the gal_list.
        gal = gal.rotate(theta)

        # Apply the shear from the power spectrum.  We should either turn the gridded shears
        # grid_g1[iy, ix] and grid_g2[iy, ix] into gridded reduced shears using a utility called
        # galsim.lensing.theoryToObserved, or use ps.getShear() which by default gets the reduced
        # shear.  ps.getShear() is also more flexible because it can get the shear at positions that
        # are not on the original grid, as long as they are contained within the bounds of the full
        # grid. So in this example we'll use ps.getShear().
        alt_g1, alt_g2 = ps.getShear(pos)
        gal = gal.shear(g1=alt_g1, g2=alt_g2)

        # Apply half-pixel shift in a random direction.
        shift_r = pixel_scale * 0.5
        ud = galsim.UniformDeviate(rng)
        theta = ud() * 2. * math.pi
        dx = shift_r * math.cos(theta)
        dy = shift_r * math.sin(theta)
        gal = gal.shift(dx, dy)

        # Make the final image, convolving with the psf
        final = galsim.Convolve([psf, gal])

        # Draw the image
        final.drawImage(sub_gal_image)

        # Now add noise to get our desired S/N
        # See demo5.py for more info about how this works.
        sky_level_pixel = sky_level * pixel_scale**2
        noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel)
        sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise)

        # For the PSF image, we also shift the PSF by the same amount.
        psf = psf.shift(dx, dy)

        # Draw the PSF image:
        # We use real space integration over the pixels to avoid some of the
        # artifacts that can show up with Fourier convolution.
        # The level of the artifacts is quite low, but when drawing with
        # so little noise, they are apparent with ds9's zscale viewing.
        psf.drawImage(sub_psf_image, method='real_space')

        # Again, add noise, but at higher S/N this time.
        sub_psf_image.addNoiseSNR(noise, psf_signal_to_noise)

        logger.info('Galaxy (%d,%d): position relative to center = %s', ix, iy,
                    str(pos))

    logger.info('Done making images of postage stamps')

    # Now write the images to disk.
    images = [gal_image, psf_image]
    galsim.fits.writeMulti(images, file_name)
    logger.info('Wrote image to %r', file_name)
Пример #21
0
def test_power_spectrum_with_kappa():
    """Test that the convergence map generated by the PowerSpectrum class is consistent with the
    Kaiser Squires inversion of the corresponding shear field.
    """
    import time
    t1 = time.time()
    # Note that in order for this test to pass, we have to control aliasing by smoothing the power
    # spectrum to go to zero above some maximum k.  This is the only way to get agreement at high
    # precision between the gamma's and kappa's from the lensing engine vs. that from a Kaiser and
    # Squires inversion.
    rseed=177774
    ngrid=100
    dx_grid_arcmin = 6
    # First lookup a cosmologically relevant power spectrum (bandlimited version to remove aliasing
    # and allow high-precision comparison).
    tab_ps = galsim.LookupTable(
        file='../examples/data/cosmo-fid.zmed1.00_smoothed.out', interpolant='linear')

    # Begin with E-mode input power
    psE = galsim.PowerSpectrum(tab_ps, None, units=galsim.radians)
    g1E, g2E, k_test = psE.buildGrid(
        grid_spacing=dx_grid_arcmin, ngrid=ngrid, units=galsim.arcmin,
        rng=galsim.BaseDeviate(rseed), get_convergence=True)
    kE_ks, kB_ks = galsim.lensing_ps.kappaKaiserSquires(g1E, g2E)
    # Test that E-mode kappa matches to some sensible accuracy
    exact_dp = 15
    np.testing.assert_array_almost_equal(
        k_test, kE_ks, decimal=exact_dp,
        err_msg="E-mode only PowerSpectrum output kappaE does not match KS inversion to 16 d.p.")
    # Test that B-mode kappa matches zero to some sensible accuracy
    np.testing.assert_array_almost_equal(
        kB_ks, np.zeros_like(kE_ks), decimal=exact_dp,
        err_msg="E-mode only PowerSpectrum output kappaB from KS does not match zero to 16 d.p.")

    # Then do B-mode only input power
    psB = galsim.PowerSpectrum(None, tab_ps, units=galsim.radians)
    g1B, g2B, k_test = psB.buildGrid(
        grid_spacing=dx_grid_arcmin, ngrid=ngrid, units=galsim.arcmin,
        rng=galsim.BaseDeviate(rseed), get_convergence=True)
    kE_ks, kB_ks = galsim.lensing_ps.kappaKaiserSquires(g1B, g2B)
    # Test that kappa output by PS code matches zero to some sensible accuracy
    np.testing.assert_array_almost_equal(
        k_test, np.zeros_like(k_test), decimal=exact_dp,
        err_msg="B-mode only PowerSpectrum output kappa does not match zero to 16 d.p.")
    # Test that E-mode kappa inferred via KS also matches zero to some sensible accuracy
    np.testing.assert_array_almost_equal(
        kE_ks, np.zeros_like(kB_ks), decimal=exact_dp,
        err_msg="B-mode only PowerSpectrum output kappaE from KS does not match zero to 16 d.p.")

    # Then for luck take B-mode only shears but rotate by 45 degrees before KS, and check
    # consistency 
    kE_ks_rotated, kB_ks_rotated = galsim.lensing_ps.kappaKaiserSquires(g2B, -g1B)
    np.testing.assert_array_almost_equal(
        kE_ks_rotated, kB_ks, decimal=exact_dp,
        err_msg="KS inverted kappaE from B-mode only PowerSpectrum fails rotation test.")
    np.testing.assert_array_almost_equal(
        kB_ks_rotated, np.zeros_like(kB_ks), decimal=exact_dp,
        err_msg="KS inverted kappaB from B-mode only PowerSpectrum fails rotation test.")

    # Finally, do E- and B-mode power
    psB = galsim.PowerSpectrum(tab_ps, tab_ps, units=galsim.radians)
    g1EB, g2EB, k_test = psB.buildGrid(
        grid_spacing=dx_grid_arcmin, ngrid=ngrid, units=galsim.arcmin,
        rng=galsim.BaseDeviate(rseed), get_convergence=True)
    kE_ks, kB_ks = galsim.lensing_ps.kappaKaiserSquires(g1EB, g2EB)
    # Test that E-mode kappa matches to some sensible accuracy
    np.testing.assert_array_almost_equal(
        k_test, kE_ks, decimal=exact_dp,
        err_msg="E/B PowerSpectrum output kappa does not match KS inversion to 16 d.p.")
    # Test rotating the shears by 45 degrees
    kE_ks_rotated, kB_ks_rotated = galsim.lensing_ps.kappaKaiserSquires(g2EB, -g1EB)
    np.testing.assert_array_almost_equal(
        kE_ks_rotated, kB_ks, decimal=exact_dp,
        err_msg="KS inverted kappaE from E/B PowerSpectrum fails rotation test.")
    np.testing.assert_array_almost_equal(
        kB_ks_rotated, -kE_ks, decimal=exact_dp,
        err_msg="KS inverted kappaB from E/B PowerSpectrum fails rotation test.")

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Пример #22
0
                           g1_col='g1',
                           g2_col='g2')
    # Define the corrfunc object
    gg = treecorr.GGCorrelation(min_sep=min_sep,
                                max_sep=max_sep,
                                bin_size=0.1,
                                sep_units='degrees')
    # Actually calculate the correlation function.
    gg.process(cat)
    os.remove('temp.fits')
    return gg


# Here's where we actually do stuff.  Start by making the PowerSpectrum object, and defining the
# grid range.
test_ps = galsim.PowerSpectrum(e_power_function=theory_tab, units='radians')
grid_range = dtheta * np.arange(grid_nx)
x, y = np.meshgrid(grid_range, grid_range)

# Now we do the iterations to build the shear grids.
for ind in range(n_iter):
    print 'Building grid %d' % ind
    g1, g2 = test_ps.buildGrid(grid_spacing=dtheta,
                               ngrid=grid_nx,
                               rng=rng,
                               units='degrees',
                               kmin_factor=kmin_factor,
                               kmax_factor=kmax_factor)

    print 'Calculating correlations %d' % ind
    gg = run_treecorr(x, y, g1, g2)
Пример #23
0
def main(argv):
    """
    Make images using constant PSF and variable shear:
      - The main image is 0.2 x 0.2 degrees.
      - Pixel scale is 0.2 arcsec, hence the image is 3600 x 3600 pixels.
      - Applied shear is from a cosmological power spectrum read in from file.
      - The PSF is a real one from SDSS, and corresponds to a convolution of atmospheric PSF,
        optical PSF, and pixel response, which has been sampled at pixel centers.  We used a PSF
        from SDSS in order to have a PSF profile that could correspond to what you see with a real
        telescope. However, in order that the galaxy resolution not be too poor, we tell GalSim that
        the pixel scale for that PSF image is 0.2" rather than 0.396".  We are simultaneously lying
        about the intrinsic size of the PSF and about the pixel scale when we do this.
      - Noise is correlated with the same spatial correlation function as found in HST COSMOS weak
        lensing science images, with a point (zero distance) variance that we normalize to 1.e4.
      - Galaxies are real galaxies, each with S/N~100 based on a point variance-only calculation
        (such as discussed in Leauthaud et al 2007).  The true SNR is somewhat lower, due to the
        presence of correlation in the noise.

    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo11")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    stamp_size = 100  # number of pixels in each dimension of galaxy images
    pixel_scale = 0.2  # arcsec/pixel
    image_size = 0.2 * galsim.degrees  # size of big image in each dimension
    image_size = int(
        (image_size / galsim.arcsec) / pixel_scale)  # convert to pixels
    image_size_arcsec = image_size * pixel_scale  # size of big image in each dimension (arcsec)
    noise_variance = 1.e4  # ADU^2
    nobj = 288  # number of galaxies in entire field
    # (This corresponds to 2 galaxies / arcmin^2)
    grid_spacing = 90.0  # The spacing between the samples for the power spectrum
    # realization (arcsec)
    gal_signal_to_noise = 100  # S/N of each galaxy

    # random_seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 24783923

    file_name = os.path.join('output', 'tabulated_power_spectrum.fits.fz')

    logger.info('Starting demo script 11')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_example.fits'
    # This script is designed to be run from the examples directory so dir is a relative path.
    # But the '../examples/' part lets bin/demo11 also be run from the bin directory.
    dir = '../examples/data'
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    real_galaxy_catalog.preload()
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # List of IDs to use.  We select 5 particularly irregular galaxies for this demo.
    # Then we'll choose randomly from this list.
    id_list = [106416, 106731, 108402, 116045, 116448]

    # Make the 5 galaxies we're going to use here rather than remake them each time.
    # This means the Fourier transforms of the real galaxy images don't need to be recalculated
    # each time, so it's a bit more efficient.
    gal_list = [
        galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list
    ]

    # Setup the PowerSpectrum object we'll be using:
    # To do this, we first have to read in the tabulated power spectrum.
    # We use a tabulated power spectrum from iCosmo (http://icosmo.org), with the following
    # cosmological parameters and survey design:
    # H_0 = 70 km/s/Mpc
    # Omega_m = 0.25
    # Omega_Lambda = 0.75
    # w_0 = -1.0
    # w_a = 0.0
    # n_s = 0.96
    # sigma_8 = 0.8
    # Smith et al. prescription for the non-linear power spectrum.
    # Eisenstein & Hu transfer function with wiggles.
    # Default dN/dz with z_med = 1.0
    # The file has, as required, just two columns which are k and P(k).  However, iCosmo works in
    # terms of ell and C_ell; ell is inverse radians and C_ell in radians^2.  Since GalSim tends to
    # work in terms of arcsec, we have to tell it that the inputs are radians^-1 so it can convert
    # to store in terms of arcsec^-1.
    pk_file = os.path.join('..', 'examples', 'data', 'cosmo-fid.zmed1.00.out')
    ps = galsim.PowerSpectrum(pk_file, units=galsim.radians)
    # The argument here is "e_power_function" which defines the E-mode power to use.
    logger.info('Set up power spectrum from tabulated P(k)')

    # Now let's read in the PSF.  It's a real SDSS PSF, which means pixel scale of 0.396".  However,
    # the typical seeing is 1.2" and we want to simulate better seeing, so we will just tell GalSim
    # that the pixel scale is 0.2".  We have to be careful with SDSS PSF images, as they have an
    # added 'soft bias' of 1000 which has been removed before creation of this file, so that the sky
    # level is properly zero.  Also, the file is bzipped, to demonstrate the new capability of
    # reading in a file that has been compressed in various ways (which GalSim can infer from the
    # filename).  We want to read the image directly into an InterpolatedImage GSObject, so we can
    # manipulate it as needed (here, the only manipulation needed is convolution).  We want a PSF
    # with flux 1, and we can set the pixel scale using a keyword.
    psf_file = os.path.join('..', 'examples', 'data',
                            'example_sdss_psf_sky0.fits.bz2')
    psf = galsim.InterpolatedImage(psf_file, dx=pixel_scale, flux=1.)
    # We do not include a pixel response function galsim.Pixel here, because the image that was read
    # in from file already included it.
    logger.info('Read in PSF image from bzipped FITS file')

    # Setup the image:
    full_image = galsim.ImageF(image_size, image_size, scale=pixel_scale)

    # The default convention for indexing an image is to follow the FITS standard where the
    # lower-left pixel is called (1,1).  However, this can be counter-intuitive to people more
    # used to C or python indexing, where indices start at 0.  It is possible to change the
    # coordinates of the lower-left pixel with the methods `setOrigin`.  For this demo, we
    # switch to 0-based indexing, so the lower-left pixel will be called (0,0).
    full_image.setOrigin(0, 0)

    # Get the center of the image in arcsec
    center = full_image.bounds.trueCenter() * pixel_scale

    # As for demo10, we use random_seed+nobj for the random numbers required for the
    # whole image.  In this case, both the power spectrum realization and the noise on the
    # full image we apply later.
    rng = galsim.BaseDeviate(random_seed + nobj)
    # We want to make random positions within our image.  However, currently for shears from a power
    # spectrum we first have to get shears on a grid of positions, and then we can choose random
    # positions within that.  So, let's make the grid.  We're going to make it as large as the
    # image, with grid points spaced by 90 arcsec (hence interpolation only happens below 90"
    # scales, below the interesting scales on which we want the shear power spectrum to be
    # represented exactly).  Lensing engine wants positions in arcsec, so calculate that:
    ps.buildGrid(grid_spacing=grid_spacing,
                 ngrid=int(image_size_arcsec / grid_spacing) + 1,
                 center=center,
                 rng=rng)
    logger.info('Made gridded shears')

    # Now we need to loop over our objects:
    for k in range(nobj):
        time1 = time.time()
        # The usual random number generator using a different seed for each galaxy.
        ud = galsim.UniformDeviate(random_seed + k)

        # Choose a random position in the image
        x = ud() * (image_size - 1)
        y = ud() * (image_size - 1)

        # Turn this into a position in arcsec
        pos = galsim.PositionD(x, y) * pixel_scale

        # Get the reduced shears and magnification at this point
        g1, g2, mu = ps.getLensing(pos=pos)

        # Construct the galaxy:
        # Select randomly from among our list of galaxies.
        index = int(ud() * len(gal_list))
        gal = gal_list[index]

        # Draw the size from a plausible size distribution: N(r) ~ r^-3.5
        # For this, we use the class DistDeviate which can draw deviates from an arbitrary
        # probability distribution.  This distribution can be defined either as a functional
        # form as we do here, or as tabulated lists of x and p values, from which the
        # function is interpolated.
        distdev = galsim.DistDeviate(ud,
                                     function=lambda x: x**-3.5,
                                     x_min=1,
                                     x_max=5)
        dilat = distdev()
        # Use createDilated rather than applyDilation, so we don't change the galaxies in the
        # original gal_list -- createDilated makes a new copy.
        gal = gal.createDilated(dilat)

        # Apply a random rotation
        theta = ud() * 2.0 * numpy.pi * galsim.radians
        gal.applyRotation(theta)

        # Apply the cosmological (reduced) shear and magnification at this position using a single
        # GSObject method.
        gal.applyLensing(g1, g2, mu)

        # Convolve with the PSF.  We don't have to include a pixel response explicitly, since the
        # SDSS PSF image that we are using included the pixel response already.
        final = galsim.Convolve(psf, gal)

        # Account for the fractional part of the position:
        x_nom = x + 0.5  # Because stamp size is even!  See discussion in demo9.py
        y_nom = y + 0.5
        ix_nom = int(math.floor(x_nom + 0.5))
        iy_nom = int(math.floor(y_nom + 0.5))
        offset = galsim.PositionD(x_nom - ix_nom, y_nom - iy_nom)

        # Draw it with our desired stamp size
        stamp = galsim.ImageF(stamp_size, stamp_size)
        final.draw(image=stamp, dx=pixel_scale, offset=offset)

        # Rescale flux to get the S/N we want.  We have to do that before we add it to the big
        # image, which might have another galaxy near that point (so our S/N calculation would
        # erroneously include the flux from the other object).
        # See demo5.py for the math behind this calculation.
        sn_meas = math.sqrt(numpy.sum(stamp.array**2) / noise_variance)
        flux_scaling = gal_signal_to_noise / sn_meas
        stamp *= flux_scaling

        # Recenter the stamp at the desired position:
        stamp.setCenter(ix_nom, iy_nom)

        # Find the overlapping bounds:
        bounds = stamp.bounds & full_image.bounds
        full_image[bounds] += stamp[bounds]

        time2 = time.time()
        tot_time = time2 - time1
        logger.info('Galaxy %d: position relative to corner = %s, t=%f s', k,
                    str(pos), tot_time)

    # Add correlated noise to the image -- the correlation function comes from the HST COSMOS images
    # and is described in more detail in the galsim.correlatednoise.getCOSMOSNoise() docstring.
    # This function requires a FITS file, stored in the GalSim repository, that represents this
    # correlation information: the path to this file is a required argument.
    cf_file_name = os.path.join('..', 'examples', 'data',
                                'acs_I_unrot_sci_20_cf.fits')

    # Then use this to initialize the correlation function that we will use to add noise to the
    # full_image.  We set the dx_cosmos keyword equal to our pixel scale, so that the noise among
    # neighboring pixels is correlated at the same level as it was among neighboring pixels in HST
    # COSMOS.  Using the original pixel scale, dx_cosmos=0.03 [arcsec], would leave very little
    # correlation among our larger 0.2 arcsec pixels. We also set the point (zero-distance) variance
    # to our desired value.
    cn = galsim.correlatednoise.getCOSMOSNoise(rng,
                                               cf_file_name,
                                               dx_cosmos=pixel_scale,
                                               variance=noise_variance)

    # Now add noise according to this correlation function to the full_image.  We have to do this
    # step at the end, rather than adding to individual postage stamps, in order to get the noise
    # level right in the overlap regions between postage stamps.
    full_image.addNoise(
        cn)  # Note image must have the right scale, as it does here.
    logger.info('Added noise to final large image')

    # Now write the image to disk.  It is automatically compressed with Rice compression,
    # since the filename we provide ends in .fz.
    full_image.write(file_name)
    logger.info('Wrote image to %r', file_name)
Пример #24
0
def main(argv):
    """
    Make images using variable PSF and shear:
      - The main image is 10 x 10 postage stamps.
      - Each postage stamp is 48 x 48 pixels.
      - The second HDU has the corresponding PSF image.
      - Applied shear is from a power spectrum P(k) ~ k^1.8.
      - Galaxies are real galaxies oriented in a ring test of 20 each.
      - The PSF is Gaussian with FWHM, ellipticity and position angle functions of (x,y)
      - Noise is Poisson using a nominal sky value of 1.e6.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo10")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    n_tiles = 10  # number of tiles in each direction.
    stamp_size = 48  # pixels

    pixel_scale = 0.44  # arcsec / pixel
    sky_level = 1.e6  # ADU / arcsec^2

    # The random seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 3339201

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

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

    # These will be created for each object below.  The values we'll use will be functions
    # of (x,y) relative to the center of the image.  (r = sqrt(x^2+y^2))
    # psf_fwhm = 0.9 + 0.5 * (r/100)^2  -- arcsec
    # psf_e = 0.4 * (r/100)^1.5         -- large value at the edge, so visible by eye.
    # psf_beta = atan2(y/x) + pi/2      -- tangential pattern

    gal_dilation = 3  # Make the galaxies a bit larger than their original size.
    gal_signal_to_noise = 100  # Pretty high.
    psf_signal_to_noise = 1000  # Even higher.

    logger.info('Starting demo script 10')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
    dir = 'data'
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # List of IDs to use.  We select 5 particularly irregular galaxies for this demo.
    # Then we'll choose randomly from this list.
    id_list = [106416, 106731, 108402, 116045, 116448]

    # Make the 5 galaxies we're going to use here rather than remake them each time.
    # This means the Fourier transforms of the real galaxy images don't need to be recalculated
    # each time, so it's a bit more efficient.
    gal_list = [
        galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list
    ]
    # Grab the index numbers before we transform them and lose the index attribute.
    cosmos_index = [gal.index for gal in gal_list]

    # Make the galaxies a bit larger than their original observed size.
    gal_list = [gal.dilate(gal_dilation) for gal in gal_list]

    # Setup the PowerSpectrum object we'll be using:
    ps = galsim.PowerSpectrum(lambda k: k**1.8)
    # The argument here is "e_power_function" which defines the E-mode power to use.

    # There is also a b_power_function if you want to include any B-mode power:
    #     ps = galsim.PowerSpectrum(e_power_function, b_power_function)

    # You may even omit the e_power_function argument and have a pure B-mode power spectrum.
    #     ps = galsim.PowerSpectrum(b_power_function = b_power_function)

    # All the random number generator classes derive from BaseDeviate.
    # When we construct another kind of deviate class from any other
    # kind of deviate class, the two share the same underlying random number
    # generator.  Sometimes it can be clearer to just construct a BaseDeviate
    # explicitly and then construct anything else you need from that.
    # Note: A BaseDeviate cannot be used to generate any values.  It can
    # only be used in the constructor for other kinds of deviates.
    # The seeds for the objects are random_seed+1..random_seed+nobj.
    # The seeds for things at the image or file level use random_seed itself.
    nobj = n_tiles * n_tiles
    rng = galsim.BaseDeviate(random_seed)

    # Have the PowerSpectrum object build a grid of shear values for us to use.
    grid_g1, grid_g2 = ps.buildGrid(grid_spacing=stamp_size * pixel_scale,
                                    ngrid=n_tiles,
                                    rng=rng)

    # Setup the images:
    gal_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)
    psf_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)

    # Update the image WCS to use the image center as the origin of the WCS.
    # The class that acts like a PixelScale except for this offset is called OffsetWCS.
    im_center = gal_image.true_center
    wcs = galsim.OffsetWCS(scale=pixel_scale, origin=im_center)
    gal_image.wcs = wcs
    psf_image.wcs = wcs

    # We will place the tiles in a random order.  To do this, we make two lists for the
    # ix and iy values.  Then we apply a random permutation to the lists (in tandem).
    ix_list = []
    iy_list = []
    for ix in range(n_tiles):
        for iy in range(n_tiles):
            ix_list.append(ix)
            iy_list.append(iy)
    # This next function will use the given random number generator, rng, and use it to
    # randomly permute any number of lists.  All lists will have the same random permutation
    # applied.
    galsim.random.permute(rng, ix_list, iy_list)

    # Initialize the OutputCatalog for the truth values
    names = [
        'gal_num', 'x_image', 'y_image', 'psf_e1', 'psf_e2', 'psf_fwhm',
        'cosmos_id', 'cosmos_index', 'theta', 'g1', 'g2', 'shift_x', 'shift_y'
    ]
    types = [
        int, float, float, float, float, float, str, int, float, float, float,
        float, float
    ]
    truth_catalog = galsim.OutputCatalog(names, types)

    # Build each postage stamp:
    for k in range(nobj):
        # The usual random number generator using a different seed for each galaxy.
        rng = galsim.BaseDeviate(random_seed + k + 1)

        # Determine the bounds for this stamp and its center position.
        ix = ix_list[k]
        iy = iy_list[k]
        b = galsim.BoundsI(ix * stamp_size + 1, (ix + 1) * stamp_size,
                           iy * stamp_size + 1, (iy + 1) * stamp_size)
        sub_gal_image = gal_image[b]
        sub_psf_image = psf_image[b]

        pos = wcs.toWorld(b.true_center)
        # The image comes out as about 211 arcsec across, so we define our variable
        # parameters in terms of (r/100 arcsec), so roughly the scale size of the image.
        rsq = (pos.x**2 + pos.y**2)
        r = math.sqrt(rsq)

        psf_fwhm = 0.9 + 0.5 * rsq / 100**2  # arcsec
        psf_e = 0.4 * (r / 100.)**1.5
        psf_beta = (math.atan2(pos.y, pos.x) + math.pi / 2) * galsim.radians

        # Define the PSF profile
        psf = galsim.Gaussian(fwhm=psf_fwhm)
        psf_shape = galsim.Shear(e=psf_e, beta=psf_beta)
        psf = psf.shear(psf_shape)

        # Define the galaxy profile:

        # For this demo, we are doing a ring test where the same galaxy profile is drawn at many
        # orientations stepped uniformly in angle, making a ring in e1-e2 space.
        # We're drawing each profile at 20 different orientations and then skipping to the
        # next galaxy in the list.  So theta steps by 1/20 * 360 degrees:
        theta_deg = (k % 20) * 360. / 20
        theta = theta_deg * galsim.degrees

        # The index needs to increment every 20 objects so we use k/20 using integer math.
        index = k // 20
        gal = gal_list[index]

        # This makes a new copy so we're not changing the object in the gal_list.
        gal = gal.rotate(theta)

        # Apply the shear from the power spectrum.  We should either turn the gridded shears
        # grid_g1[iy, ix] and grid_g2[iy, ix] into gridded reduced shears using a utility called
        # galsim.lensing.theoryToObserved, or use ps.getShear() which by default gets the reduced
        # shear.  ps.getShear() is also more flexible because it can get the shear at positions that
        # are not on the original grid, as long as they are contained within the bounds of the full
        # grid. So in this example we'll use ps.getShear().
        alt_g1, alt_g2 = ps.getShear(pos)
        gal = gal.shear(g1=alt_g1, g2=alt_g2)

        # Apply half-pixel shift in a random direction.
        shift_r = pixel_scale * 0.5
        ud = galsim.UniformDeviate(rng)
        t = ud() * 2. * math.pi
        dx = shift_r * math.cos(t)
        dy = shift_r * math.sin(t)
        gal = gal.shift(dx, dy)

        # Make the final image, convolving with the psf
        final = galsim.Convolve([psf, gal])

        # Draw the image
        final.drawImage(sub_gal_image)

        # For the PSF image, we don't match the galaxy shift.  Rather, we use the offset
        # parameter to drawImage to apply a random offset of up to 0.5 pixels in each direction.
        # Note the difference in units between shift and offset.  The shift is applied to the
        # surface brightness profile, so it is in sky coordinates (as all dimension are for
        # GSObjects), which are arcsec here.  The offset though is applied to the image itself,
        # so it is in pixels.  Hence, we don't multiply by pixel_scale.
        psf_dx = ud() - 0.5
        psf_dy = ud() - 0.5
        psf_offset = galsim.PositionD(psf_dx, psf_dy)

        # Draw the PSF image:
        # We use real space integration over the pixels to avoid some of the
        # artifacts that can show up with Fourier convolution.
        # The level of the artifacts is quite low, but when drawing with
        # so little noise, they are apparent with ds9's zscale viewing.
        psf.drawImage(sub_psf_image, method='real_space', offset=psf_offset)

        # Build the noise model: Poisson noise with a given sky level.
        sky_level_pixel = sky_level * pixel_scale**2
        noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel)

        # Add noise to the PSF image, using the normal noise model, but scaling the
        # PSF flux high enough to reach the desired signal-to-noise.
        # See demo5.py for more info about how this works.
        sub_psf_image.addNoiseSNR(noise, psf_signal_to_noise)

        # And also to the galaxy image using its signal-to-noise.
        sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise)

        # Add the truth values to the truth catalog
        row = [
            k, b.true_center.x, b.true_center.y, psf_shape.e1,
            psf_shape.e2, psf_fwhm, id_list[index], cosmos_index[index],
            (theta_deg % 360.), alt_g1, alt_g2, dx, dy
        ]
        truth_catalog.addRow(row)

        logger.info('Galaxy (%d,%d): position relative to center = %s', ix, iy,
                    str(pos))

    logger.info('Done making images of postage stamps')

    # In this case, we'll attach the truth catalog as an additional HDU in the same file as
    # the image data.
    truth_hdu = truth_catalog.writeFitsHdu()

    # Now write the images to disk.
    images = [gal_image, psf_image, truth_hdu]
    # Any items in the "images" list that is already an hdu is just used directly.
    # The actual images are converted to FITS hdus that contain the image data.
    galsim.fits.writeMulti(images, file_name)
    logger.info('Wrote image to %r', file_name)
Пример #25
0
def main(argv):
    """
    Make images using constant PSF and variable shear:
      - The main image is 0.2 x 0.2 degrees.
      - Pixel scale is 0.2 arcsec, hence the image is 3600 x 3600 pixels.
      - Applied shear is from a cosmological power spectrum read in from file.
      - The PSF is a real one from SDSS, and corresponds to a convolution of atmospheric PSF,
        optical PSF, and pixel response, which has been sampled at pixel centers.  We used a PSF
        from SDSS in order to have a PSF profile that could correspond to what you see with a real
        telescope. However, in order that the galaxy resolution not be too poor, we tell GalSim that
        the pixel scale for that PSF image is 0.2" rather than 0.396".  We are simultaneously lying
        about the intrinsic size of the PSF and about the pixel scale when we do this.
      - The galaxy images include some initial correlated noise from the original HST observation.
        However, we whiten the noise of the final image so the final image has stationary 
        Gaussian noise, rather than correlated noise.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo11")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    base_stamp_size = 32  # number of pixels in each dimension of galaxy images
    # This will be scaled up according to the dilation.
    # Hence the "base_" prefix.

    pixel_scale = 0.2  # arcsec/pixel
    image_size = 0.2 * galsim.degrees  # size of big image in each dimension
    image_size = int(
        (image_size / galsim.arcsec) / pixel_scale)  # convert to pixels
    image_size_arcsec = image_size * pixel_scale  # size of big image in each dimension (arcsec)
    noise_variance = 1.e4  # ADU^2
    nobj = 288  # number of galaxies in entire field
    # (This corresponds to 2 galaxies / arcmin^2)
    grid_spacing = 90.0  # The spacing between the samples for the power spectrum
    # realization (arcsec)
    gal_signal_to_noise = 100  # S/N of each galaxy

    # random_seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 24783923

    file_name = os.path.join('output', 'tabulated_power_spectrum.fits.fz')

    logger.info('Starting demo script 11')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_example.fits'
    dir = 'data'
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # List of IDs to use.  We select 5 particularly irregular galaxies for this demo.
    # Then we'll choose randomly from this list.
    id_list = [106416, 106731, 108402, 116045, 116448]

    # We will cache the galaxies that we make in order to save some of the calculations that
    # happen on construction.  In particular, we don't want to recalculate the Fourier transforms
    # of the real galaxy images, so it's more efficient so make a store of RealGalaxy instances.
    # We start with them all = None, and fill them in as we make them.
    gal_list = [None] * len(id_list)

    # Setup the PowerSpectrum object we'll be using:
    # To do this, we first have to read in the tabulated shear power spectrum, often denoted
    # C_ell(ell), where ell has units of inverse angle and C_ell has units of angle^2.  However,
    # GalSim works in the flat-sky approximation, so we use this notation interchangeably with
    # P(k).  GalSim does not calculate shear power spectra for users, who must be able to provide
    # their own (or use the examples in the repository).
    #
    # Here we use a tabulated power spectrum from iCosmo (http://icosmo.org), with the following
    # cosmological parameters and survey design:
    # H_0 = 70 km/s/Mpc
    # Omega_m = 0.25
    # Omega_Lambda = 0.75
    # w_0 = -1.0
    # w_a = 0.0
    # n_s = 0.96
    # sigma_8 = 0.8
    # Smith et al. prescription for the non-linear power spectrum.
    # Eisenstein & Hu transfer function with wiggles.
    # Default dN/dz with z_med = 1.0
    # The file has, as required, just two columns which are k and P(k).  However, iCosmo works in
    # terms of ell and C_ell; ell is inverse radians and C_ell in radians^2.  Since GalSim tends to
    # work in terms of arcsec, we have to tell it that the inputs are radians^-1 so it can convert
    # to store in terms of arcsec^-1.
    pk_file = os.path.join('data', 'cosmo-fid.zmed1.00.out')
    ps = galsim.PowerSpectrum(pk_file, units=galsim.radians)
    # The argument here is "e_power_function" which defines the E-mode power to use.
    logger.info('Set up power spectrum from tabulated P(k)')

    # Now let's read in the PSF.  It's a real SDSS PSF, which means pixel scale of 0.396".  However,
    # the typical seeing is 1.2" and we want to simulate better seeing, so we will just tell GalSim
    # that the pixel scale is 0.2".  We have to be careful with SDSS PSF images, as they have an
    # added 'soft bias' of 1000 which has been removed before creation of this file, so that the sky
    # level is properly zero.  Also, the file is bzipped, to demonstrate the new capability of
    # reading in a file that has been compressed in various ways (which GalSim can infer from the
    # filename).  We want to read the image directly into an InterpolatedImage GSObject, so we can
    # manipulate it as needed (here, the only manipulation needed is convolution).  We want a PSF
    # with flux 1, and we can set the pixel scale using a keyword.
    psf_file = os.path.join('data', 'example_sdss_psf_sky0.fits.bz2')
    psf = galsim.InterpolatedImage(psf_file, scale=pixel_scale, flux=1.)
    logger.info('Read in PSF image from bzipped FITS file')

    # Setup the image:
    full_image = galsim.ImageF(image_size, image_size)

    # The default convention for indexing an image is to follow the FITS standard where the
    # lower-left pixel is called (1,1).  However, this can be counter-intuitive to people more
    # used to C or python indexing, where indices start at 0.  It is possible to change the
    # coordinates of the lower-left pixel with the methods `setOrigin`.  For this demo, we
    # switch to 0-based indexing, so the lower-left pixel will be called (0,0).
    full_image.setOrigin(0, 0)

    # As for demo10, we use random_seed+nobj for the random numbers required for the
    # whole image.  In this case, both the power spectrum realization and the noise on the
    # full image we apply later.
    rng = galsim.BaseDeviate(random_seed + nobj)

    # We want to make random positions within our image.  However, currently for shears from a power
    # spectrum we first have to get shears on a grid of positions, and then we can choose random
    # positions within that.  So, let's make the grid.  We're going to make it as large as the
    # image, with grid points spaced by 90 arcsec (hence interpolation only happens below 90"
    # scales, below the interesting scales on which we want the shear power spectrum to be
    # represented exactly).  The lensing engine wants positions in arcsec, so calculate that:
    ps.buildGrid(grid_spacing=grid_spacing,
                 ngrid=int(math.ceil(image_size_arcsec / grid_spacing)),
                 rng=rng.duplicate())
    logger.info('Made gridded shears')

    # We keep track of how much noise is already in the image from the RealGalaxies.
    # The default initial value is all pixels = 0.
    noise_image = galsim.ImageF(image_size, image_size)
    noise_image.setOrigin(0, 0)

    # Make a slightly non-trivial WCS.  We'll use a slightly rotated coordinate system
    # and center it at the image center.
    theta = 0.17 * galsim.degrees
    # ( dudx  dudy ) = ( cos(theta)  -sin(theta) ) * pixel_scale
    # ( dvdx  dvdy )   ( sin(theta)   cos(theta) )
    dudx = math.cos(theta.rad()) * pixel_scale
    dudy = -math.sin(theta.rad()) * pixel_scale
    dvdx = math.sin(theta.rad()) * pixel_scale
    dvdy = math.cos(theta.rad()) * pixel_scale
    image_center = full_image.trueCenter()
    affine = galsim.AffineTransform(dudx,
                                    dudy,
                                    dvdx,
                                    dvdy,
                                    origin=full_image.trueCenter())

    # We can also put it on the celestial sphere to give it a bit more realism.
    # The TAN projection takes a (u,v) coordinate system on a tangent plane and projects
    # that plane onto the sky using a given point as the tangent point.  The tangent
    # point should be given as a CelestialCoord.
    sky_center = galsim.CelestialCoord(ra=19.3 * galsim.hours,
                                       dec=-33.1 * galsim.degrees)

    # The third parameter, units, defaults to arcsec, but we make it explicit here.
    # It sets the angular units of the (u,v) intermediate coordinate system.
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    full_image.wcs = wcs

    # Now we need to loop over our objects:
    for k in range(nobj):
        time1 = time.time()
        # The usual random number generator using a different seed for each galaxy.
        ud = galsim.UniformDeviate(random_seed + k)

        # Draw the size from a plausible size distribution: N(r) ~ r^-2.5
        # For this, we use the class DistDeviate which can draw deviates from an arbitrary
        # probability distribution.  This distribution can be defined either as a functional
        # form as we do here, or as tabulated lists of x and p values, from which the
        # function is interpolated.
        # N.B. This calculation logically belongs later in the script, but given how the config
        #      structure works and the fact that we also use this value for the stamp size
        #      calculation, in order to get the output file to match the YAML output file, it
        #      turns out this is where we need to put this use of the random number generator.
        distdev = galsim.DistDeviate(ud,
                                     function=lambda x: x**-2.5,
                                     x_min=1,
                                     x_max=5)
        dilat = distdev()

        # Choose a random position in the image
        x = ud() * (image_size - 1)
        y = ud() * (image_size - 1)
        image_pos = galsim.PositionD(x, y)

        # Turn this into a position in world coordinates
        # We leave this in the (u,v) plane, since the PowerSpectrum class is really defined
        # on the tangent plane, not in (ra,dec).
        world_pos = affine.toWorld(image_pos)

        # Get the reduced shears and magnification at this point
        g1, g2, mu = ps.getLensing(pos=world_pos)

        # Construct the galaxy:
        # Select randomly from among our list of galaxies.
        index = int(ud() * len(gal_list))
        gal = gal_list[index]

        # If we haven't made this galaxy yet, we need to do so.
        if gal is None:
            # When whitening the image, we need to make sure the original correlated noise is
            # present throughout the whole image, otherwise the whitening will do the wrong thing
            # to the parts of the image that don't include the original image.  The RealGalaxy
            # stores the correct noise profile to use as the gal.noise attribute.  This noise
            # profile is automatically updated as we shear, dilate, convolve, etc.  But we need to
            # tell it how large to pad with this noise by hand.  This is a bit complicated for the
            # code to figure out on its own, so we have to supply the size for noise padding
            # with the noise_pad_size parameter.

            # In this case, the postage stamp will be 32 pixels for the undilated galaxies.
            # We expand the postage stamp as we dilate the galaxies, so that factor doesn't
            # come into play here.  The shear and magnification are not significant, but the
            # image can be rotated, which adds an extra factor of sqrt(2). So the net required
            # padded size is
            #     noise_pad_size = 32 * sqrt(2) * 0.2 arcsec/pixel = 9.1 arcsec
            # We round this up to 10 to be safe.
            gal = galsim.RealGalaxy(real_galaxy_catalog,
                                    rng=ud,
                                    id=id_list[index],
                                    noise_pad_size=10)
            # Save it for next time we use this galaxy.
            gal_list[index] = gal

        # Apply the dilation we calculated above.
        gal = gal.dilate(dilat)

        # Apply a random rotation
        theta = ud() * 2.0 * numpy.pi * galsim.radians
        gal = gal.rotate(theta)

        # Apply the cosmological (reduced) shear and magnification at this position using a single
        # GSObject method.
        gal = gal.lens(g1, g2, mu)

        # Convolve with the PSF.
        final = galsim.Convolve(psf, gal)

        # Account for the fractional part of the position:
        ix = int(math.floor(x + 0.5))
        iy = int(math.floor(y + 0.5))
        offset = galsim.PositionD(x - ix, y - iy)

        # Draw it with our desired stamp size (scaled up by the dilation factor):
        # Note: We make the stamp size odd to make the above calculation of the offset easier.
        this_stamp_size = 2 * int(math.ceil(base_stamp_size * dilat / 2)) + 1
        stamp = galsim.ImageF(this_stamp_size, this_stamp_size)

        # We use method='no_pixel' here because the SDSS PSF image that we are using includes the
        # pixel response already.
        final.drawImage(image=stamp,
                        wcs=wcs.local(image_pos),
                        offset=offset,
                        method='no_pixel')

        # Now we can whiten or symmetrize the noise on the postage stamp.  Galsim automatically
        # propagates the noise correctly from the initial RealGalaxy object through the applied
        # shear, distortion, rotation, and convolution into the final object's noise attribute.  To
        # make the noise fully white, use the image.whitenNoise() method. The returned value is the
        # variance of the Gaussian noise that is present after the whitening process.
        #
        # However, this is often overkill for many applications.  If it is acceptable to merely end
        # up with noise with some degree of symmetry (say 4-fold or 8-fold symmetry), then you can
        # instead have GalSim just add enough noise to make the resulting noise have this kind of
        # symmetry.  Usually this requires adding significantly less additional noise, which means
        # you can have the resulting total variance be somewhat smaller.  The returned variance
        # corresponds to the zero-lag value of the noise correlation function, which will still have
        # off-diagonal elements.  We can do this step using the image.symmetrizeNoise() method.
        #new_variance = stamp.whitenNoise(final.noise)
        new_variance = stamp.symmetrizeNoise(final.noise, 8)

        # Rescale flux to get the S/N we want.  We have to do that before we add it to the big
        # image, which might have another galaxy near that point (so our S/N calculation would
        # erroneously include the flux from the other object).
        # See demo5.py for the math behind this calculation.
        sn_meas = math.sqrt(numpy.sum(stamp.array**2) / noise_variance)
        flux_scaling = gal_signal_to_noise / sn_meas
        stamp *= flux_scaling
        # This also scales up the current variance by flux_scaling**2.
        new_variance *= flux_scaling**2

        # Recenter the stamp at the desired position:
        stamp.setCenter(ix, iy)

        # Find the overlapping bounds:
        bounds = stamp.bounds & full_image.bounds
        full_image[bounds] += stamp[bounds]

        # We need to keep track of how much variance we have currently in the image, so when
        # we add more noise, we can omit what is already there.
        noise_image[bounds] += new_variance

        time2 = time.time()
        tot_time = time2 - time1
        logger.info('Galaxy %d: position relative to center = %s, t=%f s', k,
                    str(world_pos), tot_time)

    # We already have some noise in the image, but it isn't uniform.  So the first thing to do is
    # to make the Gaussian noise uniform across the whole image.  We have a special noise class
    # that can do this.  VariableGaussianNoise takes an image of variance values and applies
    # Gaussian noise with the corresponding variance to each pixel.
    # So all we need to do is build an image with how much noise to add to each pixel to get us
    # up to the maximum value that we already have in the image.
    max_current_variance = numpy.max(noise_image.array)
    noise_image = max_current_variance - noise_image
    vn = galsim.VariableGaussianNoise(rng, noise_image)
    full_image.addNoise(vn)

    # Now max_current_variance is the noise level across the full image.  We don't want to add that
    # twice, so subtract off this much from the intended noise that we want to end up in the image.
    noise_variance -= max_current_variance

    # Now add Gaussian noise with this variance to the final image.  We have to do this step
    # at the end, rather than adding to individual postage stamps, in order to get the noise
    # level right in the overlap regions between postage stamps.
    noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance))
    full_image.addNoise(noise)
    logger.info('Added noise to final large image')

    # Now write the image to disk.  It is automatically compressed with Rice compression,
    # since the filename we provide ends in .fz.
    full_image.write(file_name)
    logger.info('Wrote image to %r', file_name)

    # Compute some sky positions of some of the pixels to compare with the values of RA, Dec
    # that ds9 reports.  ds9 always uses (1,1) for the lower left pixel, so the pixel coordinates
    # of these pixels are different by 1, but you can check that the RA and Dec values are
    # the same as what GalSim calculates.
    ra_str = sky_center.ra.hms()
    dec_str = sky_center.dec.dms()
    logger.info('Center of image    is at RA %sh %sm %ss, DEC %sd %sm %ss',
                ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                dec_str[3:5], dec_str[5:])
    for (x, y) in [(0, 0), (0, image_size - 1), (image_size - 1, 0),
                   (image_size - 1, image_size - 1)]:
        world_pos = wcs.toWorld(galsim.PositionD(x, y))
        ra_str = world_pos.ra.hms()
        dec_str = world_pos.dec.dms()
        logger.info('Pixel (%4d, %4d) is at RA %sh %sm %ss, DEC %sd %sm %ss',
                    x, y, ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                    dec_str[3:5], dec_str[5:])
    logger.info(
        'ds9 reports these pixels as (1,1), (1,3600), etc. with the same RA, Dec.'
    )