Ejemplo n.º 1
0
    def buildImage(self, config, base, image_num, obj_num, logger):
        """
        Build an Image consisting of a tiled array of postage stamps.

        @param config       The configuration dict for the image field.
        @param base         The base configuration dict.
        @param image_num    The current image number.
        @param obj_num      The first object number in the image.
        @param logger       If given, a logger object to log progress.

        @returns the final image
        """
        full_xsize = base['image_xsize']
        full_ysize = base['image_ysize']
        wcs = base['wcs']

        full_image = galsim.ImageF(full_xsize, full_ysize)
        full_image.setOrigin(base['image_origin'])
        full_image.wcs = wcs
        full_image.setZero()

        nobjects = self.nx_tiles * self.ny_tiles

        # Make a list of ix,iy values according to the specified order:
        if 'order' in config:
            order = galsim.config.ParseValue(config,'order',base,str)[0].lower()
        else:
            order = 'row'
        if order.startswith('row'):
            ix_list = [ ix for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ]
            iy_list = [ iy for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ]
        elif order.startswith('col'):
            ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ]
            iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ]
        elif order.startswith('rand'):
            ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ]
            iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ]
            rng = base['rng']
            galsim.random.permute(rng, ix_list, iy_list)
        else:
            raise ValueError("Invalid order.  Must be row, column, or random")

        # Define a 'image_pos' field so the stamps can set their position appropriately in case
        # we need it for PowerSpectum or NFWHalo.
        x0 = (self.stamp_xsize-1)/2. + base['image_origin'].x
        y0 = (self.stamp_ysize-1)/2. + base['image_origin'].y
        dx = self.stamp_xsize + self.xborder
        dy = self.stamp_ysize + self.yborder
        config['image_pos'] = {
            'type' : 'XY' ,
            'x' : { 'type' : 'List',
                    'items' : [ x0 + ix*dx for ix in ix_list ]
                  },
            'y' : { 'type' : 'List',
                    'items' : [ y0 + iy*dy for iy in iy_list ]
                  }
        }

        stamps, current_vars = galsim.config.BuildStamps(
                nobjects, base, logger=logger, obj_num=obj_num,
                xsize=self.stamp_xsize, ysize=self.stamp_ysize, do_noise=self.do_noise_in_stamps)

        base['index_key'] = 'image_num'

        for k in range(nobjects):
            # This is our signal that the object was skipped.
            if stamps[k] is None: continue
            if logger:
                logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds))
                logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(stamps[k].bounds))
            assert full_image.bounds.includes(stamps[k].bounds)
            b = stamps[k].bounds
            full_image[b] += stamps[k]

        # Bring the noise in the image so far up to a flat noise variance
        # Save the resulting noise variance as self.current_var.
        self.current_var = 0
        if not self.do_noise_in_stamps:
            if 'noise' in config:
                self.current_var = galsim.config.FlattenNoiseVariance(
                        base, full_image, stamps, current_vars, logger)
        return full_image
Ejemplo n.º 2
0
def test_corr_padding():
    """Test for correlated noise padding of InterpolatedImage."""
    import time
    t1 = time.time()

    # Set up some defaults for tests.
    decimal_precise=4
    decimal_coarse=2
    imgfile = 'fits_files/blankimg.fits'
    orig_nx = 187
    orig_ny = 164
    big_nx = 319
    big_ny = 322
    orig_seed = 151241

    # Read in some small image of a noise field from HST.
    # Rescale it to have a decently large amplitude for the purpose of doing these tests.
    im = 1.e2*galsim.fits.read(imgfile)
    # Make a CorrrlatedNoise out of it.
    cn = galsim.CorrelatedNoise(im, galsim.BaseDeviate(orig_seed))

    # first, make a noise image
    orig_img = galsim.ImageF(orig_nx, orig_ny, scale=1.)
    orig_img.addNoise(cn)

    # make it into an InterpolatedImage with some zero-padding
    # (note that default is zero-padding, by factors of several)
    int_im = galsim.InterpolatedImage(orig_img)
    # draw into a larger image
    big_img = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img, scale=1.)
    # check that variance is diluted by expected amount - should be exact, so check precisely!
    big_var_expected = np.var(orig_img.array)*float(orig_nx*orig_ny)/(big_nx*big_ny)
    np.testing.assert_almost_equal(np.var(big_img.array), big_var_expected, decimal=decimal_precise,
        err_msg='Variance not diluted by expected amount when zero-padding')

    # make it into an InterpolatedImage with noise-padding
    int_im = galsim.InterpolatedImage(orig_img, rng = galsim.GaussianDeviate(orig_seed),
                                      noise_pad = im, noise_pad_size = max(big_nx,big_ny))

    # draw into a larger image
    big_img = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img, scale=1.)
    # check that variance is same as original - here, we cannot be too precise because the padded
    # region is not huge and the comparison will be, well, noisy.
    np.testing.assert_almost_equal(np.var(big_img.array), np.var(orig_img.array),
        decimal=decimal_coarse,
        err_msg='Variance not correct after padding image with correlated noise')

    # check that if we pass in a RNG, it is actually used to pad with the same noise field
    # basically, redo all of the above steps and draw into a new image, make sure it's the same as
    # previous.
    int_im = galsim.InterpolatedImage(
        orig_img, rng=galsim.GaussianDeviate(orig_seed), noise_pad=cn,
        noise_pad_size = max(big_nx,big_ny))
    big_img_2 = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img_2, scale=1.)
    np.testing.assert_array_almost_equal(big_img_2.array, big_img.array, decimal=decimal_precise,
        err_msg='Cannot reproduce correlated noise-padded image with same choice of seed')

    # Finally, check inputs:
    # what if we give it a screwy way of defining the image padding?
    try:
        np.testing.assert_raises(ValueError,galsim.InterpolatedImage,orig_img,noise_pad=-1.)
    except ImportError:
        print 'The assert_raises tests require nose'
    # also, check that whether we give it a string, image, or cn, it gives the same noise field
    # (given the same random seed)
    infile = 'fits_files/blankimg.fits'
    inimg = galsim.fits.read(infile)
    incf = galsim.CorrelatedNoise(inimg, galsim.GaussianDeviate()) # input RNG will be ignored below
    int_im2 = galsim.InterpolatedImage(orig_img, rng=galsim.GaussianDeviate(orig_seed),
                                       noise_pad=inimg, noise_pad_size = max(big_nx,big_ny))
    int_im3 = galsim.InterpolatedImage(orig_img, rng=galsim.GaussianDeviate(orig_seed),
                                       noise_pad=incf, noise_pad_size = max(big_nx,big_ny))
    big_img2 = galsim.ImageF(big_nx, big_ny)
    big_img3 = galsim.ImageF(big_nx, big_ny)
    int_im2.draw(big_img2, scale=1.)
    int_im3.draw(big_img3, scale=1.)
    np.testing.assert_equal(big_img2.array, big_img3.array,
                            err_msg='Diff ways of specifying correlated noise give diff answers')

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 3
0
def test_gaussian():
    """Test the generation of a specific Gaussian profile against a known result.
    """
    savedImg = galsim.fits.read(os.path.join(imgdir, "gauss_1.fits"))
    savedImg.setCenter(0, 0)
    dx = 0.2
    myImg = galsim.ImageF(savedImg.bounds, scale=dx)
    myImg.setCenter(0, 0)

    gauss = galsim.Gaussian(flux=1, sigma=1)
    # Reference images were made with old centering, which is equivalent to use_true_center=False.
    myImg = gauss.drawImage(myImg,
                            scale=dx,
                            method="sb",
                            use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg="Using GSObject Gaussian disagrees with expected result")
    np.testing.assert_almost_equal(
        myImg.array.sum(dtype=float) * dx**2,
        myImg.added_flux,
        5,
        err_msg="Gaussian profile GSObject::draw returned wrong added_flux")

    # Check a non-square image
    print(myImg.bounds)
    recImg = galsim.ImageF(45, 66)
    recImg.setCenter(0, 0)
    recImg = gauss.drawImage(recImg,
                             scale=dx,
                             method="sb",
                             use_true_center=False)
    np.testing.assert_array_almost_equal(
        recImg[savedImg.bounds].array,
        savedImg.array,
        5,
        err_msg=
        "Drawing Gaussian on non-square image disagrees with expected result")
    np.testing.assert_almost_equal(
        recImg.array.sum(dtype=float) * dx**2,
        recImg.added_flux,
        5,
        err_msg=
        "Gaussian profile GSObject::draw on non-square image returned wrong added_flux"
    )

    # Check with default_params
    gauss = galsim.Gaussian(flux=1, sigma=1, gsparams=default_params)
    gauss.drawImage(myImg, scale=0.2, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Gaussian with default_params disagrees with expected result"
    )
    gauss = galsim.Gaussian(flux=1, sigma=1, gsparams=galsim.GSParams())
    gauss.drawImage(myImg, scale=0.2, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Gaussian with GSParams() disagrees with expected result"
    )

    # Use non-unity values.
    gauss = galsim.Gaussian(flux=1.7, sigma=2.3)
    gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8)
    gauss2 = galsim.Gaussian(flux=1.7, sigma=2.3, gsparams=gsp)
    assert gauss2 != gauss
    assert gauss2 == gauss.withGSParams(gsp)
    assert gauss2 == gauss.withGSParams(xvalue_accuracy=1.e-8,
                                        kvalue_accuracy=1.e-8)
    assert gauss2 == gauss.withGSParams(xvalue_accuracy=1.e-8).withGSParams(
        kvalue_accuracy=1.e-8)
    assert gauss2 == gauss.withGSParams(galsim.GSParams(xvalue_accuracy=1.e-8),
                                        kvalue_accuracy=1.e-8)
    assert gauss2 == gauss.withGSParams(gsp).withGSParams(
        kvalue_accuracy=1.e-8)
    assert gauss2 == gauss.withGSParams(galsim.GSParams(
        xvalue_accuracy=1.e-8)).withGSParams(kvalue_accuracy=1.e-8)
    check_basic(gauss, "Gaussian")

    # Check invalid parameters
    assert_raises(TypeError, gauss.withGSParams, xvalue_threshold=1.e-8)
    assert_raises(TypeError,
                  gauss.withGSParams,
                  xvalue_accuracy=1.e-8,
                  kvalue=1.e-8)

    # Test photon shooting.
    do_shoot(gauss, myImg, "Gaussian")

    # Test kvalues
    do_kvalue(gauss, myImg, "Gaussian")

    # Check picklability
    do_pickle(galsim.GSParams())  # Check GSParams explicitly here too.
    do_pickle(
        galsim.GSParams(minimum_fft_size=12,
                        maximum_fft_size=40,
                        folding_threshold=1.e-1,
                        maxk_threshold=2.e-1,
                        kvalue_accuracy=3.e-1,
                        xvalue_accuracy=4.e-1,
                        shoot_accuracy=5.e-1,
                        realspace_relerr=6.e-1,
                        realspace_abserr=7.e-1,
                        integration_relerr=8.e-1,
                        integration_abserr=9.e-1))
    do_pickle(gauss, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(gauss)

    # Should raise an exception if >=2 radii are provided.
    assert_raises(TypeError,
                  galsim.Gaussian,
                  sigma=3,
                  half_light_radius=1,
                  fwhm=2)
    assert_raises(TypeError, galsim.Gaussian, half_light_radius=1, fwhm=2)
    assert_raises(TypeError, galsim.Gaussian, sigma=3, fwhm=2)
    assert_raises(TypeError, galsim.Gaussian, sigma=3, half_light_radius=1)
    # Or none.
    assert_raises(TypeError, galsim.Gaussian)

    # Finally, test the noise property for things that don't have any noise set.
    assert gauss.noise is None
    # And accessing the attribute from the class should indicate that it is a lazyproperty
    assert 'lazy_property' in str(galsim.GSObject._noise)

    # And check that trying to use GSObject directly is an error.
    assert_raises(NotImplementedError, galsim.GSObject)
Ejemplo n.º 4
0
def main(argv):
    """
    Make images similar to that done for the Great08 challenge:
      - Each fits file is 10 x 10 postage stamps.
        (The real Great08 images are 100x100, but in the interest of making the Demo
         script a bit quicker, we only build 100 stars and 100 galaxies.)
      - Each postage stamp is 40 x 40 pixels.
      - One image is all stars.
      - A second image is all galaxies.
      - Applied shear is the same for each galaxy.
      - Galaxies are oriented randomly, but in pairs to cancel shape noise.
      - Noise is Poisson using a nominal sky value of 1.e6.
      - Galaxies are Exponential profiles.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo5")

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

    nx_tiles = 10  #
    ny_tiles = 10  #
    stamp_xsize = 40  #
    stamp_ysize = 40  #

    random_seed = 6424512  #

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

    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')
    psf_file_name = os.path.join('output', 'g08_psf.fits')
    psf_beta = 3  #
    psf_fwhm = 2.85  # arcsec (=pixels)
    psf_trunc = 2. * psf_fwhm  # arcsec (=pixels)
    psf_e1 = -0.019  #
    psf_e2 = -0.007  #

    gal_file_name = os.path.join('output', 'g08_gal.fits')
    gal_signal_to_noise = 200  # Great08 "LowNoise" run
    gal_resolution = 0.98  # r_gal / r_psf (use r = half_light_radius)
    # Note: Great08 defined their resolution as r_obs / r_psf, using the convolved
    #       size rather than the pre-convolved size.
    #       Therefore, our r_gal/r_psf = 0.98 approximately corresponds to
    #       their r_obs / r_psf = 1.4.

    gal_ellip_rms = 0.2  # using "distortion" definition of ellipticity:
    #   e = (a^2-b^2)/(a^2+b^2), where a and b are the
    #   semi-major and semi-minor axes, respectively.
    gal_ellip_max = 0.6  # Maximum value of e, to avoid getting near e=1.
    gal_g1 = 0.013  # Applied shear, using normal shear definition:
    gal_g2 = -0.008  #   g = (a-b)/(a+b)

    shift_radius = 1.0  # arcsec (=pixels)

    logger.info('Starting demo script 5 using:')
    logger.info('    - image with %d x %d postage stamps', nx_tiles, ny_tiles)
    logger.info('    - postage stamps of size %d x %d pixels', stamp_xsize,
                stamp_ysize)
    logger.info('    - Moffat PSF (beta = %.1f, FWHM = %.2f, trunc = %.2f),',
                psf_beta, psf_fwhm, psf_trunc)
    logger.info('    - PSF ellip = (%.3f,%.3f)', psf_e1, psf_e2)
    logger.info('    - Exponential galaxies')
    logger.info('    - Resolution (r_gal / r_psf) = %.2f', gal_resolution)
    logger.info('    - Ellipticities have rms = %.1f, max = %.1f',
                gal_ellip_rms, gal_ellip_max)
    logger.info('    - Applied gravitational shear = (%.3f,%.3f)', gal_g1,
                gal_g2)
    logger.info('    - Poisson noise (sky level = %.1e).', sky_level)
    logger.info('    - Centroid shifts up to = %.2f pixels', shift_radius)

    # Define the PSF profile
    psf = galsim.Moffat(beta=psf_beta, fwhm=psf_fwhm, trunc=psf_trunc)

    # When something can be constructed from multiple sizes, e.g. Moffat, then
    # you can get any size out even if it wasn't the way the object was constructed.
    # In this case, we extract the half-light radius, even though we built it with fwhm.
    # We'll use this later to set the galaxy's half-light radius in terms of a resolution.
    psf_re = psf.half_light_radius

    psf = psf.shear(e1=psf_e1, e2=psf_e2)
    logger.debug('Made PSF profile')

    # Define the galaxy profile

    # First figure out the size we need from the resolution
    gal_re = psf_re * gal_resolution

    # Make the galaxy profile starting with flux = 1.
    gal = galsim.Exponential(flux=1., half_light_radius=gal_re)
    logger.debug('Made galaxy profile')

    # This profile is placed with different orientations and noise realizations
    # at each postage stamp in the gal image.
    gal_image = galsim.ImageF(stamp_xsize * nx_tiles - 1,
                              stamp_ysize * ny_tiles - 1,
                              scale=pixel_scale)
    psf_image = galsim.ImageF(stamp_xsize * nx_tiles - 1,
                              stamp_ysize * ny_tiles - 1,
                              scale=pixel_scale)

    shift_radius_sq = shift_radius**2

    first_in_pair = True  # Make pairs that are rotated by 90 degrees

    k = 0
    for iy in range(ny_tiles):
        for ix in range(nx_tiles):
            # The normal procedure for setting random numbers in GalSim is to start a new
            # random number generator for each object using sequential seed values.
            # This sounds weird at first (especially if you were indoctrinated by Numerical
            # Recipes), but for the boost random number generator we use, the "random"
            # number sequences produced from sequential initial seeds are highly uncorrelated.
            #
            # The reason for this procedure is that when we use multiple processes to build
            # our images, we want to make sure that the results are deterministic regardless
            # of the way the objects get parcelled out to the different processes.
            #
            # Of course, this script isn't using multiple processes, so it isn't required here.
            # However, we do it nonetheless in order to get the same results as the config
            # version of this demo script (demo5.yaml).
            ud = galsim.UniformDeviate(random_seed + k + 1)

            # Any kind of random number generator can take another RNG as its first
            # argument rather than a seed value.  This makes both objects use the same
            # underlying generator for their pseudo-random values.
            gd = galsim.GaussianDeviate(ud, sigma=gal_ellip_rms)

            # The -1's in the next line are to provide a border of
            # 1 pixel between postage stamps
            b = galsim.BoundsI(ix * stamp_xsize + 1,
                               (ix + 1) * stamp_xsize - 1,
                               iy * stamp_ysize + 1,
                               (iy + 1) * stamp_ysize - 1)
            sub_gal_image = gal_image[b]
            sub_psf_image = psf_image[b]

            # Great08 randomized the locations of the two galaxies in each pair,
            # but for simplicity, we just do them in sequential postage stamps.
            if first_in_pair:
                # Use a random orientation:
                beta = ud() * 2. * math.pi * galsim.radians

                # Determine the ellipticity to use for this galaxy.
                ellip = 1
                while (ellip > gal_ellip_max):
                    # Don't do `ellip = math.fabs(gd())`
                    # Python basically implements this as a macro, so gd() is called twice!
                    val = gd()
                    ellip = math.fabs(val)

                # Make a new copy of the galaxy with an applied e1/e2-type distortion
                # by specifying the ellipticity and a real-space position angle
                ellip_gal = gal.shear(e=ellip, beta=beta)

                first_in_pair = False
            else:
                # Use the previous ellip_gal profile and rotate it by 90 degrees
                ellip_gal = ellip_gal.rotate(90 * galsim.degrees)

                first_in_pair = True

            # Apply the gravitational reduced shear by specifying g1/g2
            this_gal = ellip_gal.shear(g1=gal_g1, g2=gal_g2)

            # Apply a random shift_radius:
            rsq = 2 * shift_radius_sq
            while (rsq > shift_radius_sq):
                dx = (2 * ud() - 1) * shift_radius
                dy = (2 * ud() - 1) * shift_radius
                rsq = dx**2 + dy**2

            this_gal = this_gal.shift(dx, dy)
            # Note that the shifted psf that we create here is purely for the purpose of being able
            # to draw a separate, shifted psf image.  We do not use it when convolving the galaxy
            # with the psf.
            this_psf = psf.shift(dx, dy)

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

            # Draw the image
            final_gal.drawImage(sub_gal_image)

            # Now add an appropriate amount of noise to get our desired S/N
            # There are lots of definitions of S/N, but here is the one used by Great08
            # We use a weighted integral of the flux:
            #   S = sum W(x,y) I(x,y) / sum W(x,y)
            #   N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2
            # Now we assume that Var(I(x,y)) is constant so
            #   Var(I(x,y)) = noise_var
            # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y).
            # Then a few things cancel and we find that
            # S/N = sqrt( sum I(x,y)^2 / noise_var )
            #
            # The above procedure is encapsulated in the function image.addNoiseSNR which
            # sets the flux appropriately given the variance of the noise model.
            # In our case, noise_var = sky_level_pixel
            sky_level_pixel = sky_level * pixel_scale**2
            noise = galsim.PoissonNoise(ud, sky_level=sky_level_pixel)
            sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise)

            # Draw the PSF image
            # No noise on PSF images.  Just draw it as is.
            this_psf.drawImage(sub_psf_image)

            # For first instance, measure moments
            if ix == 0 and iy == 0:
                psf_shape = sub_psf_image.FindAdaptiveMom()
                temp_e = psf_shape.observed_shape.e
                if temp_e > 0.0:
                    g_to_e = psf_shape.observed_shape.g / temp_e
                else:
                    g_to_e = 0.0
                logger.info(
                    'Measured best-fit elliptical Gaussian for first PSF image: '
                )
                logger.info('  g1, g2, sigma = %7.4f, %7.4f, %7.4f (pixels)',
                            g_to_e * psf_shape.observed_shape.e1,
                            g_to_e * psf_shape.observed_shape.e2,
                            psf_shape.moments_sigma)

            x = b.center.x
            y = b.center.y
            logger.info(
                'Galaxy (%d,%d): center = (%.0f,%0.f)  (e,beta) = (%.4f,%.3f)',
                ix, iy, x, y, ellip, beta / galsim.radians)
            k = k + 1

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

    # Now write the images to disk.
    psf_image.write(psf_file_name)
    logger.info('Wrote PSF file %s', psf_file_name)

    gal_image.write(gal_file_name)
    logger.info('Wrote image to %r',
                gal_file_name)  # using %r adds quotes around filename for us
Ejemplo n.º 5
0
def test_uncorr_padding():
    """Test for uncorrelated noise padding of InterpolatedImage."""
    import time
    t1 = time.time()

    # Set up some defaults: use weird image sizes / shapes and noise variances.
    decimal_precise=5
    decimal_coarse=2
    orig_nx = 147
    orig_ny = 174
    noise_var = 1.73
    big_nx = 519
    big_ny = 482
    orig_seed = 151241

    # first, make a noise image
    orig_img = galsim.ImageF(orig_nx, orig_ny, scale=1.)
    gd = galsim.GaussianDeviate(orig_seed, mean=0., sigma=np.sqrt(noise_var))
    orig_img.addNoise(galsim.DeviateNoise(gd))

    # make it into an InterpolatedImage with some zero-padding
    # (note that default is zero-padding, by factors of several)
    int_im = galsim.InterpolatedImage(orig_img)
    # draw into a larger image
    big_img = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img, scale=1.)
    # check that variance is diluted by expected amount - should be exact, so check precisely!
    # Note that this only works if the big image has the same even/odd-ness in the two sizes.
    # Otherwise the center of the original image will fall between pixels in the big image.
    # Then the variance will be smoothed somewhat by the interpolant.
    big_var_expected = np.var(orig_img.array)*float(orig_nx*orig_ny)/(big_nx*big_ny)
    np.testing.assert_almost_equal(
        np.var(big_img.array), big_var_expected, decimal=decimal_precise,
        err_msg='Variance not diluted by expected amount when zero-padding')

    # make it into an InterpolatedImage with noise-padding
    int_im = galsim.InterpolatedImage(orig_img, noise_pad=noise_var,
                                      noise_pad_size=max(big_nx,big_ny),
                                      rng = galsim.GaussianDeviate(orig_seed))
    # draw into a larger image
    big_img = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img, scale=1.)
    # check that variance is same as original - here, we cannot be too precise because the padded
    # region is not huge and the comparison will be, well, noisy.
    np.testing.assert_almost_equal(
        np.var(big_img.array), noise_var, decimal=decimal_coarse,
        err_msg='Variance not correct after padding image with noise')

    # check that if we pass in a RNG, it is actually used to pad with the same noise field
    # basically, redo all of the above steps and draw into a new image, make sure it's the same as
    # previous.
    int_im = galsim.InterpolatedImage(orig_img, noise_pad=noise_var,
                                      noise_pad_size=max(big_nx,big_ny),
                                      rng = galsim.GaussianDeviate(orig_seed))
    big_img_2 = galsim.ImageF(big_nx, big_ny)
    int_im.draw(big_img_2, scale=1.)
    np.testing.assert_array_almost_equal(
        big_img_2.array, big_img.array, decimal=decimal_precise,
        err_msg='Cannot reproduce noise-padded image with same choice of seed')

    # Finally check inputs: what if we give it an input variance that is neg?  A list?
    try:
        np.testing.assert_raises(ValueError,galsim.InterpolatedImage,orig_img,noise_pad=-1.)
    except ImportError:
        print 'The assert_raises tests require nose'

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 6
0
def test_masks():
    """Test that moments and shear estimation routines respond appropriately to masks."""
    import time
    t1 = time.time()
    # set up some toy galaxy and PSF
    my_sigma = 1.0
    my_pixscale = 0.1
    my_g1 = 0.15
    my_g2 = -0.4
    imsize = 256
    g = galsim.Gaussian(sigma=my_sigma)
    p = galsim.Gaussian(
        sigma=my_sigma
    )  # the ePSF is Gaussian (kind of silly but it means we can
    # predict results exactly)
    g.applyShear(g1=my_g1, g2=my_g2)
    obj = galsim.Convolve(g, p)
    im = galsim.ImageF(imsize, imsize)
    p_im = galsim.ImageF(imsize, imsize)
    obj.draw(image=im, dx=my_pixscale)
    p.draw(image=p_im, dx=my_pixscale)

    # make some screwy weight and badpix images that should cause issues, and check that the
    # exception is thrown
    good_weight_im = galsim.ImageI(imsize, imsize, init_value=1)
    try:
        ## different size from image
        weight_im = galsim.ImageI(imsize, 2 * imsize)
        np.testing.assert_raises(ValueError, galsim.hsm.FindAdaptiveMom, im,
                                 weight_im)
        np.testing.assert_raises(ValueError, galsim.hsm.EstimateShear, im,
                                 p_im, weight_im)
        badpix_im = galsim.ImageI(imsize, 2 * imsize)
        np.testing.assert_raises(ValueError, galsim.hsm.FindAdaptiveMom, im,
                                 badpix_im)
        np.testing.assert_raises(ValueError, galsim.hsm.EstimateShear, im,
                                 p_im, good_weight_im, badpix_im)
        ## weird values
        weight_im = galsim.ImageI(imsize, imsize, init_value=-3)
        np.testing.assert_raises(ValueError, galsim.hsm.FindAdaptiveMom, im,
                                 weight_im)
        np.testing.assert_raises(ValueError, galsim.hsm.EstimateShear, im,
                                 p_im, weight_im)
        ## excludes all pixels
        weight_im = galsim.ImageI(imsize, imsize)
        np.testing.assert_raises(RuntimeError, galsim.hsm.FindAdaptiveMom, im,
                                 weight_im)
        np.testing.assert_raises(RuntimeError, galsim.hsm.EstimateShear, im,
                                 p_im, weight_im)
        badpix_im = galsim.ImageI(imsize, imsize, init_value=-1)
        np.testing.assert_raises(RuntimeError, galsim.hsm.FindAdaptiveMom, im,
                                 good_weight_im, badpix_im)
        np.testing.assert_raises(RuntimeError, galsim.hsm.EstimateShear, im,
                                 p_im, good_weight_im, badpix_im)

    except ImportError:
        # assert_raises requires nose, which we don't want to force people to install.
        # So if they are running this without nose, we just skip these tests.
        pass

    # check moments, shear without mask
    resm = im.FindAdaptiveMom()
    ress = galsim.hsm.EstimateShear(im, p_im)

    # check moments, shear with weight image that includes all pixels
    weightall1 = galsim.ImageI(imsize, imsize, init_value=1)
    resm_weightall1 = im.FindAdaptiveMom(weightall1)
    ress_weightall1 = galsim.hsm.EstimateShear(im, p_im, weightall1)

    # We'll do this series of tests a few times, so encapsulate the code here.
    def check_equal(resm, ress, resm_test, ress_test, tag):
        np.testing.assert_equal(resm.observed_shape.e1,
                                resm_test.observed_shape.e1,
                                err_msg="e1 from FindAdaptiveMom changes " +
                                tag)
        np.testing.assert_equal(resm.observed_shape.e2,
                                resm_test.observed_shape.e2,
                                err_msg="e2 from FindAdaptiveMom changes " +
                                tag)
        np.testing.assert_equal(resm.moments_sigma,
                                resm_test.moments_sigma,
                                err_msg="sigma from FindAdaptiveMom changes " +
                                tag)
        np.testing.assert_equal(
            ress.observed_shape.e1,
            ress_test.observed_shape.e1,
            err_msg="observed e1 from EstimateShear changes " + tag)
        np.testing.assert_equal(
            ress.observed_shape.e2,
            ress_test.observed_shape.e2,
            err_msg="observed e2 from EstimateShear changes " + tag)
        np.testing.assert_equal(
            ress.moments_sigma,
            ress_test.moments_sigma,
            err_msg="observed sigma from EstimateShear changes " + tag)
        np.testing.assert_equal(
            ress.corrected_e1,
            ress_test.corrected_e1,
            err_msg="corrected e1 from EstimateShear changes " + tag)
        np.testing.assert_equal(
            ress.corrected_e2,
            ress_test.corrected_e2,
            err_msg="corrected e2 from EstimateShear changes " + tag)
        np.testing.assert_equal(
            ress.resolution_factor,
            ress_test.resolution_factor,
            err_msg="resolution factor from EstimateShear changes " + tag)

    check_equal(resm, ress, resm_weightall1, ress_weightall1,
                "when using inclusive weight")

    # check moments and shears with mask of edges, should be nearly the same
    # (this seems dumb, but it's helpful for keeping track of whether the pointers in the C++ code
    # are being properly updated despite the masks.  If we monkey in that code again, it will be a
    # useful check.)
    maskedge = galsim.ImageI(imsize, imsize, init_value=1)
    xmin = maskedge.xmin
    xmax = maskedge.xmax
    ymin = maskedge.ymin
    ymax = maskedge.ymax
    edgenum = 3
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                maskedge.setValue(ind1, ind2, 0)
    resm_maskedge = im.FindAdaptiveMom(maskedge)
    ress_maskedge = galsim.hsm.EstimateShear(im, p_im, maskedge)
    test_decimal = 4
    np.testing.assert_almost_equal(
        resm.observed_shape.e1,
        resm_maskedge.observed_shape.e1,
        decimal=test_decimal,
        err_msg="e1 from FindAdaptiveMom changes when masking edge")
    np.testing.assert_almost_equal(
        resm.observed_shape.e2,
        resm_maskedge.observed_shape.e2,
        decimal=test_decimal,
        err_msg="e2 from FindAdaptiveMom changes when masking edge")
    np.testing.assert_almost_equal(
        resm.moments_sigma,
        resm_maskedge.moments_sigma,
        decimal=test_decimal,
        err_msg="sigma from FindAdaptiveMom changes when masking edge")
    np.testing.assert_almost_equal(
        ress.observed_shape.e1,
        ress_maskedge.observed_shape.e1,
        decimal=test_decimal,
        err_msg="observed e1 from EstimateShear changes when masking edge")
    np.testing.assert_almost_equal(
        ress.observed_shape.e2,
        ress_maskedge.observed_shape.e2,
        decimal=test_decimal,
        err_msg="observed e2 from EstimateShear changes when masking edge")
    np.testing.assert_almost_equal(
        ress.moments_sigma,
        ress_maskedge.moments_sigma,
        decimal=test_decimal,
        err_msg="observed sigma from EstimateShear changes when masking edge")
    np.testing.assert_almost_equal(
        ress.corrected_e1,
        ress_maskedge.corrected_e1,
        decimal=test_decimal,
        err_msg="corrected e1 from EstimateShear changes when masking edge")
    np.testing.assert_almost_equal(
        ress.corrected_e2,
        ress_maskedge.corrected_e2,
        decimal=test_decimal,
        err_msg="corrected e2 from EstimateShear changes when masking edge")
    np.testing.assert_almost_equal(
        ress.resolution_factor,
        ress_maskedge.resolution_factor,
        decimal=test_decimal,
        err_msg="resolution factor from EstimateShear changes when masking edge"
    )

    # check that results don't change *at all* i.e. using assert_equal when we do this edge masking
    # in different ways:
    ## do the same as the previous test, but with weight map that is floats (0.0 or 1.0)
    maskedge = galsim.ImageF(imsize, imsize, init_value=1.)
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                maskedge.setValue(ind1, ind2, 0.)
    resm_maskedge1 = im.FindAdaptiveMom(maskedge)
    ress_maskedge1 = galsim.hsm.EstimateShear(im, p_im, maskedge)
    check_equal(resm_maskedge, ress_maskedge, resm_maskedge1, ress_maskedge1,
                "when masking with floats")

    ## make the weight map for allowed pixels a nonzero value that also != 1
    maskedge = galsim.ImageF(imsize, imsize, init_value=2.3)
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                maskedge.setValue(ind1, ind2, 0.)
    resm_maskedge1 = im.FindAdaptiveMom(maskedge)
    ress_maskedge1 = galsim.hsm.EstimateShear(im, p_im, maskedge)
    check_equal(resm_maskedge, ress_maskedge, resm_maskedge1, ress_maskedge1,
                "when masking with floats != 1")

    ## make the weight map all equal to 1, and use a badpix map with a range of nonzero values
    maskedge = galsim.ImageI(imsize, imsize, init_value=1)
    badpixedge = galsim.ImageI(imsize, imsize, init_value=0)
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                badpixedge.setValue(ind1, ind2, ind1 + 1)
    resm_maskedge1 = im.FindAdaptiveMom(maskedge, badpixedge)
    ress_maskedge1 = galsim.hsm.EstimateShear(im, p_im, maskedge, badpixedge)
    check_equal(resm_maskedge, ress_maskedge, resm_maskedge1, ress_maskedge1,
                "when masking with badpix")

    ## same as previous, but with badpix of floats
    maskedge = galsim.ImageI(imsize, imsize, init_value=1)
    badpixedge = galsim.ImageF(imsize, imsize, init_value=0.)
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                badpixedge.setValue(ind1, ind2, float(ind1 + 1))
    resm_maskedge1 = im.FindAdaptiveMom(maskedge, badpixedge)
    ress_maskedge1 = galsim.hsm.EstimateShear(im, p_im, maskedge, badpixedge)
    check_equal(resm_maskedge, ress_maskedge, resm_maskedge1, ress_maskedge1,
                "when masking with badpix (floats)")

    ## do some of the masking using weight map, and the rest using badpix
    maskedge = galsim.ImageI(imsize, imsize, init_value=1)
    badpixedge = galsim.ImageI(imsize, imsize, init_value=0)
    meanval = int(0.5 * (xmin + xmax))
    for ind1 in range(xmin, xmax + 1):
        for ind2 in range(ymin, ymax + 1):
            if (ind1 <= (xmin + edgenum)) or (ind1 >= (xmax - edgenum)) or (
                    ind2 <= (ymin + edgenum)) or (ind2 >= (ymax - edgenum)):
                if ind1 < meanval:
                    badpixedge.setValue(ind1, ind2, 1)
                else:
                    maskedge.setValue(ind1, ind2, 0)
    resm_maskedge1 = im.FindAdaptiveMom(maskedge, badpixedge)
    ress_maskedge1 = galsim.hsm.EstimateShear(im, p_im, maskedge, badpixedge)
    check_equal(resm_maskedge, ress_maskedge, resm_maskedge1, ress_maskedge1,
                "when masking with badpix and weight map")

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Ejemplo n.º 7
0
def main(argv):
    """
    Getting reasonably close to including all the principle features of an image from a
    ground-based telescope:
      - Use a bulge plus disk model for the galaxy 
      - Both galaxy components are Sersic profiles (n=3.5 and n=1.5 respectively)
      - Let the PSF have both atmospheric and optical components.
      - The atmospheric component is a Kolmogorov spectrum.
      - The optical component has some defocus, coma, and astigmatism.
      - Add both Poisson noise to the image and Gaussian read noise.
      - Let the pixels be slightly distorted relative to the sky.
    """

    gal_flux = 1.e6  # ADU  ("Analog-to-digital units", the units of the numbers on a CCD)
    bulge_n = 3.5  #
    bulge_re = 2.3  # arcsec
    disk_n = 1.5  #
    disk_r0 = 0.85  # arcsec (corresponds to half_light_radius of ~3.7 arcsec)
    bulge_frac = 0.3  #
    gal_q = 0.73  # (axis ratio 0 < q < 1)
    gal_beta = 23  # degrees (position angle on the sky)
    atmos_fwhm = 2.1  # arcsec
    atmos_e = 0.13  #
    atmos_beta = 0.81  # radians
    opt_defocus = 0.53  # wavelengths
    opt_a1 = -0.29  # wavelengths
    opt_a2 = 0.12  # wavelengths
    opt_c1 = 0.64  # wavelengths
    opt_c2 = -0.33  # wavelengths
    opt_obscuration = 0.3  # linear scale size of secondary mirror obscuration
    lam = 800  # nm    NB: don't use lambda - that's a reserved word.
    tel_diam = 4.  # meters
    pixel_scale = 0.23  # arcsec / pixel
    image_size = 64  # n x n pixels
    wcs_g1 = -0.02  #
    wcs_g2 = 0.01  #
    sky_level = 2.5e4  # ADU / arcsec^2
    gain = 1.7  # photons / ADU
    read_noise = 0.3  # ADU / pixel

    random_seed = 1314662

    # Initialize the (pseudo-)random number generator that we will be using below.
    rng = galsim.BaseDeviate(random_seed)

    # Define the galaxy profile.
    # Normally Sersic profiles are specified by half-light radius, the radius that
    # encloses half of the total flux.  However, for some purposes, it can be
    # preferable to instead specify the scale radius, where the surface brightness
    # drops to 1/e of the central peak value.
    bulge = galsim.Sersic(bulge_n, half_light_radius=bulge_re)
    disk = galsim.Sersic(disk_n, scale_radius=disk_r0)

    # Objects may be multiplied by a scalar (which means scaling the flux) and also
    # added to each other.
    gal = bulge_frac * bulge + (1 - bulge_frac) * disk
    # Could also have written the following, which does the same thing:
    #   gal = galsim.Add([ bulge.withFlux(bulge_frac) , disk.withFlux(1-bulge_frac) ])
    # Both syntaxes work with more than two summands as well.

    # Set the overall flux of the combined object.
    gal = gal.withFlux(gal_flux)

    # Set the shape of the galaxy according to axis ratio and position angle
    # Note: All angles in GalSim must have explicit units.
    gal_shape = galsim.Shear(q=gal_q, beta=gal_beta * galsim.degrees)
    gal = gal.shear(gal_shape)

    # Define the atmospheric part of the PSF.
    # Note: the flux here is the default flux=1.
    atmos = galsim.Kolmogorov(fwhm=atmos_fwhm)
    # For the PSF shape here, we use ellipticity rather than axis ratio.
    # And the position angle can be either degrees or radians.  Here we chose radians.
    atmos = atmos.shear(e=atmos_e, beta=atmos_beta * galsim.radians)
    logger.debug('Made atmospheric PSF profile')

    # Define the optical part of the PSF:
    # The first argument of OpticalPSF below is lambda/diam (wavelength of light / telescope
    # diameter), which needs to be in the same units used to specify the image scale.  We are using
    # arcsec for that, so we have to self-consistently use arcsec here, using the following
    # calculation:
    lam_over_diam = lam * 1.e-9 / tel_diam  # radians
    lam_over_diam *= 206265  # arcsec
    # Note that we could also have made GalSim do the conversion for us if we did not know the right
    # factor:
    # lam_over_diam = lam * 1.e-9 / tel_diam * galsim.radians
    # lam_over_diam = lam_over_diam / galsim.arcsec
    logger.debug('Calculated lambda over diam = %f arcsec', lam_over_diam)
    # The rest of the values should be given in units of the wavelength of the incident light.
    optics = galsim.OpticalPSF(lam_over_diam,
                               defocus=opt_defocus,
                               coma1=opt_c1,
                               coma2=opt_c2,
                               astig1=opt_a1,
                               astig2=opt_a2,
                               obscuration=opt_obscuration)
    logger.debug('Made optical PSF profile')

    # So far, our coordinate transformation between image and sky coordinates has been just a
    # scaling of the units between pixels and arcsec, which we have defined as the "pixel scale".
    # This is fine for many purposes, so we have made it easy to treat the coordinate systems
    # this way via the `scale` parameter to commands like drawImage.  However, in general, the
    # transformation between the two coordinate systems can be more complicated than that,
    # including distortions, rotations, variation in pixel size, and so forth.  GalSim can
    # model a number of different "World Coordinate System" (WCS) transformations.  See the
    # docstring for BaseWCS for more information.

    # In this case, we use a WCS that includes a distortion (specified as g1,g2 in this case),
    # which we call a ShearWCS.
    wcs = galsim.ShearWCS(scale=pixel_scale,
                          shear=galsim.Shear(g1=wcs_g1, g2=wcs_g2))
    logger.debug('Made the WCS')

    # Next we will convolve the components in world coordinates.
    psf = galsim.Convolve([atmos, optics])
    final = galsim.Convolve([psf, gal])
    logger.debug('Convolved components into final profile')

    # This time we specify a particular size for the image rather than let GalSim
    # choose the size automatically.  GalSim has several kinds of images that it can use:
    #   ImageF uses 32-bit floats    (like a C float, aka numpy.float32)
    #   ImageD uses 64-bit floats    (like a C double, aka numpy.float64)
    #   ImageS uses 16-bit integers  (usually like a C short, aka numpy.int16)
    #   ImageI uses 32-bit integers  (usually like a C int, aka numpy.int32)
    # If you let the GalSim drawImage command create the image for you, it will create an ImageF.
    # However, you can make a different type if you prefer.  In this case, we still use
    # ImageF, since 32-bit floats are fine.  We just want to set the size explicitly.
    image = galsim.ImageF(image_size, image_size)
    # Draw the image with the given WCS.  Note that we use wcs rather than scale when the
    # WCS is more complicated than just a pixel scale.
    final.drawImage(image=image, wcs=wcs)

    # Also draw the effective PSF by itself and the optical PSF component alone.
    image_epsf = galsim.ImageF(image_size, image_size)
    psf.drawImage(image_epsf, wcs=wcs)

    # We also draw the optical part of the PSF at its own Nyquist-sampled pixel size
    # in order to better see the features of the (highly structured) profile.
    # In this case, we draw a "surface brightness image" using method='sb'.  Rather than
    # integrate the flux over the area of each pixel, this method just samples the surface
    # brightness value at the locations of the pixel centers.  We will encounter a few other
    # drawing methods as we go through this sequence of demos.  cf. demos 7, 8, 10, and 11.
    image_opticalpsf = optics.drawImage(method='sb')
    logger.debug('Made image of the profile')

    # Add a constant sky level to the image.
    image += sky_level * pixel_scale**2

    # This time, we use CCDNoise to model the real noise in a CCD image.  It takes a sky level,
    # gain, and read noise, so it can be a bit more realistic than the simpler GaussianNoise
    # or PoissonNoise that we used in demos 1 and 2.
    #
    # The gain is in units of photons/ADU.  Technically, real CCDs quote the gain as e-/ADU.
    # An ideal CCD has one electron per incident photon, but real CCDs have quantum efficiencies
    # less than 1, so not every photon triggers an electron.  We are essentially folding
    # the quantum efficiency (and filter transmission and anything else like that) into the gain.
    # The read_noise value is given as ADU/pixel.  This is modeled as a pure Gaussian noise
    # added to the image after applying the pure Poisson noise.
    image.addNoise(galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))

    # Subtract off the sky.
    image -= sky_level * pixel_scale**2
    logger.debug('Added Gaussian and Poisson noise')

    # Write the images to files.
    file_name = os.path.join('output', 'demo3.fits')
    file_name_epsf = os.path.join('output', 'demo3_epsf.fits')
    file_name_opticalpsf = os.path.join('output', 'demo3_opticalpsf.fits')
    image.write(file_name)
    image_epsf.write(file_name_epsf)
    image_opticalpsf.write(file_name_opticalpsf)
    logger.info('Wrote image to %r', file_name)
    logger.info('Wrote effective PSF image to %r', file_name_epsf)
    logger.info('Wrote optics-only PSF image (Nyquist sampled) to %r',
                file_name_opticalpsf)

    # Check that the HSM package, which is bundled with GalSim, finds a good estimate
    # of the shear.
    results = galsim.hsm.EstimateShear(image, image_epsf)

    logger.info('HSM reports that the image has observed shape and size:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)',
                results.observed_shape.e1, results.observed_shape.e2,
                results.moments_sigma)
    logger.info(
        'When carrying out Regaussianization PSF correction, HSM reports')
    logger.info('    e1, e2 = %.3f, %.3f', results.corrected_e1,
                results.corrected_e2)
    logger.info(
        'Expected values in the limit that noise and non-Gaussianity are negligible:'
    )
    # Convention for shear addition is to apply the second term initially followed by the first.
    # So this needs to be the WCS shear + the galaxy shape in that order.
    total_shape = galsim.Shear(g1=wcs_g1, g2=wcs_g2) + gal_shape
    logger.info('    e1, e2 = %.3f, %.3f', total_shape.e1, total_shape.e2)
Ejemplo n.º 8
0
def main(argv):
    """
    Make a fits image cube using real COSMOS galaxies from a catalog describing the training
    sample.

      - The number of images in the cube matches the number of rows in the catalog.
      - Each image size is computed automatically by GalSim based on the Nyquist size.
      - Both galaxies and stars.
      - PSF is a double Gaussian, the same for each galaxy.
      - Galaxies are randomly rotated to remove the imprint of any lensing shears in the COSMOS
        data.
      - The same shear is applied to each galaxy.
      - Noise is Poisson using a nominal sky value of 1.e6 ADU/arcsec^2,
        the noise in the original COSMOS data.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo6")

    # Define some parameters we'll use below.

    cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
    dir = 'data'
    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')
    cube_file_name = os.path.join('output', 'cube_real.fits')
    psf_file_name = os.path.join('output', 'psf_real.fits')

    random_seed = 1512413
    sky_level = 1.e6  # ADU / arcsec^2
    pixel_scale = 0.16  # arcsec
    gal_flux = 1.e5  # arbitrary choice, makes nice (not too) noisy images
    gal_g1 = -0.027  #
    gal_g2 = 0.031  #
    gal_mu = 1.082  # mu = ( (1-kappa)^2 - g1^2 - g2^2 )^-1
    psf_inner_fwhm = 0.6  # arcsec
    psf_outer_fwhm = 2.3  # arcsec
    psf_inner_fraction = 0.8  # fraction of total PSF flux in the inner Gaussian
    psf_outer_fraction = 0.2  # fraction of total PSF flux in the inner Gaussian
    ngal = 100

    logger.info('Starting demo script 6 using:')
    logger.info('    - real galaxies from catalog %r', cat_file_name)
    logger.info('    - double Gaussian PSF')
    logger.info('    - pixel scale = %.2f', pixel_scale)
    logger.info('    - Applied gravitational shear = (%.3f,%.3f)', gal_g1,
                gal_g2)
    logger.info('    - Poisson noise (sky level = %.1e).', sky_level)

    # Read in galaxy catalog
    # Note: dir is the directory both for the catalog itself and also the directory prefix
    # for the image files listed in the catalog.
    # If the images are in a different directory, you may also specify image_dir, which gives
    # the relative path from dir to wherever the images are located.
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # Make the double Gaussian PSF
    psf1 = galsim.Gaussian(fwhm=psf_inner_fwhm, flux=psf_inner_fraction)
    psf2 = galsim.Gaussian(fwhm=psf_outer_fwhm, flux=psf_outer_fraction)
    psf = psf1 + psf2
    # Draw the PSF with no noise.
    psf_image = psf.drawImage(scale=pixel_scale)
    # write to file
    psf_image.write(psf_file_name)
    logger.info('Created PSF and wrote to file %r', psf_file_name)

    # Build the images
    all_images = []
    for k in range(ngal):
        logger.debug('Start work on image %d', k)
        t1 = time.time()

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

        gal = galsim.RealGalaxy(real_galaxy_catalog, index=k, flux=gal_flux)
        logger.debug('   Read in training sample galaxy and PSF from file')
        t2 = time.time()

        # Rotate by a random angle
        theta = 2. * math.pi * rng() * galsim.radians
        gal = gal.rotate(theta)

        # Apply the desired shear
        gal = gal.shear(g1=gal_g1, g2=gal_g2)

        # Also apply a magnification mu = ( (1-kappa)^2 - |gamma|^2 )^-1
        # This conserves surface brightness, so it scales both the area and flux.
        gal = gal.magnify(gal_mu)

        # Make the combined profile
        final = galsim.Convolve([psf, gal])

        # Offset by up to 1/2 pixel in each direction
        # We had previously (in demo4 and demo5) used shift(dx,dy) as a way to shift the center of
        # the image.  Since that is applied to the galaxy, the units are arcsec (since the galaxy
        # profile itself doesn't know about the pixel scale).  Here, the offset applies to the
        # drawn image, which does know about the pixel scale, so the units of offset are pixels,
        # not arcsec.  Here, we apply an offset of up to half a pixel in each direction.
        dx = rng() - 0.5
        dy = rng() - 0.5

        # Draw the profile
        if k == 0:
            # Note that the offset argument may be a galsim.PositionD object or a tuple (dx,dy).
            im = final.drawImage(scale=pixel_scale, offset=(dx, dy))
            xsize, ysize = im.array.shape
        else:
            im = galsim.ImageF(xsize, ysize)
            final.drawImage(im, scale=pixel_scale, offset=(dx, dy))

        logger.debug('   Drew image')
        t3 = time.time()

        # Add a constant background level
        background = sky_level * pixel_scale**2
        im += background

        # Add Poisson noise.  This time, we don't give a sky_level, since we have already
        # added it to the image, so we don't want any more added.  The sky_level parameter
        # really defines how much _extra_ sky should be added above what is already in the image.
        im.addNoise(galsim.PoissonNoise(rng))

        logger.debug('   Added Poisson noise')
        t4 = time.time()

        # Store that into the list of all images
        all_images += [im]
        t5 = time.time()

        logger.debug('   Times: %f, %f, %f, %f', t2 - t1, t3 - t2, t4 - t3,
                     t5 - t4)
        logger.info('Image %d: size = %d x %d, total time = %f sec', k, xsize,
                    ysize, t5 - t1)

    logger.info('Done making images of galaxies')

    # Now write the image to disk.
    # We write the images to a fits data cube.
    galsim.fits.writeCube(all_images, cube_file_name)
    logger.info('Wrote image to fits data cube %r', cube_file_name)
Ejemplo n.º 9
0
def galaxy2(sky=0.15, galSNR=6000.0, fwhm=7.0):
    """Make the second fake galaxy.

    @param  sky:
        Per pixel sky value, used to get Poisson noise (default=0.2)

    @param  galSNR:
        The SNR of the galaxy, used to get the noise (default=5000.0)

    @param  fwhm:
        FWHM of the Gaussian PSF used to convolve the image (default=6.0)

    ------
    Three component edge-on disk galaxy with bulge+disk+halo   :

        Component 1: xCen=420.0, yCen=380; FLux=300;
                     Re=8.0;   n=3.0, q=0.8, PA1=45.0

        Component 2: xCen=420.0, yCen=380; FLux=500;
                     Re=40.0;  n=0.8, q=0.1, PA1=45.0

        Component 3: xCen=420.0, yCen=380; FLux=600;
                     Re=35.0;  n=1.5, q=0.6, PA1=40.0

        Contamination 1: FLux=100; Re=20.0; n=2.5, q=0.7, PA1=0.0

        Contamination 2: FLux=200; Re=30.0; n=2.0, q=0.6, PA1=75.0
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("galaxy2")

    # Basic information of the image
    nx, ny = 800, 800
    exptime = 100.0
    scale = 1.0
    logger.info('Galaxy2: Sky=%6.2f, SNR=%5d', sky, galSNR)

    # Get the PSF, and save the PSF image
    psf = getPsf(fwhm, psfFile='galaxy2_psf.fits')

    # Make a GalSim Image
    gal2Img = galsim.ImageF(nx, ny)
    imgCen = gal2Img.bounds.trueCenter()
    imgWcs = galsim.OffsetWCS(scale=scale, origin=imgCen)
    gal2Img.wcs = imgWcs

    # Component 1: flux1 = 300; reff1 = 8.0; nser1 = 3.0; q1 = 0.8; pa1 = 45.0
    comp1 = galsim.Sersic(
        n=3.0, half_light_radius=8.0, flux=(300.0 * exptime)).shear(
            q=0.8,
            beta=(0.0 * galsim.degrees)).rotate(45.0 * galsim.degrees).shift(
                (420.0 - (nx / 2.0)), (380.0 - (ny / 2.0)))

    # Component 1: flux1 = 500; reff1 = 40.0; nser1 = 0.8; q1 = 0.1; pa1 = 45.0
    comp2 = galsim.Sersic(n=0.8,
                          half_light_radius=40.0,
                          flux=(500.0 * exptime)).shear(
                              q=0.10, beta=(0.0 * galsim.degrees)).rotate(
                                  45.0 * galsim.degrees).shift(
                                      (420.0 - (nx / 2.0)),
                                      (380.0 - (ny / 2.0)))

    # Component 3: flux1 = 600; reff1 = 35.0; nser1 = 1.5; q1 = 0.6; pa1 = 40.0
    comp3 = galsim.Sersic(n=1.5,
                          half_light_radius=35.0,
                          flux=(600.0 * exptime)).shear(
                              q=0.6, beta=(0.0 * galsim.degrees)).rotate(
                                  40.0 * galsim.degrees).shift(
                                      (420.0 - (nx / 2.0)),
                                      (380.0 - (ny / 2.0)))

    # Contamination 1:
    cont1 = galsim.Sersic(n=2.5,
                          half_light_radius=20.0,
                          flux=(100.0 * exptime)).shear(
                              q=0.7, beta=(0.0 * galsim.degrees)).rotate(
                                  0.0 * galsim.degrees).shift(50, 200)

    # Contamination 2:
    cont2 = galsim.Sersic(n=2.0,
                          half_light_radius=30.0,
                          flux=(200.0 * exptime)).shear(
                              q=0.6, beta=(0.0 * galsim.degrees)).rotate(
                                  75.0 * galsim.degrees).shift(-300, -190)

    # Add all components together
    gal2 = galsim.Add([comp1, comp2, comp3, cont1, cont2])

    # Convolution
    gal2Conv = galsim.Convolve([psf, gal2])

    # Draw the image
    gal2Conv.drawImage(gal2Img, method='no_pixel')

    # Add Noise
    rng = random.seed(datetime.now())
    noise = galsim.PoissonNoise(rng, sky_level=sky)
    gal2Img.addNoiseSNR(noise, galSNR)

    # Save the FITS file
    gal2File = 'galaxy2_img.fits'
    logger.info('Write to FITS file : %s', gal2File)
    galsim.fits.write(gal2Img, gal2File)

    # Save the PNG picture
    savePng(gal2Img, pngFile='galaxy2_img.png')

    return gal2Img
Ejemplo n.º 10
0
def test_shapelet_adjustments():
    """Test that adjusting the Shapelet profile in various ways does the right thing
    """
    import time
    t1 = time.time()

    ftypes = [np.float32, np.float64]

    nx = 128
    ny = 128
    scale = 0.2
    im = galsim.ImageF(nx,ny, scale=scale)

    sigma = 1.8
    order = 6
    bvec = [1.3,                                            # n = 0
            0.02, 0.03,                                     # n = 1
            0.23, -0.19, 0.08,                              # n = 2
            0.01, 0.02, 0.04, -0.03,                        # n = 3
            -0.09, 0.07, -0.11, -0.08, 0.11,                # n = 4
            -0.03, -0.02, -0.08, 0.01, -0.06, -0.03,        # n = 5
            0.06, -0.02, 0.00, -0.05, -0.04, 0.01, 0.09 ]   # n = 6

    ref_shapelet = galsim.Shapelet(sigma=sigma, order=order, bvec=bvec)
    ref_im = galsim.ImageF(nx,ny)
    ref_shapelet.drawImage(ref_im, scale=scale)

    # Test that the Shapelet withFlux does the same thing as the GSObject withFlux
    gsref_shapelet = galsim.GSObject(ref_shapelet)  # Make it opaque to the Shapelet versions
    gsref_shapelet.withFlux(23.).drawImage(ref_im, method='no_pixel')
    shapelet = galsim.Shapelet(sigma=sigma, order=order, bvec=bvec)
    shapelet.withFlux(23.).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet withFlux disagrees with GSObject withFlux")

    # Test that scaling the Shapelet flux does the same thing as the GSObject scaling
    gsref_shapelet *= 0.23
    gsref_shapelet.drawImage(ref_im, method='no_pixel')
    shapelet *= 0.23
    shapelet.drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet *= 0.23 disagrees with GSObject *= 0.23")

    # Test that the Shapelet rotate does the same thing as the GSObject rotate
    gsref_shapelet.rotate(23. * galsim.degrees).drawImage(ref_im, method='no_pixel')
    shapelet.rotate(23. * galsim.degrees).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet rotate disagrees with GSObject rotate")

    # Test that the Shapelet dilate does the same thing as the GSObject dilate
    gsref_shapelet.dilate(1.3).drawImage(ref_im, method='no_pixel')
    shapelet.dilate(1.3).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet dilate disagrees with GSObject dilate")

    # Test that the Shapelet magnify does the same thing as the GSObject magnify
    gsref_shapelet.magnify(0.8).drawImage(ref_im, method='no_pixel')
    shapelet.magnify(0.8).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet magnify disagrees with GSObject magnify")

    # Test that lens works on Shapelet
    gsref_shapelet.lens(-0.05, 0.15, 1.1).drawImage(ref_im, method='no_pixel')
    shapelet.lens(-0.05, 0.15, 1.1).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array, ref_im.array, 6,
        err_msg="Shapelet lens disagrees with GSObject lens")

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 11
0
    def buildImage(self, config, base, image_num, obj_num, logger):
        """Build an Image containing multiple objects placed at arbitrary locations.

        @param config       The configuration dict for the image field.
        @param base         The base configuration dict.
        @param image_num    The current image number.
        @param obj_num      The first object number in the image.
        @param logger       If given, a logger object to log progress.

        @returns the final image and the current noise variance in the image as a tuple
        """
        full_xsize = base['image_xsize']
        full_ysize = base['image_ysize']
        wcs = base['wcs']

        full_image = galsim.ImageF(full_xsize, full_ysize)
        full_image.setOrigin(base['image_origin'])
        full_image.wcs = wcs
        full_image.setZero()
        base['current_image'] = full_image

        if 'image_pos' in config and 'world_pos' in config:
            raise AttributeError(
                "Both image_pos and world_pos specified for Scattered image.")

        if 'image_pos' not in config and 'world_pos' not in config:
            xmin = base['image_origin'].x
            xmax = xmin + full_xsize - 1
            ymin = base['image_origin'].y
            ymax = ymin + full_ysize - 1
            config['image_pos'] = {
                'type': 'XY',
                'x': {
                    'type': 'Random',
                    'min': xmin,
                    'max': xmax
                },
                'y': {
                    'type': 'Random',
                    'min': ymin,
                    'max': ymax
                }
            }

        stamps, current_vars = galsim.config.BuildStamps(self.nobjects,
                                                         base,
                                                         logger=logger,
                                                         obj_num=obj_num,
                                                         do_noise=False)

        base['index_key'] = 'image_num'

        for k in range(self.nobjects):
            # This is our signal that the object was skipped.
            if stamps[k] is None: continue
            bounds = stamps[k].bounds & full_image.bounds
            logger.debug('image %d: full bounds = %s', image_num,
                         str(full_image.bounds))
            logger.debug('image %d: stamp %d bounds = %s', image_num, k,
                         str(stamps[k].bounds))
            logger.debug('image %d: Overlap = %s', image_num, str(bounds))
            if bounds.isDefined():
                full_image[bounds] += stamps[k][bounds]
            else:
                logger.info(
                    "Object centered at (%d,%d) is entirely off the main image,\n"
                    % (stamps[k].center.x, stamps[k].center.y) +
                    "whose bounds are (%d,%d,%d,%d)." %
                    (full_image.bounds.xmin, full_image.bounds.xmax,
                     full_image.bounds.ymin, full_image.bounds.ymax))

        # Bring the image so far up to a flat noise variance
        current_var = galsim.config.FlattenNoiseVariance(
            base, full_image, stamps, current_vars, logger)

        return full_image, current_var
Ejemplo n.º 12
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

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

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

    # Define some parameters we'll use below.

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

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

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

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

    psf_fwhm = 0.65  # arcsec

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

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

    logger.info('Starting demo script 7')

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

    # Make the PSF profiles:
    psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams)
    psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams)
    psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm,
                                 flux=0.2,
                                 gsparams=gsparams)
    psf3 = psf3_inner + psf3_outer
    atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    # The OpticalPSF and set of Zernike values chosen below correspond to a reasonably well aligned,
    # smallish ~0.3m / 12 inch diameter telescope with a central obscuration of ~0.12m or 5 inches
    # diameter, being used in optical wavebands.
    # In the Noll convention, the value of the Zernike coefficient also gives the RMS optical path
    # difference across a circular pupil.  An RMS difference of ~0.5 or larger indicates that parts
    # of the wavefront are in fully destructive interference, and so we might expect aberrations to
    # become strong when Zernike aberrations summed in quadrature approach 0.5 wave.
    # The aberrations chosen in this case correspond to operating close to a 0.25 wave RMS optical
    # path difference:
    optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm,
                               obscuration=0.4,
                               defocus=0.06,
                               astig1=0.12,
                               astig2=-0.08,
                               coma1=0.07,
                               coma2=0.04,
                               spher=-0.13,
                               gsparams=gsparams)
    psf4 = galsim.Convolve([atmos, optics
                            ])  # Convolve inherits the gsparams from the first
    # item in the list.  (Or you can supply a gsparams
    # argument explicitly if you want to override this.)
    atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams)
    psf5 = galsim.Convolve([atmos, optics])
    psfs = [psf1, psf2, psf3, psf4, psf5]
    psf_names = [
        "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF",
        "Kolmogorov * Airy"
    ]
    psf_times = [0, 0, 0, 0, 0]
    psf_fft_times = [0, 0, 0, 0, 0]
    psf_phot_times = [0, 0, 0, 0, 0]

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

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

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

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

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

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

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

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

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

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

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

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

                # Draw the profile
                final.draw(fft_image)

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

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

                t4 = time.time()

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

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

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

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

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

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

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

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

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

for i, index in enumerate(indices):

    lam = spectrum["lam"][index]
    nphot = spectrum["nphot"][index]

    print index, lam, nphot

    psf = galsim.OpticalPSF(lam=lam,
                            diam=1.2,
                            obscuration=0.29,
                            nstruts=6,
                            flux=1.0)

    image = galsim.ImageF(config.psfstampsize, config.psfstampsize)
    psf.drawImage(image=image, scale=0.1 / config.psfoversampling)

    image *= nphot

    images.append(image)

image = sum(images)

totflux = np.sum(image.array)
image /= totflux

print "Output image flux:", np.sum(image.array)

image.write(os.path.join(config.workdir, "psf.fits"))
Ejemplo n.º 14
0
def test_sersic():
    """Test the generation of a specific Sersic profile against a known result.
    """
    # Test Sersic
    savedImg = galsim.fits.read(os.path.join(imgdir, "sersic_3_1.fits"))
    dx = 0.2
    myImg = galsim.ImageF(savedImg.bounds, scale=dx)
    myImg.setCenter(0, 0)

    sersic = galsim.Sersic(n=3, flux=1, half_light_radius=1)
    sersic.drawImage(myImg, scale=dx, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg="Using GSObject Sersic disagrees with expected result")

    # Check with default_params
    sersic = galsim.Sersic(n=3,
                           flux=1,
                           half_light_radius=1,
                           gsparams=default_params)
    sersic.drawImage(myImg, scale=dx, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Sersic with default_params disagrees with expected result"
    )
    sersic = galsim.Sersic(n=3,
                           flux=1,
                           half_light_radius=1,
                           gsparams=galsim.GSParams())
    sersic.drawImage(myImg, scale=dx, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Sersic with GSParams() disagrees with expected result")

    # Use non-unity values.
    sersic = galsim.Sersic(n=3, flux=1.7, half_light_radius=2.3)
    gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8)
    sersic2 = galsim.Sersic(n=3, flux=1.7, half_light_radius=2.3, gsparams=gsp)
    assert sersic2 != sersic
    assert sersic2 == sersic.withGSParams(gsp)
    check_basic(sersic, "Sersic")

    # Test photon shooting.
    # Convolve with a small gaussian to smooth out the central peak.
    sersic2 = galsim.Convolve(sersic, galsim.Gaussian(sigma=0.3))
    do_shoot(sersic2, myImg, "Sersic")

    # Test kvalues
    do_kvalue(sersic, myImg, "Sersic")

    # Check picklability
    do_pickle(sersic, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(sersic)

    # Now repeat everything using a truncation.  (Above had no truncation.)

    # Test Truncated Sersic
    # Don't use an integer truncation, since we don't want the truncation line to pass directly
    # through the center of a pixel where numerical rounding differences may decide whether the
    # value is zero or not.
    # This regression test compares to an image built using the code base at 82259f0
    savedImg = galsim.fits.read(os.path.join(imgdir, "sersic_3_1_10.fits"))
    myImg = galsim.ImageF(savedImg.bounds, scale=dx)
    myImg.setCenter(0, 0)

    sersic = galsim.Sersic(n=3, flux=1, half_light_radius=1, trunc=9.99)
    sersic.drawImage(myImg, scale=dx, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg="Using truncated GSObject Sersic disagrees with expected result"
    )

    # Use non-unity values.
    test_flux = 1.8
    sersic = galsim.Sersic(n=3,
                           flux=test_flux,
                           half_light_radius=2.3,
                           trunc=5.9)
    cen = galsim.PositionD(0, 0)
    np.testing.assert_equal(sersic.centroid, cen)
    np.testing.assert_almost_equal(sersic.kValue(cen), (1 + 0j) * test_flux)
    np.testing.assert_almost_equal(sersic.flux, test_flux)
    np.testing.assert_almost_equal(sersic.xValue(cen), sersic.max_sb)

    check_basic(sersic, "Truncated Sersic")

    # Test photon shooting.
    # Convolve with a small gaussian to smooth out the central peak.
    sersic2 = galsim.Convolve(sersic, galsim.Gaussian(sigma=0.3))
    do_shoot(sersic2, myImg, "Truncated Sersic")

    # Test kvalues
    do_kvalue(sersic, myImg, "Truncated Sersic")

    # Check picklability
    do_pickle(sersic, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(sersic)

    # Check for normalization consistencies with kValue checks. xValues tested in test_sersic_radii.

    # For half-light radius specified truncated Sersic, with flux_untruncated flag set
    sersic = galsim.Sersic(n=3,
                           flux=test_flux,
                           half_light_radius=1,
                           trunc=10,
                           flux_untruncated=True)
    do_kvalue(
        sersic, myImg,
        "Truncated Sersic w/ flux_untruncated, half-light radius specified")

    # For scale radius specified Sersic
    sersic = galsim.Sersic(n=3, flux=test_flux, scale_radius=0.05)
    do_kvalue(sersic, myImg, "Sersic w/ scale radius specified")

    # For scale radius specified truncated Sersic
    sersic = galsim.Sersic(n=3, flux=test_flux, scale_radius=0.05, trunc=10)
    do_kvalue(sersic, myImg, "Truncated Sersic w/ scale radius specified")

    # For scale radius specified truncated Sersic, with flux_untruncated flag set
    sersic = galsim.Sersic(n=3,
                           flux=test_flux,
                           scale_radius=0.05,
                           trunc=10,
                           flux_untruncated=True)
    do_kvalue(sersic, myImg,
              "Truncated Sersic w/ flux_untruncated, scale radius specified")

    # Test severely truncated Sersic
    sersic = galsim.Sersic(n=4,
                           flux=test_flux,
                           half_light_radius=1,
                           trunc=1.45)
    do_kvalue(sersic, myImg, "Severely truncated n=4 Sersic")

    # Should raise an exception if both scale_radius and half_light_radius are provided.
    assert_raises(TypeError,
                  galsim.Sersic,
                  n=1.2,
                  scale_radius=3,
                  half_light_radius=1)
    assert_raises(TypeError, galsim.Sersic, n=1.2)
    assert_raises(TypeError,
                  galsim.DeVaucouleurs,
                  scale_radius=3,
                  half_light_radius=1)
    assert_raises(TypeError, galsim.DeVaucouleurs)

    # Allowed range is [0.3, 6.2]
    assert_raises(ValueError, galsim.Sersic, n=0.2, scale_radius=3)
    assert_raises(ValueError, galsim.Sersic, n=6.3, scale_radius=3)

    # trunc must be > sqrt(2) * hlr
    assert_raises(ValueError,
                  galsim.Sersic,
                  n=3,
                  half_light_radius=1,
                  trunc=1.4)
    assert_raises(ValueError,
                  galsim.DeVaucouleurs,
                  half_light_radius=1,
                  trunc=1.4)

    # Other errors
    assert_raises(TypeError, galsim.Sersic, scale_radius=3)
    assert_raises(ValueError, galsim.Sersic, n=3, scale_radius=3, trunc=-1)
    assert_raises(ValueError, galsim.DeVaucouleurs, scale_radius=3, trunc=-1)
Ejemplo n.º 15
0
def test_shapelet_adjustments():
    """Test that adjusting the Shapelet profile in various ways does the right thing
    """
    ftypes = [np.float32, np.float64]

    nx = 128
    ny = 128
    scale = 0.2
    im = galsim.ImageF(nx, ny, scale=scale)

    sigma = 1.8
    order = 6
    bvec = [
        1.3,  # n = 0
        0.02,
        0.03,  # n = 1
        0.23,
        -0.19,
        0.08,  # n = 2
        0.01,
        0.02,
        0.04,
        -0.03,  # n = 3
        -0.09,
        0.07,
        -0.11,
        -0.08,
        0.11,  # n = 4
        -0.03,
        -0.02,
        -0.08,
        0.01,
        -0.06,
        -0.03,  # n = 5
        0.06,
        -0.02,
        0.00,
        -0.05,
        -0.04,
        0.01,
        0.09
    ]  # n = 6

    ref_shapelet = galsim.Shapelet(sigma=sigma, order=order, bvec=bvec)
    ref_im = galsim.ImageF(nx, ny)
    ref_shapelet.drawImage(ref_im, scale=scale)

    # Test PQ and NM access
    np.testing.assert_equal(ref_shapelet.getPQ(0, 0), (bvec[0], 0))
    np.testing.assert_equal(ref_shapelet.getPQ(1, 1), (bvec[5], 0))
    np.testing.assert_equal(ref_shapelet.getPQ(1, 2), (bvec[8], -bvec[9]))
    np.testing.assert_equal(ref_shapelet.getPQ(3, 2), (bvec[19], bvec[20]))
    np.testing.assert_equal(ref_shapelet.getNM(0, 0), (bvec[0], 0))
    np.testing.assert_equal(ref_shapelet.getNM(2, 0), (bvec[5], 0))
    np.testing.assert_equal(ref_shapelet.getNM(3, -1), (bvec[8], -bvec[9]))
    np.testing.assert_equal(ref_shapelet.getNM(5, 1), (bvec[19], bvec[20]))

    # Test that the Shapelet withFlux does the same thing as the GSObject withFlux
    gsref_shapelet = galsim.GSObject(
        ref_shapelet)  # Make it opaque to the Shapelet versions
    gsref_shapelet.withFlux(23.).drawImage(ref_im, method='no_pixel')
    shapelet = galsim.Shapelet(sigma=sigma, order=order, bvec=bvec)
    shapelet.withFlux(23.).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet withFlux disagrees with GSObject withFlux")

    # Test that scaling the Shapelet flux does the same thing as the GSObject scaling
    (gsref_shapelet * 0.23).drawImage(ref_im, method='no_pixel')
    (shapelet * 0.23).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet *= 0.23 disagrees with GSObject *= 0.23")

    # Test that the Shapelet rotate does the same thing as the GSObject rotate
    gsref_shapelet.rotate(23. * galsim.degrees).drawImage(ref_im,
                                                          method='no_pixel')
    shapelet.rotate(23. * galsim.degrees).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet rotate disagrees with GSObject rotate")

    # Test that the Shapelet dilate does the same thing as the GSObject dilate
    gsref_shapelet.dilate(1.3).drawImage(ref_im, method='no_pixel')
    shapelet.dilate(1.3).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet dilate disagrees with GSObject dilate")

    # Test that the Shapelet expand does the same thing as the GSObject expand
    gsref_shapelet.expand(1.7).drawImage(ref_im, method='no_pixel')
    shapelet.expand(1.7).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet expand disagrees with GSObject expand")

    # Test that the Shapelet magnify does the same thing as the GSObject magnify
    gsref_shapelet.magnify(0.8).drawImage(ref_im, method='no_pixel')
    shapelet.magnify(0.8).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet magnify disagrees with GSObject magnify")

    # Test that lens works on Shapelet
    gsref_shapelet.lens(-0.05, 0.15, 1.1).drawImage(ref_im, method='no_pixel')
    shapelet.lens(-0.05, 0.15, 1.1).drawImage(im, method='no_pixel')
    np.testing.assert_array_almost_equal(
        im.array,
        ref_im.array,
        6,
        err_msg="Shapelet lens disagrees with GSObject lens")
Ejemplo n.º 16
0
k = 0
galarray = []
galarray0 = []
for i in range(100):
    k += 1
    rng = galsim.UniformDeviate(random_seed + k + 1)
    flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min
    this_gal = gal1.withFlux(flux)
    hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
    this_gal = this_gal.dilate(hlr)
    this_gal = this_gal.shear(g1=0.5, g2=0.5)
    final = galsim.Convolve([this_gal, psf])

    print(rng())

    image0 = galsim.ImageF(2 * nx + 2, ny, scale=pixel_scale)
    fft_image0 = image0[galsim.BoundsI(1, nx, 1, ny)]
    phot_image0 = image0[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]
    final.drawImage(fft_image0, method='fft')
    final.drawImage(phot_image0, method='phot')

    image = galsim.ImageF(2 * nx + 2, ny, scale=pixel_scale)
    fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
    phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]
    final.drawImage(fft_image, method='fft')

    sky_level_pixel = sky_level * pixel_scale**2
    fft_image.addNoise(galsim.PoissonNoise(
        rng, sky_level=sky_level_pixel))  #add noise
    rng = galsim.UniformDeviate(random_seed + k + 1)
    rng()
Ejemplo n.º 17
0
def test_shapelet_drawImage():
    """Test some measured properties of a drawn shapelet against the supposed true values
    """
    ftypes = [np.float32, np.float64]
    scale = 0.2
    test_flux = 23.

    im = galsim.ImageF(129, 129, scale=scale)
    for sigma in [1., 0.3, 2.4]:
        for order in [0, 2, 8]:
            bvec = np.zeros(galsim.ShapeletSize(order))
            bvec[0] = 1.  # N,m = 0,0
            k = 0
            for n in range(1, order + 1):
                k += n + 1
                if n % 2 == 0:  # even n
                    bvec[k] = 0.23 / (n * n)  # N,m = n,0  or p,q = n/2,n/2
                    if n >= 2:
                        bvec[k - 2] = 0.14 / n  # N,m = n,2  real part
                        bvec[k - 1] = -0.08 / n  # N,m = n,2  imag part
                else:  # odd n
                    if n >= 1:
                        bvec[k - 1] = -0.08 / n**3.2  # N,m = n,1  real part
                        bvec[k] = 0.05 / n**2.1  # N,m = n,1  imag part
                    if n >= 3:
                        bvec[k - 3] = 0.31 / n**4.2  # N,m = n,3  real part
                        bvec[k - 2] = -0.18 / n**3.9  # N,m = n,3  imag part
            print('shapelet vector = ', bvec)
            shapelet = galsim.Shapelet(sigma=sigma, order=order, bvec=bvec)

            check_basic(shapelet, "Shapelet", approx_maxsb=True)

            # Test normalization  (This is normally part of do_shoot.  When we eventually
            # implement photon shooting, we should go back to the normal do_shoot call,
            # and remove this section.)
            shapelet = shapelet.withFlux(test_flux)
            shapelet.drawImage(im)
            flux = im.array.sum()
            print('im.sum = ', flux, '  cf. ', test_flux)
            np.testing.assert_almost_equal(
                flux / test_flux,
                1.,
                4,
                err_msg=
                "Flux normalization for Shapelet disagrees with expected result"
            )
            np.testing.assert_allclose(
                im.array.max(),
                shapelet.maxSB() * im.scale**2,
                rtol=0.1,
                err_msg="Shapelet maxSB did not match maximum pixel")

            # Test centroid
            # Note: this only works if the image has odd sizes.  If they are even, then
            # setCenter doesn't actually set the center to the true center of the image
            # (since it falls between pixels).
            im.setCenter(0, 0)
            x, y = np.meshgrid(
                np.arange(im.array.shape[0]).astype(float) + im.getXMin(),
                np.arange(im.array.shape[1]).astype(float) + im.getYMin())
            x *= scale
            y *= scale
            flux = im.array.sum()
            mx = (x * im.array).sum() / flux
            my = (y * im.array).sum() / flux
            conv = galsim.Convolve([shapelet, galsim.Pixel(scale)])
            print('centroid = ', mx, my, ' cf. ', conv.centroid())
            np.testing.assert_almost_equal(
                mx,
                shapelet.centroid().x,
                3,
                err_msg=
                "Measured centroid (x) for Shapelet disagrees with expected result"
            )
            np.testing.assert_almost_equal(
                my,
                shapelet.centroid().y,
                3,
                err_msg=
                "Measured centroid (y) for Shapelet disagrees with expected result"
            )
Ejemplo n.º 18
0
def compare_dft_vs_photon_object(gsobject,
                                 psf_object=None,
                                 rng=None,
                                 pixel_scale=1.,
                                 size=512,
                                 wmult=4.,
                                 abs_tol_ellip=1.e-5,
                                 abs_tol_size=1.e-5,
                                 n_trials_per_iter=32,
                                 n_photons_per_trial=1e7,
                                 moments=True,
                                 hsm=False):
    """Take an input object (with optional PSF) and render it in two ways comparing results at high
    precision.

    Using both photon shooting (via drawShoot()) and Discrete Fourier Transform (via draw()) to
    render images, we compare the numerical values of adaptive moments estimates of size and
    ellipticity to check consistency.

    This function takes actual GSObjects as its input, but because these are not yet picklable this
    means that the internals cannot be parallelized using the Python multiprocessing module.  For
    a parallelized function, that instead uses a config dictionary to specify the test objects, see
    the function compare_dft_vs_photon_config() in this module.

    We generate successive sets of `n_trials_per_iter` photon-shot images, using 
    `n_photons_per_trial` photons in each image, until the standard error on the mean absolute size
    and ellipticity drop below `abs_tol_size` and `abs_tol_ellip`.  We then output a
    ComparisonShapeData object which stores the results.

    Note that `n_photons_per_trial` should be large (>~ 1e6) to ensure that any biases detected
    between the photon shooting and DFT-drawn images are due to numerical differences rather than
    biases on adaptive moments due to noise itself, a generic feature in this work.  This can be
    verified with a convergence test.

    @param gsobject         The GSObject for which this test is to be performed (prior
                            to PSF convolution if a PSF is also supplied via `psf_object`).
                            Note that this function will automatically handle integration over
                            a Pixel of width `pixel_scale`, so a Pixel should not be included in
                            the supplied `gsobject` (unless you really mean to include it, which
                            will be very rare in normal usage).
    @param psf_object       Optional additional PSF for tests of convolved objects, also a
                            GSObject.  Note that this function will automatically handle
                            integration over a Pixel of width `pixel_scale`, so this should not
                            be included in the supplied `psf_object`.  [default: None]
    @param rng              A BaseDeviate or derived deviate class instance to provide
                            the pseudo random numbers for the photon shooting.  [default: None]
    @param pixel_scale      The pixel scale to use in the test images. [default: 1]
    @param size             The size of the images in the rendering tests - all test images
                            are currently square. [default: 512]
    @param wmult            The `wmult` parameter used in .draw() (see the GSObject .draw()
                            method docs via `help(galsim.GSObject.draw)` for more details).
                            [default: 4]
    @param abs_tol_ellip    The test will keep iterating, adding ever greater numbers of
                            trials, until estimates of the 1-sigma standard error on mean 
                            ellipticity moments from photon-shot images are smaller than this
                            param value. [default: 1.e-5]
    @param abs_tol_size     The test will keep iterating, adding ever greater numbers of
                            trials, until estimates of the 1-sigma standard error on mean 
                            size moments from photon-shot images are smaller than this param
                            value. [default: 1.e-5]
    @param n_trials_per_iter  Number of trial images used to estimate (or successively
                            re-estimate) the standard error on the delta quantities above for
                            each iteration of the tests. [default: 32]
    @param n_photons_per_trial  Number of photons shot in drawShoot() for each trial.  This should
                            be large enough that any noise bias (a.k.a. noise rectification
                            bias) on moments estimates is small. [default: 1e7]
    @param moments          Set True to compare rendered images using AdaptiveMoments
                            estimates of simple observed estimates. [default: True]
    @param hsm              Should the rendered images be compared using HSM shear estimates?
                            (i.e. including a PSF correction for shears) [not implemented]
    """
    import sys
    import logging
    import time

    # Some sanity checks on inputs
    if hsm is True:
        if psf_object is None:
            raise ValueError(
                'An input psf_object is required for HSM shear estimate testing.'
            )
        else:
            # Raise an apologetic exception about the HSM not yet being implemented!
            raise NotImplementedError('Sorry, HSM tests not yet implemented!')

    if rng is None:
        rng = galsim.BaseDeviate()

    # Then define some convenience functions for handling lists and multiple trial operations
    def _mean(array_like):
        return np.mean(np.asarray(array_like))

    def _stderr(array_like):
        return np.std(np.asarray(array_like)) / np.sqrt(len(array_like))

    def _shoot_trials_single(gsobject, ntrials, dx, imsize, rng, n_photons):
        """Convenience function to run `ntrials` and collect the results, uses only a single core.

        Uses a Python for loop but this is very unlikely to be a rate determining factor provided
        n_photons is suitably large (>1e6).
        """
        g1obslist = []
        g2obslist = []
        sigmalist = []
        im = galsim.ImageF(imsize, imsize)
        for i in xrange(ntrials):
            gsobject.drawShoot(im, dx=dx, n_photons=n_photons, rng=rng)
            res = im.FindAdaptiveMom()
            g1obslist.append(res.observed_shape.g1)
            g2obslist.append(res.observed_shape.g2)
            sigmalist.append(res.moments_sigma)
            logging.debug('Completed ' + str(i + 1) + '/' + str(ntrials) +
                          ' trials in this iteration')
            #im.write('check_shoot_trial'+str(i + 1)) CHECK IMAGE
        return g1obslist, g2obslist, sigmalist

    # OK, that's the end of the helper functions-within-helper functions, back to the main unit

    # Start the timer
    t1 = time.time()

    # If a PSF is supplied, do the convolution, otherwise just use the gal_object
    if psf_object is None:
        logging.info(
            'No psf_object supplied, running tests using input gsobject only')
        test_object = gsobject
    else:
        logging.info(
            'Generating test_object by convolving gsobject with input psf_object'
        )
        test_object = galsim.Convolve([gsobject, psf_object])

    # Draw the FFT image, only needs to be done once
    # For the FFT drawn image we need to include the galsim.Pixel, for the photon shooting we don't!
    test_object_pixelized = galsim.Convolve(
        [test_object, galsim.Pixel(pixel_scale)])
    im_draw = galsim.ImageF(size, size)
    test_object_pixelized.draw(im_draw, dx=pixel_scale, wmult=wmult)
    res_draw = im_draw.FindAdaptiveMom()
    sigma_draw = res_draw.moments_sigma
    g1obs_draw = res_draw.observed_shape.g1
    g2obs_draw = res_draw.observed_shape.g2

    # Setup storage lists for the trial shooting results
    sigma_shoot_list = []
    g1obs_shoot_list = []
    g2obs_shoot_list = []
    sigmaerr = 666.  # Slightly kludgy but will not accidentally fail the first `while` condition
    g1obserr = 666.
    g2obserr = 666.

    # Initialize iteration counter
    itercount = 0

    # Then begin while loop, farming out sets of n_trials_per_iter trials until we get the
    # statistical accuracy we require
    while (g1obserr > abs_tol_ellip) or (g2obserr > abs_tol_ellip) or (
            sigmaerr > abs_tol_size):

        # Run the trials using helper function
        g1obs_list_tmp, g2obs_list_tmp, sigma_list_tmp = _shoot_trials_single(
            test_object, n_trials_per_iter, pixel_scale, size, rng,
            n_photons_per_trial)

        # Collect results and calculate new standard error
        g1obs_shoot_list.extend(g1obs_list_tmp)
        g2obs_shoot_list.extend(g2obs_list_tmp)
        sigma_shoot_list.extend(sigma_list_tmp)
        g1obserr = _stderr(g1obs_shoot_list)
        g2obserr = _stderr(g2obs_shoot_list)
        sigmaerr = _stderr(sigma_shoot_list)
        itercount += 1
        sys.stdout.write(
            "."
        )  # This doesn't add a carriage return at the end of the line, nice!
        logging.debug('Completed ' + str(itercount) + ' iterations')
        logging.debug('(g1obserr, g2obserr, sigmaerr) = ' + str(g1obserr) +
                      ', ' + str(g2obserr) + ', ' + str(sigmaerr))

    sys.stdout.write("\n")

    # Take the runtime and collate results into a ComparisonShapeData
    runtime = time.time() - t1
    results = ComparisonShapeData(g1obs_draw,
                                  g2obs_draw,
                                  sigma_draw,
                                  _mean(g1obs_shoot_list),
                                  _mean(g2obs_shoot_list),
                                  _mean(sigma_shoot_list),
                                  g1obserr,
                                  g2obserr,
                                  sigmaerr,
                                  size,
                                  pixel_scale,
                                  wmult,
                                  itercount,
                                  n_trials_per_iter,
                                  n_photons_per_trial,
                                  runtime,
                                  gsobject=gsobject,
                                  psf_object=psf_object)

    logging.info('\n' + str(results))
    return results
Ejemplo n.º 19
0
def test_shearest_shape():
    """Test that shear estimation is insensitive to shape of input images."""
    # this test can help reveal bugs having to do with x / y indexing issues
    import time
    t1 = time.time()
    # just do test for one particular gaussian
    g1 = shear_values[1]
    g2 = shear_values[2]
    e1_psf = 0.05
    e2_psf = -0.04
    total_shear = np.sqrt(g1**2 + g2**2)
    conversion_factor = np.tanh(2.0 * math.atanh(total_shear)) / total_shear
    distortion_1 = g1 * conversion_factor
    distortion_2 = g2 * conversion_factor
    gal = galsim.Exponential(flux=1.0, half_light_radius=1.)
    gal.applyShear(g1=g1, g2=g2)
    psf = galsim.Kolmogorov(flux=1.0, fwhm=0.7)
    psf.applyShear(e1=e1_psf, e2=e2_psf)
    final = galsim.Convolve([gal, psf])

    imsize = [128, 256]
    for method_index in range(len(correction_methods)):
        print correction_methods[method_index]

        save_e1 = -100.
        save_e2 = -100.
        for gal_x_imsize in imsize:
            for gal_y_imsize in imsize:
                for psf_x_imsize in imsize:
                    for psf_y_imsize in imsize:
                        print gal_x_imsize, gal_y_imsize, psf_x_imsize, psf_y_imsize
                        final_image = galsim.ImageF(gal_x_imsize, gal_y_imsize)
                        epsf_image = galsim.ImageF(psf_x_imsize, psf_y_imsize)

                        final.draw(image=final_image, dx=pixel_scale)
                        psf.draw(image=epsf_image, dx=pixel_scale)
                        result = galsim.hsm.EstimateShear(
                            final_image,
                            epsf_image,
                            shear_est=correction_methods[method_index])
                        e1 = result.corrected_e1
                        e2 = result.corrected_e2
                        # make sure answers don't change as we vary image size

                        tot_e = np.sqrt(save_e1**2 + save_e2**2)
                        if tot_e < 99.:
                            print "Testing!"
                            np.testing.assert_almost_equal(
                                e1,
                                save_e1,
                                err_msg="- incorrect e1",
                                decimal=decimal_shape)
                            np.testing.assert_almost_equal(
                                e2,
                                save_e2,
                                err_msg="- incorrect e2",
                                decimal=decimal_shape)
                        print save_e1, save_e2, e1, e2
                        save_e1 = e1
                        save_e2 = e2

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Ejemplo n.º 20
0
def test_deltaFunction():
    """Test the generation of a Delta function profile
    """
    # Check construction with no arguments gives expected result
    delta = galsim.DeltaFunction()
    np.testing.assert_almost_equal(delta.flux, 1.0)
    check_basic(delta, "DeltaFunction")
    do_pickle(delta)

    # Check with default_params
    delta = galsim.DeltaFunction(flux=1, gsparams=default_params)
    np.testing.assert_almost_equal(delta.flux, 1.0)

    test_flux = 17.9
    delta = galsim.DeltaFunction(flux=test_flux)
    np.testing.assert_almost_equal(delta.flux, test_flux)
    check_basic(delta, "DeltaFunction")
    do_pickle(delta)

    gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8)
    delta2 = galsim.DeltaFunction(flux=test_flux, gsparams=gsp)
    assert delta2 != delta
    assert delta2 == delta.withGSParams(gsp)

    # Test operations with no-ops on DeltaFunction
    delta_shr = delta.shear(g1=0.3, g2=0.1)
    np.testing.assert_almost_equal(delta_shr.flux, test_flux)

    delta_dil = delta.dilate(2.0)
    np.testing.assert_almost_equal(delta_dil.flux, test_flux)

    delta_rot = delta.rotate(45 * galsim.radians)
    np.testing.assert_almost_equal(delta_rot.flux, test_flux)

    delta_tfm = delta.transform(dudx=1.25, dudy=0., dvdx=0., dvdy=0.8)
    np.testing.assert_almost_equal(delta_tfm.flux, test_flux)

    delta_shift = delta.shift(1., 2.)
    np.testing.assert_almost_equal(delta_shift.flux, test_flux)

    # These aren't no ops, since they do in fact alter the flux.
    delta_exp = delta.expand(2.0)
    np.testing.assert_almost_equal(delta_exp.flux, test_flux * 4)

    delta_mag = delta.magnify(2.0)
    np.testing.assert_almost_equal(delta_mag.flux, test_flux * 2)

    delta_tfm = delta.transform(dudx=1.4, dudy=0.2, dvdx=0.4, dvdy=1.2)
    np.testing.assert_almost_equal(delta_tfm.flux,
                                   test_flux * (1.4 * 1.2 - 0.2 * 0.4))

    # Test simple translation of DeltaFunction
    delta2 = delta.shift(1., 2.)
    offcen = galsim.PositionD(1, 2)
    np.testing.assert_equal(delta2.centroid, offcen)
    assert delta2.xValue(offcen) > 1.e10
    np.testing.assert_almost_equal(delta2.xValue(galsim.PositionD(0, 0)), 0)

    # Test photon shooting.
    gauss = galsim.Gaussian(sigma=1.0)
    delta_conv = galsim.Convolve(gauss, delta)
    myImg = galsim.ImageF()
    do_shoot(delta_conv, myImg, "Delta Function")

    # Test kvalues
    do_kvalue(delta_conv, myImg, "Delta Function")
Ejemplo n.º 21
0
def main(argv):
    """
    Make a fits image cube using parameters from an input catalog
      - The number of images in the cube matches the number of rows in the catalog.
      - Each image size is computed automatically by GalSim based on the Nyquist size.
      - Only galaxies.  No stars.
      - PSF is Moffat
      - Each galaxy is bulge plus disk: deVaucouleurs + Exponential.
      - The catalog's columns are:
         0 PSF beta (Moffat exponent)
         1 PSF FWHM
         2 PSF e1
         3 PSF e2
         4 PSF trunc
         5 Disc half-light-radius
         6 Disc e1
         7 Disc e2
         8 Bulge half-light-radius
         9 Bulge e1
        10 Bulge e2
        11 Galaxy dx (the two components have same center)
        12 Galaxy dy
      - Applied shear is the same for each galaxy
      - 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("demo4")

    # Define some parameters we'll use below and make directories if needed.
    cat_file_name = os.path.join('input', 'galsim_default_input.asc')
    if not os.path.isdir('output'):
        os.mkdir('output')
    multi_file_name = os.path.join('output', 'multi.fits')

    random_seed = 8241573
    sky_level = 1.e6  # ADU / arcsec^2
    pixel_scale = 1.0  # arcsec / pixel  (size units in input catalog are pixels)
    gal_flux = 1.e6  # arbitrary choice, makes nice (not too) noisy images
    gal_g1 = -0.009  #
    gal_g2 = 0.011  #
    xsize = 64  # pixels
    ysize = 64  # pixels

    logger.info('Starting demo script 4 using:')
    logger.info('    - parameters taken from catalog %r', cat_file_name)
    logger.info('    - Moffat PSF (parameters from catalog)')
    logger.info('    - pixel scale = %.2f', pixel_scale)
    logger.info('    - Bulge + Disc galaxies (parameters from catalog)')
    logger.info('    - Applied gravitational shear = (%.3f,%.3f)', gal_g1,
                gal_g2)
    logger.info('    - Poisson noise (sky level = %.1e).', sky_level)

    # Read in the input catalog
    cat = galsim.Catalog(cat_file_name)

    # save a list of the galaxy images in the "images" list variable:
    images = []
    for k in range(cat.nobjects):
        # Initialize the (pseudo-)random number generator that we will be using below.
        # Use a different random seed for each object to get different noise realizations.
        # Using sequential random seeds here is safer than it sounds.  We use Mersenne Twister
        # random number generators that are designed to be used with this kind of seeding.
        # However, to be extra safe, we actually initialize one random number generator with this
        # seed, generate and throw away two random values with that, and then use the next value
        # to seed a completely different Mersenne Twister RNG.  The result is that successive
        # RNGs created this way produce very independent random number streams.
        rng = galsim.BaseDeviate(random_seed + k + 1)

        # Take the Moffat beta from the first column (called 0) of the input catalog:
        # Note: cat.get(k,col) returns a string.  To get the value as a float, use either
        #       cat.getFloat(k,col) or float(cat.get(k,col))
        beta = cat.getFloat(k, 0)
        # A Moffat's size may be either scale_radius, fwhm, or half_light_radius.
        # Here we use fwhm, taking from the catalog as well.
        fwhm = cat.getFloat(k, 1)
        # A Moffat profile may be truncated if desired
        # The units for this are expected to be arcsec (or specifically -- whatever units
        # you are using for all the size values as defined by the pixel_scale).
        trunc = cat.getFloat(k, 4)
        # Note: You may omit the flux, since the default is flux=1.
        psf = galsim.Moffat(beta=beta, fwhm=fwhm, trunc=trunc)

        # Take the (e1, e2) shape parameters from the catalog as well.
        psf = psf.shear(e1=cat.getFloat(k, 2), e2=cat.getFloat(k, 3))

        # Galaxy is a bulge + disk with parameters taken from the catalog:
        disk = galsim.Exponential(flux=0.6,
                                  half_light_radius=cat.getFloat(k, 5))
        disk = disk.shear(e1=cat.getFloat(k, 6), e2=cat.getFloat(k, 7))

        bulge = galsim.DeVaucouleurs(flux=0.4,
                                     half_light_radius=cat.getFloat(k, 8))
        bulge = bulge.shear(e1=cat.getFloat(k, 9), e2=cat.getFloat(k, 10))

        # The flux of an Add object is the sum of the component fluxes.
        # Note that in demo3.py, a similar addition was performed by the binary operator "+".
        gal = galsim.Add([disk, bulge])
        # This flux may be overridden by withFlux.  The relative fluxes of the components
        # remains the same, but the total flux is set to gal_flux.
        gal = gal.withFlux(gal_flux)
        gal = gal.shear(g1=gal_g1, g2=gal_g2)

        # The center of the object is normally placed at the center of the postage stamp image.
        # You can change that with shift:
        gal = gal.shift(dx=cat.getFloat(k, 11), dy=cat.getFloat(k, 12))

        final = galsim.Convolve([psf, gal])

        # Draw the profile
        image = galsim.ImageF(xsize, ysize)
        final.drawImage(image, scale=pixel_scale)

        # Add Poisson noise to the image:
        image.addNoise(galsim.PoissonNoise(rng, sky_level * pixel_scale**2))

        logger.info('Drew image for object at row %d in the input catalog' % k)

        # Add the image to our list of images
        images.append(image)

    # Now write the images to a multi-extension fits file.  Each image will be in its own HDU.
    galsim.fits.writeMulti(images, multi_file_name)
    logger.info('Images written to multi-extension fits file %r',
                multi_file_name)
Ejemplo n.º 22
0
def runSkyModel(config):
    # image properties
    data_path = config.get('pipeline', 'data_path')
    pixel_scale = config.getfloat('skymodel', 'pixel_scale') * galsim.arcsec
    fov = config.getfloat('skymodel', 'field_of_view') * galsim.arcmin
    image_size = int((fov / galsim.arcmin) / (pixel_scale / galsim.arcmin))

    ra_field = config.get('field', 'field_ra')
    ra_field_gs = galsim.HMS_Angle(ra_field)
    dec_field = config.get('field', 'field_dec')
    dec_field_gs = galsim.DMS_Angle(dec_field)

    cat_file_name = config.get('field', 'catalogue')
    print('Loading catalogue from {0} ...'.format(cat_file_name))
    cat = fits.getdata(cat_file_name)
    nobj = len(cat)

    cat_wcs = ast_wcs.WCS(naxis=2)
    cat_wcs.wcs.crpix = [image_size / 2, image_size / 2]
    cat_wcs.wcs.cdelt = [
        pixel_scale / galsim.degrees, pixel_scale / galsim.degrees
    ]
    cat_wcs.wcs.crval = [0.e0, 0.e0]
    cat_wcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']

    gal_ra = cat['latitude']
    gal_dec = cat['longitude']
    gal_e1 = cat['e1']
    gal_e2 = cat['e2']
    gal_flux = cat['I1400']  #mjy
    gal_r0 = cat['size'] / 2.
    g1 = 0
    g2 = 0
    print('...done.')

    full_image = galsim.ImageF(image_size, image_size, scale=pixel_scale)
    im_center = full_image.bounds.trueCenter()
    sky_center = galsim.CelestialCoord(ra=ra_field_gs, dec=dec_field_gs)

    # - on dx's since the ra axis is flipped.
    dudx = -pixel_scale / galsim.arcsec
    dudy = 0.
    dvdx = 0.
    dvdy = pixel_scale / galsim.arcsec
    image_center = full_image.trueCenter()
    affine = galsim.AffineTransform(dudx,
                                    dudy,
                                    dvdx,
                                    dvdy,
                                    origin=full_image.trueCenter())
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    full_image.wcs = wcs

    tstart = time.time()

    nobj = 200

    for i in range(nobj):

        sys.stdout.write('\rAdding source {0} of {1} to skymodel...'.format(
            i + 1, nobj))

        gal = galsim.Exponential(scale_radius=gal_r0[i], flux=gal_flux[i])

        ellipticity = galsim.Shear(e1=gal_e1[i], e2=gal_e2[i])
        shear = galsim.Shear(g1=g1[i], g2=g2[i])
        total_shear = ellipticity + shear

        gal = gal.shear(total_shear)

        x, y = cat_wcs.wcs_world2pix(gal_ra[i], gal_dec[i], 0)
        x = float(x)
        y = float(y)

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

        stamp = gal.drawImage(scale=pixel_scale / galsim.arcsec, offset=offset)
        stamp.setCenter(ix, iy)

        bounds = stamp.bounds & full_image.bounds
        full_image[bounds] += stamp[bounds]
        sys.stdout.flush()

    tend = time.time()
    print('\n...done in {0} seconds.'.format(tend - tstart))
    all_gals_fname = data_path + config.get('field', 'fitsname')
    print('Writing image data to {0} ...'.format(all_gals_fname))
    image_data = full_image.array
    write4dImage(all_gals_fname,
                 image_data,
                 pixel_scale / galsim.degrees,
                 obs_ra=ra_field_gs / galsim.degrees,
                 obs_dec=dec_field_gs / galsim.degrees,
                 obs_freq=config.getfloat('observation', 'lowest_frequency'))
    print('...done.')

    print('runSkyModel complete.')
Ejemplo n.º 23
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) )
    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=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 / math.cos(
            dec.rad()) * 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.'
    )
Ejemplo n.º 24
0
def main(argv):
    # Where to find and output data.
    path, filename = os.path.split(__file__)
    datapath = os.path.abspath(os.path.join(path, "data/"))
    outpath = os.path.abspath(os.path.join(path, "output/"))

    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo13")

    # Initialize (pseudo-)random number generator.
    random_seed = 123456
    rng = galsim.BaseDeviate(random_seed)

    # Generate a Poisson noise model.
    poisson_noise = galsim.PoissonNoise(rng)
    logger.info('Poisson noise model created.')

    # Read in the WFIRST filters, setting an AB zeropoint appropriate for this telescope given its
    # diameter and (since we didn't use any keyword arguments to modify this) using the typical
    # exposure time for WFIRST images.
    filters = wfirst.getBandpasses(AB_zeropoint=True)
    logger.debug('Read in WFIRST imaging filters.')

    logger.info('Reading from a parametric COSMOS catalog.')
    # Read in a galaxy catalog - just a random subsample of 100 galaxies for F814W<23.5 from COSMOS.
    cat_file_name = 'real_galaxy_catalog_example_fits.fits'
    dir = 'data'
    # Use the routine that can take COSMOS real or parametric galaxy information, and tell it we
    # want parametric galaxies that represent an I<23.5 sample.
    cat = galsim.COSMOSCatalog(cat_file_name, dir=dir, use_real=False)
    logger.info('Read in %d galaxies from catalog' % cat.nobjects)
    # Just use a few galaxies, to save time.  Note that we are going to put 4000 galaxy images into
    # our big image, so if we have n_use=10, each galaxy will appear 400 times.  Users who want a
    # more interesting image with greater variation in the galaxy population can change `n_use` to
    # something larger (but it should be <=100, the number of galaxies in this small example
    # catalog).  With 4000 galaxies in a 4k x 4k image with the WFIRST pixel scale, the effective
    # galaxy number density is 74/arcmin^2.  This is not the number density that is expected for a
    # sample that is so bright (I<23.5) but it makes the image more visually interesting.  One could
    # think of it as what you'd get if you added up several images at once, making the images for a
    # sample that is much deeper have the same S/N as that for an I<23.5 sample in a single image.
    n_use = 10
    n_tot = 4000

    # Here we carry out the initial steps that are necessary to get a fully chromatic PSF.  We use
    # the getPSF() routine in the WFIRST module, which knows all about the telescope parameters
    # (diameter, bandpasses, obscuration, etc.).  Note that we arbitrarily choose a single SCA
    # (Sensor Chip Assembly) rather than all of them, for faster calculations, and use a simple
    # representation of the struts for faster calculations.  To do a more exact calculation of the
    # chromaticity and pupil plane configuration, remove the `approximate_struts` and the `n_waves`
    # keyword from the call to getPSF():
    use_SCA = 7  # This could be any number from 1...18
    logger.info('Doing expensive pre-computation of PSF.')
    t1 = time.time()
    logger.setLevel(logging.DEBUG)
    PSFs = wfirst.getPSF(SCAs=use_SCA,
                         approximate_struts=True,
                         n_waves=10,
                         logger=logger)
    logger.setLevel(logging.INFO)
    PSF = PSFs[use_SCA]
    t2 = time.time()
    logger.info('Done PSF precomputation in %.1f seconds!' % (t2 - t1))

    # Define the size of the postage stamp that we use for each individual galaxy within the larger
    # image, and for the PSF images.
    stamp_size = 256

    # We choose a particular (RA, dec) location on the sky for our observation.
    ra_targ = 90. * galsim.degrees
    dec_targ = -10. * galsim.degrees
    targ_pos = galsim.CelestialCoord(ra=ra_targ, dec=dec_targ)
    # Get the WCS for an observation at this position.  We are not supplying a date, so the routine
    # will assume it's the vernal equinox.  We are also not supplying a position angle for the
    # observatory, which means that it will just find the optimal one (the one that has the solar
    # panels pointed most directly towards the Sun given this targ_pos and date).  The output of
    # this routine is a dict of WCS objects, one for each SCA.  We then take the WCS for the SCA
    # that we are using.
    wcs_list = wfirst.getWCS(world_pos=targ_pos, SCAs=use_SCA)
    wcs = wcs_list[use_SCA]
    # We need to find the center position for this SCA.  We'll tell it to give us a CelestialCoord
    # corresponding to (X, Y) = (wfirst.n_pix/2, wfirst.n_pix/2).
    SCA_cent_pos = wcs.toWorld(
        galsim.PositionD(wfirst.n_pix / 2, wfirst.n_pix / 2))

    # We randomly distribute points in (X, Y) on the CCD.
    # If we had a real galaxy catalog with positions in terms of RA, dec we could use wcs.toImage()
    # to find where those objects should be in terms of (X, Y).
    pos_rng = galsim.UniformDeviate(random_seed)
    # Make a list of (X, Y, F814W magnitude, n_rot, flip) values.
    # (X, Y) give the position of the galaxy centroid (or the center of the postage stamp into which
    # we draw the galaxy) in the big image.
    # F814W magnitudes are randomly drawn from the catalog, and are used to create a more realistic
    # flux distribution for the galaxies instead of just having the 10 flux values for the galaxies
    # we have chosen to draw.
    # n_rot says how many 90 degree rotations to include for a given realization of each galaxy, so
    # it doesn't appear completely identical each time we put it in the image.
    # flip is a random number that will determine whether we include an x-y flip for this appearance
    # of the galaxy or not.
    x_stamp = []
    y_stamp = []
    mag_stamp = []
    n_rot_stamp = []
    flip_stamp = []
    for i_gal in xrange(n_tot):
        x_stamp.append(pos_rng() * wfirst.n_pix)
        y_stamp.append(pos_rng() * wfirst.n_pix)
        # Note that we could use wcs.toWorld() to get the (RA, dec) for these (x, y) positions.  Or,
        # if we had started with (RA, dec) positions, we could have used wcs.toImage() to get the
        # CCD coordinates for those positions.
        mag_stamp.append(cat.param_cat.mag_auto[pos_rng() * cat.nobjects])
        n_rot_stamp.append(int(4 * pos_rng()))
        flip_stamp.append(pos_rng())

    # Make the 2-component parametric GSObjects for each object, including chromaticity (roughly
    # appropriate SEDs per galaxy component, at the appropriate galaxy redshift).  Note that since
    # the PSF is position-independent within the SCA, we can simply do the convolution with that PSF
    # now instead of using a different one for each position.  We also have to include the correct
    # flux scaling: The catalog returns objects that would be observed by 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.
    logger.info(
        'Processing the objects in the catalog to get GSObject representations'
    )
    # Choose a random set of unique indices in the catalog (will be the same each time script is
    # run, due to use of the same random seed):
    rand_indices = []
    while len(rand_indices) < n_use:
        tmp_ind = int(pos_rng() * cat.nobjects)
        if tmp_ind not in rand_indices:
            rand_indices.append(tmp_ind)
    obj_list = cat.makeGalaxy(rand_indices,
                              chromatic=True,
                              gal_type='parametric',
                              deep=True)
    gal_list = []
    hst_eff_area = 2.4**2 * (1. - 0.33**2)
    wfirst_eff_area = galsim.wfirst.diameter**2 * (
        1. - galsim.wfirst.obscuration**2)
    flux_scaling = (wfirst_eff_area / hst_eff_area) * wfirst.exptime
    mag_list = []
    for ind in range(len(obj_list)):
        # First, let's check what magnitude this object has in F814W.  We want to do this because
        # (to inject some variety into our images) we are going to rescale the fluxes in all bands
        # for different instances of this galaxy in the final image in order to get a reasonable S/N
        # distribution.  So we need to save the original magnitude in F814W, to compare with a
        # randomly drawn one from the catalog.  This is not something that most users would need to
        # do.
        mag_list.append(
            cat.param_cat.mag_auto[cat.orig_index[rand_indices[ind]]])

        # Convolve the chromatic galaxy and the chromatic PSF, and rescale flux.
        final = galsim.Convolve(flux_scaling * obj_list[ind], PSF)
        logger.debug('Pre-processing for galaxy %d completed.' % ind)
        gal_list.append(final)

    # Calculate the sky level for each filter, and draw the PSF and the galaxies through the
    # filters.
    for filter_name, filter_ in filters.iteritems():
        logger.info('Beginning work for {0}.'.format(filter_name))

        # Drawing PSF.  Note that the PSF object intrinsically has a flat SED, so if we convolve it
        # with a galaxy, it will properly take on the SED of the galaxy.  For the sake of this demo,
        # we will simply convolve with a 'star' that has a flat SED and unit flux in this band, so
        # that the PSF image will be normalized to unit flux. This does mean that the PSF image
        # being drawn here is not quite the right PSF for the galaxy.  Indeed, the PSF for the
        # galaxy effectively varies within it, since it differs for the bulge and the disk.  To make
        # a real image, one would have to choose SEDs for stars and convolve with a star that has a
        # reasonable SED, but we just draw with a flat SED for this demo.
        out_filename = os.path.join(outpath,
                                    'demo13_PSF_{0}.fits'.format(filter_name))
        # Approximate a point source.
        point = galsim.Gaussian(sigma=1.e-8, flux=1.)
        # Use a flat SED here, but could use something else.  A stellar SED for instance.
        # Or a typical galaxy SED.  Depending on your purpose for drawing the PSF.
        star_sed = galsim.SED(lambda x: 1).withFlux(
            1., filter_)  # Give it unit flux in this filter.
        star = galsim.Convolve(point * star_sed, PSF)
        img_psf = galsim.ImageF(64, 64)
        star.drawImage(bandpass=filter_,
                       image=img_psf,
                       scale=wfirst.pixel_scale)
        img_psf.write(out_filename)
        logger.debug(
            'Created PSF with flat SED for {0}-band'.format(filter_name))

        # Set up the full image that will contain all the individual galaxy images, with information
        # about WCS:
        final_image = galsim.ImageF(wfirst.n_pix, wfirst.n_pix, wcs=wcs)

        # Draw the galaxies into the image.
        for i_gal in xrange(n_use):
            logger.info(
                'Drawing image for the object at row %d in the input catalog' %
                i_gal)

            # We want to only draw the galaxy once (for speed), not over and over with different
            # sub-pixel offsets.  For this reason we ignore the sub-pixel offset entirely.  Note
            # that we are setting the postage stamp to have the average WFIRST pixel scale.  This is
            # simply an approximation for the purpose of speed; really, one should draw directly
            # into final_image, which has the appropriate WCS for WFIRST.  In that case, the image
            # of the galaxy might look different in different parts of the detector due to the WCS
            # (including distortion), and we would have to re-draw each time.  To keep the demo
            # relatively quick, we are just using the approximate average pixel scale and drawing
            # once.
            stamp = galsim.Image(stamp_size,
                                 stamp_size,
                                 scale=wfirst.pixel_scale)
            gal_list[i_gal].drawImage(filter_, image=stamp)

            # Have to find where to place it:
            for i_gal_use in range(i_gal * n_tot / n_use,
                                   (i_gal + 1) * n_tot / n_use):
                # Account for the fractional part of the position:
                ix = int(math.floor(x_stamp[i_gal_use] + 0.5))
                iy = int(math.floor(y_stamp[i_gal_use] + 0.5))
                # We don't actually use this offset.
                offset = galsim.PositionD(x_stamp[i_gal] - ix,
                                          y_stamp[i_gal] - iy)

                # Create a nominal bound for the postage stamp given the integer part of the
                # position.
                stamp_bounds = galsim.BoundsI(ix - 0.5 * stamp_size,
                                              ix + 0.5 * stamp_size - 1,
                                              iy - 0.5 * stamp_size,
                                              iy + 0.5 * stamp_size - 1)

                # Find the overlapping bounds between the large image and the individual postage
                # stamp.
                bounds = stamp_bounds & final_image.bounds

                # Just to inject a bit of variety into the image, so it isn't *quite* as obvious
                # that we've repeated the same 10 objects over and over, we randomly rotate the
                # postage stamp by some factor of 90 degrees and possibly include a random flip.
                if flip_stamp[i_gal_use] > 0.5:
                    new_arr = numpy.ascontiguousarray(
                        numpy.rot90(stamp.array, n_rot_stamp[i_gal_use]))
                else:
                    new_arr = numpy.ascontiguousarray(
                        numpy.fliplr(
                            numpy.rot90(stamp.array, n_rot_stamp[i_gal_use])))
                stamp_rot = galsim.Image(new_arr, scale=stamp.scale)
                stamp_rot.setOrigin(
                    galsim.PositionI(stamp_bounds.xmin, stamp_bounds.ymin))

                # Rescale the flux to match that of a randomly chosen galaxy in the galaxy, but
                # keeping the same SED as for this particular galaxy.  This gives a bit more
                # variety in the flux values and SNR of the galaxies in the image without having
                # to render images of many more objects.
                flux_scaling = 10**(-0.4 *
                                    (mag_stamp[i_gal_use] - mag_list[i_gal]))

                # Copy the image into the right place in the big image.
                final_image[bounds] += flux_scaling * stamp_rot[bounds]

        # Now we're done with the per-galaxy drawing for this image.  The rest will be done for the
        # entire image at once.
        logger.info(
            'Postage stamps of all galaxies drawn on a single big image for this filter.'
        )
        logger.info('Adding the sky level, noise and detector non-idealities.')

        # First we get the amount of zodaical light for a position corresponding to the center of
        # this SCA.  The results are provided in units of e-/arcsec^2, using the default WFIRST
        # exposure time since we did not explicitly specify one.  Then we multiply this by a factor
        # >1 to account for the amount of stray light that is expected.  If we do not provide a date
        # for the observation, then it will assume that it's the vernal equinox (sun at (0,0) in
        # ecliptic coordinates) in 2025.
        sky_level = wfirst.getSkyLevel(filters[filter_name],
                                       world_pos=SCA_cent_pos)
        sky_level *= (1.0 + wfirst.stray_light_fraction)
        # Make a image of the sky that takes into account the spatially variable pixel scale.  Note
        # that makeSkyImage() takes a bit of time.  If you do not care about the variable pixel
        # scale, you could simply compute an approximate sky level in e-/pix by multiplying
        # sky_level by wfirst.pixel_scale**2, and add that to final_image.
        sky_image = final_image.copy()
        wcs.makeSkyImage(sky_image, sky_level)
        # This image is in units of e-/pix.  Finally we add the expected thermal backgrounds in this
        # band.  These are provided in e-/pix/s, so we have to multiply by the exposure time.
        sky_image += wfirst.thermal_backgrounds[filter_name] * wfirst.exptime
        # Adding sky level to the image.
        final_image += sky_image

        # Now that all sources of signal (from astronomical objects and background) have been added
        # to the image, we can start adding noise and detector effects.  There is a utility,
        # galsim.wfirst.allDetectorEffects(), that can apply ALL implemented noise and detector
        # effects in the proper order.  Here we step through the process and explain these in a bit
        # more detail without using that utility.

        # First, we include the expected Poisson noise:
        final_image.addNoise(poisson_noise)

        # The subsequent steps account for the non-ideality of the detectors.

        # 1) Reciprocity failure:
        # Reciprocity, in the context of photography, is the inverse relationship between the
        # incident flux (I) of a source object and the exposure time (t) required to produce a given
        # response(p) in the detector, i.e., p = I*t. However, in NIR detectors, this relation does
        # not hold always. The pixel response to a high flux is larger than its response to a low
        # flux. This flux-dependent non-linearity is known as 'reciprocity failure', and the
        # approximate amount of reciprocity failure for the WFIRST detectors is known, so we can
        # include this detector effect in our images.

        if diff_mode:
            # Save the image before applying the transformation to see the difference
            save_image = final_image.copy()

        # If we had wanted to, we could have specified a different exposure time than the default
        # one for WFIRST, but otherwise the following routine does not take any arguments.
        wfirst.addReciprocityFailure(final_image)
        logger.debug('Included reciprocity failure in {0}-band image'.format(
            filter_name))

        if diff_mode:
            # Isolate the changes due to reciprocity failure.
            diff = final_image - save_image

            out_filename = os.path.join(
                outpath, 'demo13_RecipFail_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(
                outpath, 'demo13_diff_RecipFail_{0}.fits'.format(filter_name))
            diff.write(out_filename)

        # At this point in the image generation process, an integer number of photons gets
        # detected, hence we have to round the pixel values to integers:
        final_image.quantize()

        # 2) Adding dark current to the image:
        # Even when the detector is unexposed to any radiation, the electron-hole pairs that
        # are generated within the depletion region due to finite temperature are swept by the
        # high electric field at the junction of the photodiode. This small reverse bias
        # leakage current is referred to as 'dark current'. It is specified by the average
        # number of electrons reaching the detectors per unit time and has an associated
        # Poisson noise since it is a random event.
        dark_current = wfirst.dark_current * wfirst.exptime
        dark_noise = galsim.DeviateNoise(
            galsim.PoissonDeviate(rng, dark_current))
        final_image.addNoise(dark_noise)

        # NOTE: Sky level and dark current might appear like a constant background that can be
        # simply subtracted. However, these contribute to the shot noise and matter for the
        # non-linear effects that follow. Hence, these must be included at this stage of the
        # image generation process. We subtract these backgrounds in the end.

        # 3) Applying a quadratic non-linearity:
        # In order to convert the units from electrons to ADU, we must use the gain factor. The gain
        # has a weak dependency on the charge present in each pixel. This dependency is accounted
        # for by changing the pixel values (in electrons) and applying a constant nominal gain
        # later, which is unity in our demo.

        # Save the image before applying the transformation to see the difference:
        if diff_mode:
            save_image = final_image.copy()

        # Apply the WFIRST nonlinearity routine, which knows all about the nonlinearity expected in
        # the WFIRST detectors.
        wfirst.applyNonlinearity(final_image)
        # Note that users who wish to apply some other nonlinearity function (perhaps for other NIR
        # detectors, or for CCDs) can use the more general nonlinearity routine, which uses the
        # following syntax:
        # final_image.applyNonlinearity(NLfunc=NLfunc)
        # with NLfunc being a callable function that specifies how the output image pixel values
        # should relate to the input ones.
        logger.debug(
            'Applied nonlinearity to {0}-band image'.format(filter_name))
        if diff_mode:
            diff = final_image - save_image

            out_filename = os.path.join(
                outpath, 'demo13_NL_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(
                outpath, 'demo13_diff_NL_{0}.fits'.format(filter_name))
            diff.write(out_filename)

            # Save this image to do the diff after applying IPC.
            save_image = final_image.copy()

        # 4) Including Interpixel capacitance:
        # The voltage read at a given pixel location is influenced by the charges present in the
        # neighboring pixel locations due to capacitive coupling of sense nodes. This interpixel
        # capacitance effect is modeled as a linear effect that is described as a convolution of a
        # 3x3 kernel with the image.  The WFIRST IPC routine knows about the kernel already, so the
        # user does not have to supply it.
        wfirst.applyIPC(final_image)
        logger.debug('Applied interpixel capacitance to {0}-band image'.format(
            filter_name))

        if diff_mode:
            # Isolate the changes due to the interpixel capacitance effect.
            diff = final_image - save_image

            out_filename = os.path.join(
                outpath, 'demo13_IPC_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(
                outpath, 'demo13_diff_IPC_{0}.fits'.format(filter_name))
            diff.write(out_filename)

        # 5) Adding read noise:
        # Read noise is the noise due to the on-chip amplifier that converts the charge into an
        # analog voltage.  We already applied the Poisson noise due to the sky level, so read noise
        # should just be added as Gaussian noise:
        read_noise = galsim.GaussianNoise(rng, sigma=wfirst.read_noise)
        final_image.addNoise(read_noise)
        logger.debug('Added readnoise to {0}-band image'.format(filter_name))

        # We divide by the gain to convert from e- to ADU. Currently, the gain value in the WFIRST
        # module is just set to 1, since we don't know what the exact gain will be, although it is
        # expected to be approximately 1. Eventually, this may change when the camera is assembled,
        # and there may be a different value for each SCA. For now, there is just a single number,
        # which is equal to 1.
        final_image /= wfirst.gain

        # Finally, the analog-to-digital converter reads in an integer value.
        final_image.quantize()
        # Note that the image type after this step is still a float.  If we want to actually
        # get integer values, we can do new_img = galsim.Image(final_image, dtype=int)

        # Since many people are used to viewing background-subtracted images, we provide a
        # version with the background subtracted (also rounding that to an int).
        tot_sky_image = (sky_image + dark_current) / wfirst.gain
        tot_sky_image.quantize()
        final_image -= tot_sky_image

        logger.debug(
            'Subtracted background for {0}-band image'.format(filter_name))
        # Write the final image to a file.
        out_filename = os.path.join(outpath,
                                    'demo13_{0}.fits'.format(filter_name))
        final_image.write(out_filename)

        logger.info('Completed {0}-band image.'.format(filter_name))

    logger.info(
        'You can display the output in ds9 with a command line that looks something like:'
    )
    logger.info('ds9 -zoom 0.5 -scale limits -500 1000 -rgb ' +
                '-red output/demo13_H158.fits ' +
                '-green output/demo13_J129.fits ' +
                '-blue output/demo13_Y106.fits')
Ejemplo n.º 25
0
def test_pad_image():
    """Test padding an InterpolatedImage with a pad_image."""
    import time
    t1 = time.time()

    decimal=2  # all are coarse, since there are slight changes from odd/even centering issues.
    noise_sigma = 1.73
    noise_var = noise_sigma**2
    orig_seed = 12345
    rng = galsim.BaseDeviate(orig_seed)
    noise = galsim.GaussianNoise(rng, sigma=noise_sigma)

    # make the original image 
    orig_nx = 64
    orig_ny = 64
    orig_img = galsim.ImageF(orig_nx, orig_ny, scale=1.)
    galsim.Exponential(scale_radius=1.7,flux=1000).draw(orig_img)
    orig_img.addNoise(noise)
    orig_img.setCenter(0,0)

    # We'll draw into a larger image for the tests
    pad_factor = 4
    big_nx = pad_factor*orig_nx
    big_ny = pad_factor*orig_ny
    big_img = galsim.ImageF(big_nx, big_ny, scale=1.)
    big_img.setCenter(0,0)

    # Use a few different kinds of shapes for that padding. 
    for (pad_nx, pad_ny) in [ (160,160), (179,191), (256,256), (305, 307) ]:
        #print 'pad size = ',pad_nx, pad_ny

        # make the pad_image 
        pad_img = galsim.ImageF(pad_nx, pad_ny, scale=1.)
        pad_img.addNoise(noise)
        pad_img.setCenter(0,0)

        # make an interpolated image padded with the pad_image, and outside of that
        int_im = galsim.InterpolatedImage(orig_img, pad_image=pad_img, use_true_center=False)

        # draw into the larger image
        int_im.draw(big_img, use_true_center=False)

        # check that variance is diluted by expected amount 
        # Note -- we don't use np.var, since that computes the variance relative to the 
        # actual mean value.  We just want sum(I^2)/Npix relative to the nominal I=0 value.
        var1 = np.sum(orig_img.array**2)
        if pad_nx > big_nx and pad_ny > big_ny:
            var2 = np.sum(pad_img[big_img.bounds].array**2)
        else:
            var2 = np.sum(pad_img.array**2) 
        var2 -= np.sum(pad_img[orig_img.bounds].array**2)
        var_expected = (var1 + var2) / (big_nx*big_ny)
        big_img.setCenter(0,0)
        np.testing.assert_almost_equal(
            np.mean(big_img.array**2), var_expected, decimal=decimal,
            err_msg='Variance not correct when padding with image')

        if pad_nx < big_nx and pad_ny < big_ny:
            # now also pad with noise_pad outside of the pad_image
            int_im = galsim.InterpolatedImage(orig_img, pad_image=pad_img, noise_pad=noise_var/2,
                                              noise_pad_size=max(big_nx,big_ny),
                                              rng=rng, use_true_center=False)
            int_im.draw(big_img, use_true_center=False)
    
            var3 = (noise_var/2) * float(big_nx*big_ny - pad_nx*pad_ny)
            var_expected = (var1 + var2 + var3) / (big_nx*big_ny)
            np.testing.assert_almost_equal(
                np.mean(big_img.array**2), var_expected, decimal=decimal,
                err_msg='Variance not correct after padding with image and extra noise')

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 26
0
def test_kolmogorov():
    """Test the generation of a specific Kolmogorov profile against a known result.
    """
    import math
    dx = 0.2
    test_flux = 1.8
    # This savedImg was created from the SBKolmogorov implementation in
    # commit c8efd74d1930157b1b1ffc0bfcfb5e1bf6fe3201
    # It would be nice to get an independent calculation here...
    #mySBP = galsim.SBKolmogorov(lam_over_r0=1.5, flux=test_flux)
    #savedImg = galsim.ImageF(128,128)
    #mySBP.drawImage(image=savedImg, dx=dx, method="sb")
    #savedImg.write(os.path.join(imgdir, "kolmogorov.fits"))
    savedImg = galsim.fits.read(os.path.join(imgdir, "kolmogorov.fits"))
    myImg = galsim.ImageF(savedImg.bounds, scale=dx)
    myImg.setCenter(0, 0)

    kolm = galsim.Kolmogorov(lam_over_r0=1.5, flux=test_flux)
    kolm.drawImage(myImg, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg="Using GSObject Kolmogorov disagrees with expected result")

    # Check with default_params
    kolm = galsim.Kolmogorov(lam_over_r0=1.5,
                             flux=test_flux,
                             gsparams=default_params)
    kolm.drawImage(myImg, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Kolmogorov with default_params disagrees with expected result"
    )
    kolm = galsim.Kolmogorov(lam_over_r0=1.5,
                             flux=test_flux,
                             gsparams=galsim.GSParams())
    kolm.drawImage(myImg, method="sb", use_true_center=False)
    np.testing.assert_array_almost_equal(
        myImg.array,
        savedImg.array,
        5,
        err_msg=
        "Using GSObject Kolmogorov with GSParams() disagrees with expected result"
    )

    gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8)
    kolm2 = galsim.Kolmogorov(lam_over_r0=1.5, flux=test_flux, gsparams=gsp)
    assert kolm2 != kolm
    assert kolm2 == kolm.withGSParams(gsp)

    check_basic(kolm, "Kolmogorov")

    # Test photon shooting.
    do_shoot(kolm, myImg, "Kolmogorov")

    # Test kvalues
    do_kvalue(kolm, myImg, "Kolmogorov")

    # Check picklability
    do_pickle(kolm, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(kolm)

    # Test initialization separately with lam and r0, in various units.  Since the above profiles
    # have lam/r0 = 3./2. in arbitrary units, we will tell it that lam=3.e9 nm and r0=2.0 m,
    # and use `scale_unit` of galsim.radians.  This is rather silly, but it should work.
    kolm = galsim.Kolmogorov(lam_over_r0=1.5, flux=test_flux)
    kolm2 = galsim.Kolmogorov(lam=3.e9,
                              r0=2.0,
                              scale_unit=galsim.radians,
                              flux=test_flux)
    gsobject_compare(kolm, kolm2)
    # For lam/r0 = 1.5 arcsec, and r0 = 0.2, lam = (1.5/3600/180*pi) * 0.2 * 1.e9
    lam = 1.5 * 0.2 / 3600. / 180. * math.pi * 1.e9
    print('lam = ', lam)
    kolm3 = galsim.Kolmogorov(lam=lam,
                              r0=0.2,
                              scale_unit='arcsec',
                              flux=test_flux)
    gsobject_compare(kolm, kolm3)
    # arcsec is the default scale_unit, so can leave this off.
    kolm4 = galsim.Kolmogorov(lam=lam, r0=0.2, flux=test_flux)
    gsobject_compare(kolm, kolm4)
    # Test using r0_500 instead
    r0_500 = 0.2 * (lam / 500)**-1.2
    kolm5 = galsim.Kolmogorov(lam=lam, r0_500=r0_500, flux=test_flux)
    gsobject_compare(kolm, kolm5)

    # Should raise an exception if >= 2 radius specifications are provided and/or lam and r0 are not
    # paired together.
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  lam_over_r0=3,
                  fwhm=2,
                  half_light_radius=1,
                  lam=3,
                  r0=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  fwhm=2,
                  half_light_radius=1,
                  lam=3,
                  r0=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  lam_over_r0=3,
                  half_light_radius=1,
                  lam=3,
                  r0=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  lam_over_r0=3,
                  fwhm=2,
                  lam=3,
                  r0=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  lam_over_r0=3,
                  fwhm=2,
                  half_light_radius=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  half_light_radius=1,
                  lam=3,
                  r0=1)
    assert_raises(TypeError, galsim.Kolmogorov, fwhm=2, lam=3, r0=1)
    assert_raises(TypeError, galsim.Kolmogorov, fwhm=2, half_light_radius=1)
    assert_raises(TypeError, galsim.Kolmogorov, lam_over_r0=3, lam=3, r0=1)
    assert_raises(TypeError,
                  galsim.Kolmogorov,
                  lam_over_r0=3,
                  half_light_radius=1)
    assert_raises(TypeError, galsim.Kolmogorov, lam_over_r0=3, fwhm=2)
    assert_raises(TypeError, galsim.Kolmogorov, lam_over_r0=3, lam=3)
    assert_raises(TypeError, galsim.Kolmogorov, lam_over_r0=3, r0=1)
    assert_raises(TypeError, galsim.Kolmogorov, fwhm=2, lam=3)
    assert_raises(TypeError, galsim.Kolmogorov, fwhm=2, r0=1)
    assert_raises(TypeError, galsim.Kolmogorov, half_light_radius=1, lam=3)
    assert_raises(TypeError, galsim.Kolmogorov, half_light_radius=1, r0=1)
    assert_raises(TypeError, galsim.Kolmogorov, lam=3)
    assert_raises(TypeError, galsim.Kolmogorov, r0=1)
    assert_raises(TypeError, galsim.Kolmogorov)
Ejemplo n.º 27
0
def test_conserve_dc():
    """Test that the conserve_dc option for Lanczos does so.
    Note: the idea of conserving flux is a bit of a misnomer.  No interpolant does so
    precisely in general.  What we are really testing is that a flat background input
    image has a relatively flat output image.
    """
    import time
    t1 = time.time()
    import numpy

    im1_size = 40
    scale1 = 0.23
    init_val = 1.

    im2_size = 100
    scale2 = 0.011  

    im1 = galsim.ImageF(im1_size, im1_size, scale=scale1, init_value=init_val)

    # im2 has a much smaller scale, but the same size, so interpolating an "infinite" 
    # constant field.
    im2 = galsim.ImageF(im2_size, im2_size, scale=scale2)

    for interp in ['linear', 'cubic', 'quintic']:
        print 'Testing interpolant ',interp
        obj = galsim.InterpolatedImage(im1, x_interpolant=interp, normalization='sb')
        obj.draw(im2, normalization='sb')
        print 'The maximum error is ',numpy.max(abs(im2.array-init_val))
        numpy.testing.assert_array_almost_equal(
                im2.array,init_val,5,
                '%s did not preserve a flat input flux using xvals.'%interp)

        # Convolve with a delta function to force FFT drawing.
        delta = galsim.Gaussian(sigma=1.e-8)
        obj2 = galsim.Convolve([obj,delta])
        obj2.draw(im2, normalization='sb')
        print 'The maximum error is ',numpy.max(abs(im2.array-init_val))
        numpy.testing.assert_array_almost_equal(
                im2.array,init_val,5,
                '%s did not preserve a flat input flux using uvals.'%interp)

    for n in [3,4,5,6,7,8]:  # 8 tests the generic formulae, since not specialized.
        print 'Testing Lanczos interpolant with n = ',n
        lan = galsim.Lanczos(n, conserve_dc=True)
        obj = galsim.InterpolatedImage(im1, x_interpolant=lan, normalization='sb')
        obj.draw(im2, normalization='sb')
        print 'The maximum error is ',numpy.max(abs(im2.array-init_val))
        numpy.testing.assert_array_almost_equal(
                im2.array,init_val,5,
                'Lanczos %d did not preserve a flat input flux using xvals.'%n)
    
        # Convolve with a delta function to force FFT drawing.
        delta = galsim.Gaussian(sigma=1.e-8)
        obj2 = galsim.Convolve([obj,delta])
        obj2.draw(im2, normalization='sb')
        print 'The maximum error is ',numpy.max(abs(im2.array-init_val))
        numpy.testing.assert_array_almost_equal(
                im2.array,init_val,5,
                'Lanczos %d did not preserve a flat input flux using uvals.'%n)
 
    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Ejemplo n.º 28
0
def test_fourier_sqrt():
    """Test that the FourierSqrt operator is the inverse of auto-convolution.
    """
    dx = 0.4
    myImg1 = galsim.ImageF(80, 80, scale=dx)
    myImg1.setCenter(0, 0)
    myImg2 = galsim.ImageF(80, 80, scale=dx)
    myImg2.setCenter(0, 0)

    # Test trivial case, where we could (but don't) analytically collapse the
    # chain of profiles by recognizing that FourierSqrt is the inverse of
    # AutoConvolve.
    psf = galsim.Moffat(beta=3.8, fwhm=1.3, flux=5)
    psf.drawImage(myImg1, method='no_pixel')
    sqrt1 = galsim.FourierSqrt(psf)
    psf2 = galsim.AutoConvolve(sqrt1)
    np.testing.assert_almost_equal(psf.stepk, psf2.stepk)
    psf2.drawImage(myImg2, method='no_pixel')
    printval(myImg1, myImg2)
    np.testing.assert_array_almost_equal(
        myImg1.array,
        myImg2.array,
        4,
        err_msg="Moffat sqrt convolved with self disagrees with original")

    check_basic(sqrt1, "FourierSqrt", do_x=False)

    # Test non-trivial case where we compare (in Fourier space) sqrt(a*a + b*b + 2*a*b) against (a + b)
    a = galsim.Moffat(beta=3.8, fwhm=1.3, flux=5)
    a.shift(dx=0.5, dy=-0.3)  # need nonzero centroid to test
    b = galsim.Moffat(beta=2.5, fwhm=1.6, flux=3)
    check = galsim.Sum([a, b])
    sqrt = galsim.FourierSqrt(
        galsim.Sum([
            galsim.AutoConvolve(a),
            galsim.AutoConvolve(b), 2 * galsim.Convolve([a, b])
        ]))
    np.testing.assert_almost_equal(check.stepk, sqrt.stepk)
    check.drawImage(myImg1, method='no_pixel')
    sqrt.drawImage(myImg2, method='no_pixel')
    np.testing.assert_almost_equal(check.centroid.x, sqrt.centroid.x)
    np.testing.assert_almost_equal(check.centroid.y, sqrt.centroid.y)
    np.testing.assert_almost_equal(check.flux, sqrt.flux)
    np.testing.assert_almost_equal(check.xValue(check.centroid), check.max_sb)
    print('check.max_sb = ', check.max_sb)
    print('sqrt.max_sb = ', sqrt.max_sb)
    # This isn't super accurate...
    np.testing.assert_allclose(check.max_sb, sqrt.max_sb, rtol=0.1)
    printval(myImg1, myImg2)
    np.testing.assert_array_almost_equal(
        myImg1.array,
        myImg2.array,
        4,
        err_msg="Fourier square root of expanded square disagrees with original"
    )

    # Check picklability
    do_pickle(sqrt1, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(sqrt1)

    # Should raise an exception for invalid arguments
    assert_raises(TypeError, galsim.FourierSqrt)
    assert_raises(TypeError, galsim.FourierSqrt, myImg1)
    assert_raises(TypeError, galsim.FourierSqrt, [psf])
    assert_raises(TypeError, galsim.FourierSqrt, psf, psf)
    assert_raises(TypeError, galsim.FourierSqrt, psf, real_space=False)
    assert_raises(TypeError, galsim.FourierSqrtProfile)
    assert_raises(TypeError, galsim.FourierSqrtProfile, myImg1)
    assert_raises(TypeError, galsim.FourierSqrtProfile, [psf])
    assert_raises(TypeError, galsim.FourierSqrtProfile, psf, psf)
    assert_raises(TypeError, galsim.FourierSqrtProfile, psf, real_space=False)

    assert_raises(NotImplementedError, sqrt1.xValue, galsim.PositionD(0, 0))
    assert_raises(NotImplementedError, sqrt1.drawReal, myImg1)
    assert_raises(NotImplementedError, sqrt1.shoot, 1)
Ejemplo n.º 29
0
    def build_file(seed, file_name, mass, nobj):
        """A function that does all the work to build a single file.
           Returns the total time taken.
        """
        t1 = time.time()

        # Build the image onto which we will draw the galaxies.
        full_image = galsim.ImageF(image_size, image_size)

        # The "true" center of the image is allowed to be halfway between two pixels, as is the
        # case for even-sized images.  full_image.bounds.center() is an integer position,
        # which would be 1/2 pixel up and to the right of the true center in this case.
        im_center = full_image.bounds.trueCenter()

        # For the WCS, this time we use UVFunction, which lets you define arbitrary u(x,y)
        # and v(x,y) functions.  We use a simple cubic radial function to create a
        # pincushion distortion.  This is a typical kind of telescope distortion, although
        # we exaggerate the magnitude of the effect to make it more apparent.
        # The pixel size in the center of the image is 0.05, but near the corners (r=362),
        # the pixel size is approximately 0.075, which is much more distortion than is
        # normally present in typical telescopes.  But it makes the effect of the variable
        # pixel area obvious when you look at the weight image in the output files.
        ufunc1 = lambda x, y: 0.05 * x * (1. + 2.e-6 * (x**2 + y**2))
        vfunc1 = lambda x, y: 0.05 * y * (1. + 2.e-6 * (x**2 + y**2))

        # It's not required to provide the inverse functions.  However, if we don't, then
        # you will only be able to do toWorld operations, not the inverse toImage.
        # The inverse function does not have to be exact either.  For example, you could provide
        # a function that does some kind of iterative solution to whatever accuracy you care
        # about.  But in this case, we can do the exact inverse.
        #
        # Let w = sqrt(u**2 + v**2) and r = sqrt(x**2 + y**2).  Then the solutions are:
        # x = (u/w) r and y = (u/w) r, and we use Cardano's method to solve for r given w:
        # See http://en.wikipedia.org/wiki/Cubic_function#Cardano.27s_method
        #
        # w = 0.05 r + 2.e-6 * 0.05 * r**3
        # r = 100 * ( ( 5 sqrt(w**2 + 5.e3/27) + 5 w )**(1./3.) -
        #           - ( 5 sqrt(w**2 + 5.e3/27) - 5 w )**(1./3.) )

        def xfunc1(u, v):
            import math
            wsq = u * u + v * v
            if wsq == 0.:
                return 0.
            else:
                w = math.sqrt(wsq)
                temp = 5. * math.sqrt(wsq + 5.e3 / 27)
                r = 100. * ((temp + 5 * w)**(1. / 3.) -
                            (temp - 5 * w)**(1. / 3))
                return u * r / w

        def yfunc1(u, v):
            import math
            wsq = u * u + v * v
            if wsq == 0.:
                return 0.
            else:
                w = math.sqrt(wsq)
                temp = 5. * math.sqrt(wsq + 5.e3 / 27)
                r = 100. * ((temp + 5 * w)**(1. / 3.) -
                            (temp - 5 * w)**(1. / 3))
                return v * r / w

        # You could pass the above functions to UVFunction, and normally we would do that.
        # The only down side to doing so is that the specification of the WCS in the FITS
        # file is rather ugly.  GalSim is able to turn the python byte code into strings,
        # but they are basically a really ugly mess of random-looking characters.  GalSim
        # will be able to read it back in, but human readers will have no idea what WCS
        # function was used.  To see what they look like, uncomment this line and comment
        # out the later wcs line.
        #wcs = galsim.UVFunction(ufunc1, vfunc1, xfunc1, yfunc1, origin=im_center)

        # If you provide the functions as strings, then those strings will be preserved
        # in the FITS header in a form that is more legible to human readers.
        # It also has the extra benefit of matching the output from demo9.yaml, which we
        # always try to do.  The config file has no choice but to specify the functions
        # as strings.

        ufunc = '0.05 * x * (1. + 2.e-6 * (x**2 + y**2))'
        vfunc = '0.05 * y * (1. + 2.e-6 * (x**2 + y**2))'
        xfunc = (
            '( lambda w: ( 0 if w==0 else ' +
            '100.*u/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' +
            '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )'
        )
        yfunc = (
            '( lambda w: ( 0 if w==0 else ' +
            '100.*v/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' +
            '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )'
        )

        # The origin parameter defines where on the image should be considered (x,y) = (0,0)
        # in the WCS functions.
        wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc, origin=im_center)

        # Assign this wcs to full_image
        full_image.wcs = wcs

        # The weight image will hold the inverse variance for each pixel.
        # We can set the wcs directly on construction with the wcs parameter.
        weight_image = galsim.ImageF(image_size, image_size, wcs=wcs)

        # It is common for astrometric images to also have a bad pixel mask.  We don't have any
        # defect simulation currently, so our bad pixel masks are currently all zeros.
        # But someday, we plan to add defect functionality to GalSim, at which point, we'll
        # be able to mark those defects on a bad pixel mask.
        # Note: the S in ImageS means to use "short int" for the data type.
        # This is a typical choice for a bad pixel image.
        badpix_image = galsim.ImageS(image_size, image_size, wcs=wcs)

        # We also draw a PSF image at the location of every galaxy.  This isn't normally done,
        # and since some of the PSFs overlap, it's not necessarily so useful to have this kind
        # of image.  But in this case, it's fun to look at the psf image, especially with
        # something like log scaling in ds9 to see how crazy an aberrated OpticalPSF with
        # struts can look when there is no atmospheric component to blur it out.
        psf_image = galsim.ImageF(image_size, image_size, wcs=wcs)

        # Setup the NFWHalo stuff:
        nfw = galsim.NFWHalo(mass=mass,
                             conc=nfw_conc,
                             redshift=nfw_z_halo,
                             omega_m=omega_m,
                             omega_lam=omega_lam)
        # Note: the last two are optional.  If they are omitted, then (omega_m=0.3, omega_lam=0.7)
        # are actually the defaults.  If you only specify one of them, the other is set so that
        # the total is 1.  But you can define both values so that the total is not 1 if you want.
        # Radiation is assumed to be zero and dark energy equation of state w = -1.
        # If you want to include either radiation or more complicated dark energy models,
        # you can define your own cosmology class that defines the functions a(z), E(a), and
        # Da(z_source, z_lens).  Then you can pass this to NFWHalo as a `cosmo` parameter.

        # Make the PSF profile outside the loop to minimize the (significant) OpticalPSF
        # construction overhead.
        psf = galsim.OpticalPSF(lam_over_diam=psf_lam_over_D,
                                obscuration=psf_obsc,
                                nstruts=psf_nstruts,
                                strut_thick=psf_strut_thick,
                                strut_angle=psf_strut_angle,
                                defocus=psf_defocus,
                                astig1=psf_astig1,
                                astig2=psf_astig2,
                                coma1=psf_coma1,
                                coma2=psf_coma2,
                                trefoil1=psf_trefoil1,
                                trefoil2=psf_trefoil2)

        for k in range(nobj):

            # Initialize the random number generator we will be using for this object:
            rng = galsim.UniformDeviate(seed + k)

            # Determine where this object is going to go.
            # We choose points randomly within a donut centered at the center of the main image
            # in order to avoid placing galaxies too close to the halo center where the lensing
            # is not weak.  We use an inner radius of 3 arcsec and an outer radius of 12 arcsec,
            # which takes us essentially to the edge of the image.
            radius = 12
            inner_radius = 3
            max_rsq = radius**2
            min_rsq = inner_radius**2
            while True:  # (This is essentially a do..while loop.)
                x = (2. * rng() - 1) * radius
                y = (2. * rng() - 1) * radius
                rsq = x**2 + y**2
                if rsq >= min_rsq and rsq <= max_rsq: break
            pos = galsim.PositionD(x, y)

            # We also need the position in pixels to determine where to place the postage
            # stamp on the full image.
            image_pos = wcs.toImage(pos)

            # For even-sized postage stamps, the nominal center (returned by stamp.bounds.center())
            # cannot be at the true center (returned by stamp.bounds.trueCenter()) of the postage
            # stamp, since the nominal center values have to be integers.  Thus, the nominal center
            # is 1/2 pixel up and to the right of the true center.
            # If we used odd-sized postage stamps, we wouldn't need to do this.
            x_nominal = image_pos.x + 0.5
            y_nominal = image_pos.y + 0.5

            # Get the integer values of these which will be the actual nominal center of the
            # postage stamp image.
            ix_nominal = int(math.floor(x_nominal + 0.5))
            iy_nominal = int(math.floor(y_nominal + 0.5))

            # The remainder will be accounted for in an offset when we draw.
            dx = x_nominal - ix_nominal
            dy = y_nominal - iy_nominal
            offset = galsim.PositionD(dx, dy)

            # Determine the random values for the galaxy:
            flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min
            hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
            gd = galsim.GaussianDeviate(rng, sigma=gal_eta_rms)
            eta1 = gd(
            )  # Unlike g or e, large values of eta are valid, so no need to cutoff.
            eta2 = gd()

            # Make the galaxy profile with these values:
            gal = galsim.Exponential(half_light_radius=hlr, flux=flux)
            gal = gal.shear(eta1=eta1, eta2=eta2)

            # Now apply the appropriate lensing effects for this position from
            # the NFW halo mass.
            try:
                g1, g2 = nfw.getShear(pos, nfw_z_source)
                nfw_shear = galsim.Shear(g1=g1, g2=g2)
            except:
                # This shouldn't happen, since we exclude the inner 10 arcsec, but it's a
                # good idea to use the try/except block here anyway.
                import warnings
                warnings.warn(
                    "Warning: NFWHalo shear is invalid -- probably strong lensing!  "
                    + "Using shear = 0.")
                nfw_shear = galsim.Shear(g1=0, g2=0)

            nfw_mu = nfw.getMagnification(pos, nfw_z_source)
            if nfw_mu < 0:
                import warnings
                warnings.warn(
                    "Warning: mu < 0 means strong lensing!  Using mu=25.")
                nfw_mu = 25
            elif nfw_mu > 25:
                import warnings
                warnings.warn(
                    "Warning: mu > 25 means strong lensing!  Using mu=25.")
                nfw_mu = 25

            # Calculate the total shear to apply
            # Since shear addition is not commutative, it is worth pointing out that
            # the order is in the sense that the second shear is applied first, and then
            # the first shear.  i.e. The field shear is taken to be behind the cluster.
            # Kind of a cosmic shear contribution between the source and the cluster.
            # However, this is not quite the same thing as doing:
            #     gal.shear(field_shear).shear(nfw_shear)
            # since the shear addition ignores the rotation that would occur when doing the
            # above lines.  This is normally ok, because the rotation is not observable, but
            # it is worth keeping in mind.
            total_shear = nfw_shear + field_shear

            # Apply the magnification and shear to the galaxy
            gal = gal.magnify(nfw_mu)
            gal = gal.shear(total_shear)

            # Build the final object
            final = galsim.Convolve([psf, gal])

            # Draw the stamp image
            # To draw the image at a position other than the center of the image, you can
            # use the offset parameter, which applies an offset in pixels relative to the
            # center of the image.
            # We also need to provide the local wcs at the current position.
            local_wcs = wcs.local(image_pos)
            stamp = final.drawImage(wcs=local_wcs, offset=offset)

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

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

            # Also draw the PSF
            psf_stamp = galsim.ImageF(
                stamp.bounds)  # Use same bounds as galaxy stamp
            psf.drawImage(psf_stamp, wcs=local_wcs, offset=offset)
            psf_image[bounds] += psf_stamp[bounds]

        # Add Poisson noise to the full image
        # Note: The normal calculation of Poission noise isn't quite correct right now.
        # The pixel area is variable, which means the amount of sky flux that enters each
        # pixel is also variable.  The wcs classes have a function `makeSkyImage` which
        # will fill an image with the correct amount of sky flux given the sky level
        # in units of ADU/arcsec^2.  We use the weight image as our work space for this.
        wcs.makeSkyImage(weight_image, sky_level)

        # Add this to the current full_image (temporarily).
        full_image += weight_image

        # Add Poisson noise, given the current full_image.
        # Going to the next seed isn't really required, but it matches the behavior of the
        # config parser, so doing this will result in identical output files.
        # If you didn't care about that, you could instead construct this as a continuation
        # of the last RNG from the above loop
        rng = galsim.BaseDeviate(seed + nobj)
        full_image.addNoise(galsim.PoissonNoise(rng))

        # Subtract the sky back off.
        full_image -= weight_image

        # The weight image is nominally the inverse variance of the pixel noise.  However, it is
        # common to exclude the Poisson noise from the objects themselves and only include the
        # noise from the sky photons.  The variance of the noise is just the sky level, which is
        # what is currently in the weight_image.  (If we wanted to include the variance from the
        # objects too, then we could use the full_image before we added the PoissonNoise to it.)
        # So all we need to do now is to invert the values in weight_image.
        weight_image.invertSelf()

        # Write the file to disk:
        galsim.fits.writeMulti(
            [full_image, badpix_image, weight_image, psf_image], file_name)

        t2 = time.time()
        return t2 - t1
Ejemplo n.º 30
0
def main(argv):
    # Where to find and output data
    path, filename = os.path.split(__file__)
    datapath = os.path.abspath(os.path.join(path, "data/"))
    outpath = os.path.abspath(os.path.join(path, "output/"))

    if not os.path.exists(outpath):
        print 'Creating', outpath
        os.makedirs(outpath)

    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo12")

    # initialize (pseudo-)random number generator
    random_seed = 1234567
    rng = galsim.BaseDeviate(random_seed)

    # read in SEDs
    SED_names = ['CWW_E_ext', 'CWW_Sbc_ext', 'CWW_Scd_ext', 'CWW_Im_ext']
    SEDs = {}
    for SED_name in SED_names:
        SED_filename = os.path.join(datapath, '{}.sed'.format(SED_name))
        # Here we create some galsim.SED objects to hold star or galaxy spectra.  The most
        # convenient way to create realistic spectra is to read them in from a two-column ASCII
        # file, where the first column is wavelength and the second column is flux. Wavelengths in
        # the example SED files are in Angstroms, flux in flambda.  The default wavelength type for
        # galsim.SED is nanometers, however, so we need to override by specifying
        # `wave_type = 'Ang'`.
        SED = galsim.SED(SED_filename, wave_type='Ang')
        # The normalization of SEDs affects how many photons are eventually drawn into an image.
        # One way to control this normalization is to specify the flux density in photons per nm
        # at a particular wavelength.  For example, here we normalize such that the photon density
        # is 1 photon per nm at 500 nm.
        SEDs[SED_name] = SED.withFluxDensity(target_flux_density=1.0,
                                             wavelength=500)
    logger.debug('Successfully read in SEDs')

    # read in the LSST filters
    filter_names = 'ugrizy'
    filters = {}
    for filter_name in filter_names:
        filter_filename = os.path.join(datapath,
                                       'LSST_{}.dat'.format(filter_name))
        # Here we create some galsim.Bandpass objects to represent the filters we're observing
        # through.  These include the entire imaging system throughput including the atmosphere,
        # reflective and refractive optics, filters, and the CCD quantum efficiency.  These are
        # also conveniently read in from two-column ASCII files where the first column is
        # wavelength and the second column is dimensionless flux. The example filter files have
        # units of nanometers and dimensionless throughput, which is exactly what galsim.Bandpass
        # expects, so we just specify the filename.
        filters[filter_name] = galsim.Bandpass(filter_filename)
        # For speed, we can thin out the wavelength sampling of the filter a bit.
        # In the following line, `rel_err` specifies the relative error when integrating over just
        # the filter (however, this is not necessarily the relative error when integrating over the
        # filter times an SED)
        filters[filter_name] = filters[filter_name].thin(rel_err=1e-4)
    logger.debug('Read in filters')

    pixel_scale = 0.2  # arcseconds

    #-----------------------------------------------------------------------------------------------
    # Part B: chromatic bulge+disk galaxy

    logger.info('')
    logger.info('Starting part B: chromatic bulge+disk galaxy')
    redshift = 0.8
    # make a bulge ...
    mono_bulge = galsim.DeVaucouleurs(half_light_radius=0.5)
    bulge_SED = SEDs['CWW_E_ext'].atRedshift(redshift)
    # The `*` operator can be used as a shortcut for creating a chromatic version of a GSObject:
    bulge = mono_bulge * bulge_SED
    bulge = bulge.shear(g1=0.12, g2=0.07)
    logger.debug('Created bulge component')
    # ... and a disk ...
    mono_disk = galsim.Exponential(half_light_radius=2.0)
    disk_SED = SEDs['CWW_Im_ext'].atRedshift(redshift)
    disk = mono_disk * disk_SED
    disk = disk.shear(g1=0.4, g2=0.2)
    logger.debug('Created disk component')
    # ... and then combine them.
    bdgal = 1.1 * (
        0.8 * bulge + 4 * disk
    )  # you can add and multiply ChromaticObjects just like GSObjects

    # convolve with PSF to make final profile
    #PSF = galsim.Moffat(fwhm=0.6, beta=2.5)
    psf_sigma = 1.5 * pixel_scale  # in arcsec
    PSF = galsim.Gaussian(flux=1., sigma=psf_sigma)

    bdfinal = galsim.Convolve([bdgal, PSF])
    # Note that at this stage, our galaxy is chromatic but our PSF is still achromatic.  Part C)
    # below will dive into chromatic PSFs.
    logger.debug('Created bulge+disk galaxy final profile')

    # draw profile through LSST filters
    gaussian_noise = galsim.GaussianNoise(rng, sigma=0.02)
    for filter_name, filter_ in filters.iteritems():
        img = galsim.ImageF(64, 64, scale=pixel_scale)
        bdfinal.drawImage(filter_, image=img)
        logger.debug('Created {}-band image'.format(filter_name))
        out_filename = os.path.join(outpath,
                                    'demo12b_{}.fits'.format(filter_name))

        nepochs = 3
        images = []
        for i in range(nepochs):
            newImg = img.copy()
            newImg.addNoise(gaussian_noise)
            images.append(newImg)
        galsim.fits.writeCube(images, out_filename)
        logger.debug('Wrote {}-band images to disk'.format(filter_name))
        logger.info('Added flux for {}-band image: {}'.format(
            filter_name, img.added_flux))

    logger.info(
        'You can display the output in ds9 with a command line that looks something like:'
    )
    logger.info(
        'ds9 -rgb -blue -scale limits -0.2 0.8 output/demo12b_r.fits -green -scale limits'
        +
        ' -0.25 1.0 output/demo12b_i.fits -red -scale limits -0.25 1.0 output/demo12b_z.fits'
        + ' -zoom 2 &')