def getNoiseModel(self, skyLevel=0.0, photParams=None):

        """
        This method returns the noise model implemented for this wrapper
        class.

        @param [in] skyLevel is the number of electrons per pixel due
        to the sky background.  However, this value should only be non-zero
        if the sky background has been subtracted from the image.  The
        purpose of this parameter is to provide an extra background value
        when calculating the level of Poisson noise in each pixel.  If the
        sky background is already present in the image, then the noise model
        will just set the noise level based on the intensity in each pixel
        and there is no need to add an additional skyLevel.  If the sky
        background is still included in the image, set skyLevel equal to zero.

        @param [in] photParams is an instantiation of the
        PhotometricParameters class that carries details about the
        photometric response of the telescope.  Defaults to None.

        @param [out] returns an instantiation of the GalSim CCDNoise class
        """

        return galsim.CCDNoise(self.randomNumbers, sky_level=skyLevel,
                               gain=photParams.gain, read_noise=photParams.readnoise)
Example #2
0
    def getNoiseModel(self, skyLevel=0.0, photParams=None):
        """
        This method returns the noise model implemented for this wrapper
        class.

        This is currently the same as implemented in ExampleCCDNoise.  This
        routine can both Poisson fluctuate the background and add read noise.
        We turn off the read noise by adjusting the parameters in the photParams.
        """
        return galsim.CCDNoise(self.randomNumbers,
                               sky_level=skyLevel,
                               gain=photParams.gain,
                               read_noise=photParams.readnoise)
Example #3
0
def test_dep_noise():
    """Test the deprecated methods in galsim/deprecated/noise.py
    """
    import time
    t1 = time.time()

    rng = galsim.BaseDeviate(123)
    gn = galsim.GaussianNoise(rng=rng, sigma=0.3)

    rng2 = galsim.BaseDeviate(999)
    check_dep(gn.setRNG, rng2)
    assert gn.rng is rng2

    check_dep(gn.setVariance, 1.7)
    np.testing.assert_almost_equal(gn.getVariance(), 1.7)
    check_dep(gn.scaleVariance, 1.9)
    np.testing.assert_almost_equal(gn.getVariance(), 1.7 * 1.9)

    check_dep(gn.setSigma, 2.3)
    np.testing.assert_almost_equal(gn.getSigma(), 2.3)

    pn = galsim.PoissonNoise(rng=rng, sky_level=0.3)
    check_dep(pn.setSkyLevel, 2.3)
    np.testing.assert_almost_equal(pn.getSkyLevel(), 2.3)

    cn = galsim.CCDNoise(rng=rng, gain=1.7, read_noise=0.5, sky_level=0.3)
    np.testing.assert_almost_equal(cn.getSkyLevel(), 0.3)
    np.testing.assert_almost_equal(cn.getGain(), 1.7)
    np.testing.assert_almost_equal(cn.getReadNoise(), 0.5)

    check_dep(cn.setSkyLevel, 2.3)
    np.testing.assert_almost_equal(cn.getSkyLevel(), 2.3)
    np.testing.assert_almost_equal(cn.getGain(), 1.7)
    np.testing.assert_almost_equal(cn.getReadNoise(), 0.5)

    check_dep(cn.setGain, 0.9)
    np.testing.assert_almost_equal(cn.getSkyLevel(), 2.3)
    np.testing.assert_almost_equal(cn.getGain(), 0.9)
    np.testing.assert_almost_equal(cn.getReadNoise(), 0.5)

    check_dep(cn.setReadNoise, 11)
    np.testing.assert_almost_equal(cn.getSkyLevel(), 2.3)
    np.testing.assert_almost_equal(cn.getGain(), 0.9)
    np.testing.assert_almost_equal(cn.getReadNoise(), 11)

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Example #4
0
    def addNoise(self, config, base, im, rng, current_var, draw_method,
                 logger):

        # This process goes a lot like the Poisson routine.  There are just two differences.
        # First, the Poisson noise is in electrons, not ADU, and now we allow for a gain = e-/ADU,
        # so we need to account for that properly.  Second, we also allow for an additional Gaussian
        # read noise.
        gain, read_noise, read_noise_var = self.getCCDNoiseParams(config, base)

        # Get how much extra sky to assume from the image.noise attribute.
        sky = GetSky(base['image'], base, logger)
        extra_sky = GetSky(config, base, logger)
        total_sky = sky + extra_sky  # for the return value
        if isinstance(total_sky, galsim.Image):
            var = np.mean(total_sky.array) + read_noise_var
        else:
            var = total_sky + read_noise_var

        # If we already have some variance in the image (from whitening), then we try to subtract
        # it from the read noise if possible.  If not, we subtract the rest off of the sky level.
        # It's not precisely accurate, since the existing variance is Gaussian, rather than
        # Poisson, but it's the best we can do.
        if current_var:
            logger.debug(
                'image %d, obj %d: Target variance is %f, current variance is %f',
                base.get('image_num', 0), base.get('obj_num', 0), var,
                current_var)
            read_noise_var_adu = read_noise_var / gain**2
            if isinstance(total_sky, galsim.Image):
                test = np.any(
                    total_sky.array / gain + read_noise_var_adu < current_var)
            else:
                target_var = total_sky / gain + read_noise_var_adu
                logger.debug(
                    'image %d, obj %d: Target variance is %f, current variance is %f',
                    base.get('image_num', 0), base.get('obj_num', 0),
                    target_var, current_var)
                test = target_var < current_var
            if test:
                raise galsim.GalSimConfigError(
                    "Whitening already added more noise than the requested CCD noise."
                )
            if read_noise_var_adu >= current_var:
                # First try to take away from the read_noise, since this one is actually Gaussian.
                import math
                read_noise_var -= current_var * gain**2
                read_noise = math.sqrt(read_noise_var)
            else:
                # Take read_noise down to zero, since already have at least that much already.
                current_var -= read_noise_var_adu
                read_noise = 0
                read_noise_var = 0
                # Take the rest away from the sky level
                total_sky -= current_var * gain
                extra_sky -= current_var * gain

        # At this point, there is a slight difference between fft and phot. For photon shooting,
        # the galaxy already has Poisson noise, so we want to make sure not to add that again!
        if draw_method == 'phot':
            # Add in the noise from the sky.
            if isinstance(total_sky, galsim.Image):
                noise_im = total_sky.copy()
                if gain != 1.0: noise_im *= gain
                noise_im.addNoise(galsim.PoissonNoise(rng))
                if gain != 1.0: noise_im /= gain
                noise_im -= total_sky
                # total_sky should now have zero mean, but with the noise of the total sky level.
                im += noise_im
            else:
                if gain != 1.0: im *= gain
                pd = galsim.PoissonDeviate(rng, mean=total_sky * gain)
                im.addNoise(galsim.DeviateNoise(pd))
                if gain != 1.0: im /= gain
                im -= total_sky
            # And add the read noise
            if read_noise != 0.:
                im.addNoise(galsim.GaussianNoise(rng, sigma=read_noise / gain))
        else:
            # Do the normal CCDNoise calculation.
            if isinstance(total_sky, galsim.Image):
                im += extra_sky
                im.addNoise(
                    galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))
                im -= extra_sky
            else:
                im.addNoise(
                    galsim.CCDNoise(rng,
                                    gain=gain,
                                    read_noise=read_noise,
                                    sky_level=extra_sky))

        logger.debug(
            'image %d, obj %d: Added CCD noise with gain = %f, read_noise = %f',
            base.get('image_num', 0), base.get('obj_num', 0), gain, read_noise)

        return var
Example #5
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from a processed COSMOS 2015 Catalog, scaled to match
        anticipated SuperBIT 2021 observations
      - The galaxy shape parameters are assigned in a probabilistic way through matching
        galaxy fluxes and redshifts to similar GalSim-COSMOS galaxies (see A. Gill+ 2021)
    """
    
    global logger
    logging.basicConfig(format="%(message)s", level=logging.DEBUG, stream=sys.stdout)
    logger = logging.getLogger("mock_superbit_data")

    M = MPIHelper()

    # Define some parameters we'll use below.
    sbparams = SuperBITParameters(argv=argv)
    
    # Set up the NFWHalo:
    nfw = galsim.NFWHalo(mass=sbparams.mass, conc=sbparams.nfw_conc, redshift=sbparams.nfw_z_halo,
                     omega_m=sbparams.omega_m, omega_lam=sbparams.omega_lam)

    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.   
    # cosmos_cat = galsim.COSMOSCatalog(sbparams.cat_file_name, dir=sbparams.cosmosdir)
    # fitcat = Table.read(os.path.join(sbparams.cosmosdir, sbparams.fit_file_name))

    cosmos_cat = Table.read(os.path.join(sbparams.cosmosdir,sbparams.cat_file_name))
    logger.info('Read in %d galaxies from catalog and associated fit info', len(cosmos_cat))

    cluster_cat = galsim.COSMOSCatalog(sbparams.cluster_cat_name)
    logger.debug('Read in %d cluster galaxies from catalog' % cosmos_cat.nobjects)
    

    ### Now create PSF. First, define Zernicke polynomial component
    ### note: aberrations were definined for lam = 550, and close to the
    ### center of the camera. The PSF degrades at the edge of the FOV
    lam_over_diam = sbparams.lam * 1.e-9 / sbparams.tel_diam    # radians
    lam_over_diam *= 206265.

    aberrations = numpy.zeros(38)             # Set the initial size.
    aberrations[0] = 0.                       # First entry must be zero
    aberrations[1] = -0.00305127
    aberrations[4] = -0.02474205              # Noll index 4 = Defocus
    aberrations[11] = -0.01544329             # Noll index 11 = Spherical
    aberrations[22] = 0.00199235
    aberrations[26] = 0.00000017
    aberrations[37] = 0.00000004
    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)

    # will store the Zernicke component of the PSF
    optics = galsim.OpticalPSF(lam=sbparams.lam,diam=sbparams.tel_diam, 
                        obscuration=sbparams.obscuration, nstruts=sbparams.nstruts, 
                        strut_angle=sbparams.strut_angle, strut_thick=sbparams.strut_thick,
                        aberrations=aberrations)

    logger.info('Made telescope PSF profile')
        
    ###
    ### MAKE SIMULATED OBSERVATIONS 
    ### ITERATE n TIMES TO MAKE n SEPARATE IMAGES
    ###

        
    for i in numpy.arange(1,sbparams.nexp+1):          
        # get MPI processes in sync at start of each image
        M.barrier()
        
        #rng = galsim.BaseDeviate(sbparams.noise_seed+i)

        try:
            timescale=str(sbparams.exp_time)
            outname=''.join(['superbit_gaussJitter_',str(i).zfill(3),'.fits'])
            truth_file_name=''.join([sbparams.outdir, '/truth_gaussJitter_', str(i).zfill(3), '.dat'])
            file_name = os.path.join(sbparams.outdir, outname)

        except galsim.errors.GalSimError:
            print("naming failed, check path")
            pdb.set_trace()

            
        # Setting up a truth catalog
        names = [ 'gal_num', 'x_image', 'y_image',
                    'ra', 'dec', 'g1_meas', 'g2_meas', 'nfw_mu', 'redshift','flux','truth_fwhm','truth_mom',
                      'n','hlr','inclination','scale_h_over_r']
        types = [ int, float, float, float,float,float,
                    float, float, float, float, float, float,
                      float, float, float, float]
        truth_catalog = galsim.OutputCatalog(names, types)

        
        # Set up the image:
        full_image = galsim.ImageF(sbparams.image_xsize, sbparams.image_ysize)
        sky_level = sbparams.exp_time * sbparams.sky_bkg
        full_image.fill(sky_level)
        full_image.setOrigin(0,0)
        
        
        # If you wanted to make a non-trivial WCS system, could set theta to a non-zero number
        theta = 0.0 * galsim.degrees
        dudx = numpy.cos(theta) * sbparams.pixel_scale
        dudy = -numpy.sin(theta) * sbparams.pixel_scale
        dvdx = numpy.sin(theta) * sbparams.pixel_scale
        dvdy = numpy.cos(theta) * sbparams.pixel_scale
        image_center = full_image.true_center
        affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center)
        sky_center = galsim.CelestialCoord(ra=sbparams.center_ra, dec=sbparams.center_dec)
        
        wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
        full_image.wcs = wcs

        
        ## Now let's read in the PSFEx PSF model, if using.
        ## We read the image directly into an InterpolatedImage GSObject,
        ## so we can manipulate it as needed 
        #psf_wcs=wcs
        #psf = galsim.des.DES_PSFEx(psf_filen,wcs=psf_wcs)
        #logger.info('Constructed PSF object from PSFEx file')

        #####
        ## Loop over galaxy objects:
        #####
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nobj)
        for k in range(local_start, local_end):
            time1 = time.time()
            
            # The usual random number generator using a different seed for each galaxy.
            ud = galsim.UniformDeviate(sbparams.galobj_seed+k+1)

            try: 
                # make single galaxy object
                stamp,truth = make_a_galaxy(ud=ud,wcs=wcs,affine=affine,
                        cosmos_cat=cosmos_cat,optics=optics,nfw=nfw,
                        sbparams=sbparams)                
                # Find the overlapping bounds:
                bounds = stamp.bounds & full_image.bounds
                
                # We need to keep track of how much variance we have currently in the image, so when
                # we add more noise, we can omit what is already there.

                # noise_image[bounds] += truth.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 positioned relative to center t=%f s\n',
                            k, tot_time)
                this_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,truth.z,
                            this_flux,truth.fwhm, truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
            except galsim.errors.GalSimError:
                logger.info('Galaxy %d has failed, skipping...',k)

        #####
        ### Inject cluster galaxy objects:
        #####
     
        center_coords = galsim.CelestialCoord(sbparams.center_ra,sbparams.center_dec)
        centerpix = wcs.toImage(center_coords)
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nclustergal)
        for k in range(local_start, local_end):

            time1 = time.time()
        
            # The usual random number generator using a different seed for each galaxy.
            ud = galsim.UniformDeviate(sbparams.cluster_seed+k+1)
            
            try: 
                # make single galaxy object
                cluster_stamp,truth = make_cluster_galaxy(ud=ud,wcs=wcs,affine=affine,
                                                              centerpix=centerpix,
                                                              cluster_cat=cluster_cat,
                                                              optics=optics,
                                                              sbparams=sbparams)                
                # Find the overlapping bounds:
                bounds = cluster_stamp.bounds & full_image.bounds
                
                # We need to keep track of how much variance we have currently in the image, so when
                # we add more noise, we can omit what is already there.
        
                #noise_image[bounds] += truth.variance
        
                # Finally, add the stamp to the full image.
                
                full_image[bounds] += cluster_stamp[bounds]
                time2 = time.time()
                tot_time = time2-time1
                logger.info('Cluster galaxy %d positioned relative to center t=%f s\n',
                                k, tot_time)
                this_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,truth.z,
                            this_flux,truth.fwhm,truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
            except galsim.errors.GalSimError:
                logger.info('Cluster galaxy %d has failed, skipping...',k)
                
        
            
        #####
        ### Now repeat process for stars!
        #####
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nstars)
        for k in range(local_start, local_end):
            time1 = time.time()
            ud = galsim.UniformDeviate(sbparams.stars_seed+k+1)

            star_stamp,truth = make_a_star(ud=ud, wcs=wcs, affine=affine, 
                    optics=optics, sbparams=sbparams)
            bounds = star_stamp.bounds & full_image.bounds
           
            # Add the stamp to the full image.
            try: 
                full_image[bounds] += star_stamp[bounds]
        
                time2 = time.time()
                tot_time = time2-time1
                
                logger.info('Star %d: positioned relative to center, t=%f s',
                            k,  tot_time)
                this_flux=numpy.sum(star_stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,
                            truth.z, this_flux,truth.fwhm,truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
                
            except galsim.errors.GalSimError:
                logger.info('Star %d has failed, skipping...',k)

        # Gather results from MPI processes, reduce to single result on root
        # Using same names on left and right sides is hiding lots of MPI magic
        full_image = M.gather(full_image)
        truth_catalog = M.gather(truth_catalog)
        if M.is_mpi_root():
            full_image = reduce(combine_images, full_image)
            truth_catalog = reduce(combine_catalogs, truth_catalog)
        else:
            # do the adding of noise and writing to disk entirely on root
            # root and the rest meet again at barrier at start of loop
            continue
        

        # The first thing to do is to make the Gaussian noise uniform across the whole image.
        
        # Add dark current
        logger.info('Adding Dark current')
        dark_noise = sbparams.dark_current * sbparams.exp_time
        full_image += dark_noise
        
        # Add ccd noise
        logger.info('Adding CCD noise')
        noise = galsim.CCDNoise(
            sky_level=0, gain=1/sbparams.gain,
            read_noise=sbparams.read_noise)
        full_image.addNoise(noise)
        
        logger.debug('Added noise to final output image')
        if not os.path.exists(os.path.dirname(file_name)):
            os.makedirs(os.path.dirname(file_name))
        full_image.write(file_name)

     
        # Write truth catalog to file. 
        truth_catalog.write(truth_file_name)
        logger.info('Wrote image to %r',file_name)

            
    logger.info(' ')
    logger.info('completed all images')
    logger.info(' ')
Example #6
0
def test_whiten():
    """Test the options in config to whiten images
    """
    real_gal_dir = os.path.join('..','examples','data')
    real_gal_cat = 'real_galaxy_catalog_23.5_example.fits'
    config = {
        'image' : {
            'type' : 'Single',
            'random_seed' : 1234,
            'pixel_scale' : 0.05,
        },
        'stamp' : {
            'type' : 'Basic',
            'size' : 32,
        },
        'gal' : {
            'type' : 'RealGalaxy',
            'index' : 79,
            'flux' : 100,
        },
        'psf' : {  # This is really slow if we don't convolve by a PSF.
            'type' : 'Gaussian',
            'sigma' : 0.05
        },
        'input' : {
            'real_catalog' : {
                'dir' : real_gal_dir ,
                'file_name' : real_gal_cat
            }
        }
    }

    # First build by hand (no whitening yet)
    rng = galsim.BaseDeviate(1234 + 1)
    rgc = galsim.RealGalaxyCatalog(os.path.join(real_gal_dir, real_gal_cat))
    gal = galsim.RealGalaxy(rgc, index=79, flux=100, rng=rng)
    psf = galsim.Gaussian(sigma=0.05)
    final = galsim.Convolve(gal,psf)
    im1a = final.drawImage(nx=32, ny=32, scale=0.05)

    # Compare to what config builds
    galsim.config.ProcessInput(config)
    im1b, cv1b = galsim.config.BuildStamp(config, do_noise=False)
    np.testing.assert_equal(cv1b, 0.)
    np.testing.assert_equal(im1b.array, im1a.array)

    # Now add whitening, but no noise yet.
    cv1a = final.noise.whitenImage(im1a)
    print('From whiten, current_var = ',cv1a)
    galsim.config.RemoveCurrent(config)
    config['image']['noise'] =  { 'whiten' : True, }
    im1c, cv1c = galsim.config.BuildStamp(config, do_noise=False)
    print('From BuildStamp, current_var = ',cv1c)
    np.testing.assert_equal(cv1c, cv1a)
    np.testing.assert_equal(im1c.array, im1a.array)
    rng1 = rng.duplicate()  # Save current state of rng

    # 1. Gaussian noise
    #####
    config['image']['noise'] =  {
        'type' : 'Gaussian',
        'variance' : 50,
        'whiten' : True,
    }
    galsim.config.RemoveCurrent(config)
    im2a = im1a.copy()
    im2a.addNoise(galsim.GaussianNoise(sigma=math.sqrt(50-cv1a), rng=rng))
    im2b, cv2b = galsim.config.BuildStamp(config)
    np.testing.assert_almost_equal(cv2b, 50)
    np.testing.assert_almost_equal(im2b.array, im2a.array, decimal=5)

    # If whitening already added too much noise, raise an exception
    config['image']['noise']['variance'] = 1.e-5
    try:
        np.testing.assert_raises(RuntimeError, galsim.config.BuildStamp,config)
    except ImportError:
        pass

    # 2. Poisson noise
    #####
    config['image']['noise'] =  {
        'type' : 'Poisson',
        'sky_level_pixel' : 50,
        'whiten' : True,
    }
    galsim.config.RemoveCurrent(config)
    im3a = im1a.copy()
    sky = 50 - cv1a
    rng.reset(rng1.duplicate())
    im3a.addNoise(galsim.PoissonNoise(sky_level=sky, rng=rng))
    im3b, cv3b = galsim.config.BuildStamp(config)
    np.testing.assert_almost_equal(cv3b, 50, decimal=5)
    np.testing.assert_almost_equal(im3b.array, im3a.array, decimal=5)

    # It's more complicated if the sky is quoted per arcsec and the wcs is not uniform.
    config2 = galsim.config.CopyConfig(config)
    galsim.config.RemoveCurrent(config2)
    config2['image']['sky_level'] = 100
    config2['image']['wcs'] =  {
        'type' : 'UVFunction',
        'ufunc' : '0.05*x + 0.001*x**2',
        'vfunc' : '0.05*y + 0.001*y**2',
    }
    del config2['image']['pixel_scale']
    del config2['wcs']
    config2['image']['noise']['symmetrize'] = 4 # Also switch to symmetrize, just to mix it up.
    del config2['image']['noise']['whiten']
    rng.reset(1234+1) # Start fresh, since redoing the whitening/symmetrizing
    wcs = galsim.UVFunction(ufunc='0.05*x + 0.001*x**2', vfunc='0.05*y + 0.001*y**2')
    im3c = galsim.Image(32,32, wcs=wcs)
    im3c = final.drawImage(im3c)
    cv3c = final.noise.symmetrizeImage(im3c,4)
    sky = galsim.Image(im3c.bounds, wcs=wcs)
    wcs.makeSkyImage(sky, 100)
    mean_sky = np.mean(sky.array)
    im3c += sky
    extra_sky = 50 - cv3c
    im3c.addNoise(galsim.PoissonNoise(sky_level=extra_sky, rng=rng))
    im3d, cv3d = galsim.config.BuildStamp(config2)
    np.testing.assert_almost_equal(cv3d, 50 + mean_sky, decimal=4)
    np.testing.assert_almost_equal(im3d.array, im3c.array, decimal=5)

    config['image']['noise']['sky_level_pixel'] = 1.e-5
    try:
        np.testing.assert_raises(RuntimeError, galsim.config.BuildStamp,config)
    except ImportError:
        pass

    # 3. CCDNoise
    #####
    config['image']['noise'] =  {
        'type' : 'CCD',
        'sky_level_pixel' : 25,
        'read_noise' : 5,
        'gain' : 1,
        'whiten' : True,
    }
    galsim.config.RemoveCurrent(config)
    im4a = im1a.copy()
    rn = math.sqrt(25-cv1a)
    rng.reset(rng1.duplicate())
    im4a.addNoise(galsim.CCDNoise(sky_level=25, read_noise=rn, gain=1, rng=rng))
    im4b, cv4b = galsim.config.BuildStamp(config)
    np.testing.assert_almost_equal(cv4b, 50, decimal=5)
    np.testing.assert_almost_equal(im4b.array, im4a.array, decimal=5)

    # Repeat with gain != 1
    config['image']['noise']['gain'] = 3.7
    galsim.config.RemoveCurrent(config)
    im5a = im1a.copy()
    rn = math.sqrt(25-cv1a * 3.7**2)
    rng.reset(rng1.duplicate())
    im5a.addNoise(galsim.CCDNoise(sky_level=25, read_noise=rn, gain=3.7, rng=rng))
    im5b, cv5b = galsim.config.BuildStamp(config)
    np.testing.assert_almost_equal(cv5b, 50, decimal=5)
    np.testing.assert_almost_equal(im5b.array, im5a.array, decimal=5)

    # And again with a non-trivial sky image
    galsim.config.RemoveCurrent(config2)
    config2['image']['noise'] = config['image']['noise']
    config2['image']['noise']['symmetrize'] = 4
    del config2['image']['noise']['whiten']
    rng.reset(1234+1)
    im5c = galsim.Image(32,32, wcs=wcs)
    im5c = final.drawImage(im5c)
    cv5c = final.noise.symmetrizeImage(im5c, 4)
    sky = galsim.Image(im5c.bounds, wcs=wcs)
    wcs.makeSkyImage(sky, 100)
    mean_sky = np.mean(sky.array)
    im5c += sky
    rn = math.sqrt(25-cv5c * 3.7**2)
    im5c.addNoise(galsim.CCDNoise(sky_level=25, read_noise=rn, gain=3.7, rng=rng))
    im5d, cv5d = galsim.config.BuildStamp(config2)
    np.testing.assert_almost_equal(cv5d, 50 + mean_sky, decimal=4)
    np.testing.assert_almost_equal(im5d.array, im5c.array, decimal=5)

    config['image']['noise']['sky_level_pixel'] = 1.e-5
    config['image']['noise']['read_noise'] = 0
    try:
        np.testing.assert_raises(RuntimeError, galsim.config.BuildStamp,config)
    except ImportError:
        pass

    # 4. COSMOSNoise
    #####
    file_name = os.path.join(galsim.meta_data.share_dir,'acs_I_unrot_sci_20_cf.fits')
    config['image']['noise'] =  {
        'type' : 'COSMOS',
        'file_name' : file_name,
        'variance' : 50,
        'whiten' : True,
    }
    galsim.config.RemoveCurrent(config)
    im6a = im1a.copy()
    rng.reset(rng1.duplicate())
    noise = galsim.getCOSMOSNoise(file_name=file_name, variance=50, rng=rng)
    noise -= galsim.UncorrelatedNoise(cv1a, rng=rng, wcs=noise.wcs)
    im6a.addNoise(noise)
    im6b, cv6b = galsim.config.BuildStamp(config)
    np.testing.assert_almost_equal(cv6b, 50, decimal=5)
    np.testing.assert_almost_equal(im6b.array, im6a.array, decimal=5)

    config['image']['noise']['variance'] = 1.e-5
    del config['_current_cn_tag']
    try:
        np.testing.assert_raises(RuntimeError, galsim.config.BuildStamp,config)
    except ImportError:
        pass
Example #7
0
def test_ccdnoise():
    """Test that the config layer CCD noise adds noise consistent with using a CCDNoise object.
    """
    import logging
    import time
    t1 = time.time()

    gain = 4
    sky = 50
    rn = 5
    size = 2048

    # Use this to turn on logging, but more info than we noramlly need, so generally leave it off.
    #logging.basicConfig(format="%(message)s", level=logging.DEBUG, stream=sys.stdout)
    #logger = logging.getLogger()
    logger = None

    config = {}
    # Either gal or psf is required, so just give it a Gaussian with 0 flux.
    config['gal'] = {'type': 'Gaussian', 'sigma': 1, 'flux': 0}
    config['image'] = {
        'type': 'Single',
        'size': size,
        'pixel_scale': 0.3,
        'random_seed':
        123  # Note: this means the seed for the noise will really be 124
        # since it is applied at the stamp level, so uses seed + obj_num
    }
    config['image']['noise'] = {
        'type': 'CCD',
        'sky_level_pixel': sky,
        'gain': gain,
        'read_noise': rn
    }
    image = galsim.config.BuildImage(config, logger=logger)

    print 'config-built image: '
    print 'mean = ', np.mean(image.array)
    print 'var = ', np.var(image.array.astype(float))
    test_var = np.var(image.array.astype(float))

    # Build another image that should have equivalent noise properties.
    image2 = galsim.Image(size, size, scale=0.3, dtype=float)
    rng = galsim.BaseDeviate(124)
    noise = galsim.CCDNoise(rng=rng, gain=gain, read_noise=rn)
    image2 += sky
    image2.addNoise(noise)
    image2 -= sky

    print 'manual sky:'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        err_msg="CCDNoise with manual sky failed variance test.")

    # So far this isn't too stringent of a test, since the noise module will use a CCDNoise
    # object for this.  In fact, it should do precisely the same calculation.
    # This should be equivalent to letting CCDNoise take the sky level:
    image2.fill(0)
    rng.reset(124)
    noise = galsim.CCDNoise(rng=rng, sky_level=sky, gain=gain, read_noise=rn)
    image2.addNoise(noise)

    print 'sky done by CCDNoise:'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        err_msg="CCDNoise using sky failed variance test.")

    # Check that the CCDNoiseBuilder calculates the same variance as CCDNoise
    var1 = noise.getVariance()
    var2 = galsim.config.noise.CCDNoiseBuilder().getNoiseVariance(
        config['image']['noise'], config)
    print 'CCDNoise variance = ', var1
    print 'CCDNoiseBuilder variance = ', var2
    np.testing.assert_almost_equal(
        var1, var2, err_msg="CCDNoiseBuidler calculates the wrong variance")

    # Finally, the config layer also includes its own manual implementation of CCD noise that
    # it uses when there is already some noise in the image.  We want to check that this is
    # consistent with the regular CCDNoise object.

    # This time, we just set the current_var to 1.e-20 to trigger the alternate path, but
    # without any real noise there yet.
    image2.fill(0)
    rng.reset(124)
    galsim.config.noise.CCDNoiseBuilder().addNoise(config['image']['noise'],
                                                   config,
                                                   image2,
                                                   rng,
                                                   current_var=1.e-20,
                                                   draw_method='fft',
                                                   logger=logger)

    print 'Use CCDNoiseBuilder with negligible current_var'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        err_msg="CCDNoise with current_var failed variance test.")

    # Here we pre-load the full read noise and tell it it's there with current_var
    image2.fill(0)
    gn = galsim.GaussianNoise(rng=rng, sigma=rn / gain)
    image2.addNoise(gn)
    galsim.config.noise.CCDNoiseBuilder().addNoise(config['image']['noise'],
                                                   config,
                                                   image2,
                                                   rng,
                                                   current_var=(rn / gain)**2,
                                                   draw_method='fft',
                                                   logger=logger)

    print 'Use CCDNoiseBuilder with current_var == read_noise'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    # So far we've done this to very high accuracy, since we've been using the same rng seed,
    # so the results should be identical, not just close.  However, hereon the values are just
    # close, since they are difference noise realizations.  So check to 1 decimal place.
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        decimal=1,
        err_msg="CCDNoise w/ current_var==rn failed variance test.")

    # Now we pre-load part of the read-noise, but not all.  It should add the rest as read_noise.
    image2.fill(0)
    gn = galsim.GaussianNoise(rng=rng, sigma=0.5 * rn / gain)
    image2.addNoise(gn)
    galsim.config.noise.CCDNoiseBuilder().addNoise(config['image']['noise'],
                                                   config,
                                                   image2,
                                                   rng,
                                                   current_var=(0.5 * rn /
                                                                gain)**2,
                                                   draw_method='fft',
                                                   logger=logger)

    print 'Use CCDNoiseBuilder with current_var < read_noise'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        decimal=1,
        err_msg="CCDNoise w/ current_var < rn failed variance test.")

    # Last, we go beyond the read-noise, so it should remove some of the sky level to compensate.
    image2.fill(0)
    gn = galsim.GaussianNoise(rng=rng, sigma=2. * rn / gain)
    image2.addNoise(gn)
    galsim.config.noise.CCDNoiseBuilder().addNoise(config['image']['noise'],
                                                   config,
                                                   image2,
                                                   rng,
                                                   current_var=(2. * rn /
                                                                gain)**2,
                                                   draw_method='fft',
                                                   logger=logger)

    print 'Use CCDNoiseBuilder with current_var > read_noise'
    print 'mean = ', np.mean(image2.array)
    print 'var = ', np.var(image2.array)
    np.testing.assert_almost_equal(
        np.var(image2.array),
        test_var,
        decimal=1,
        err_msg="CCDNoise w/ current_var > rn failed variance test.")

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Example #8
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles. We chose parametric fits since
        these are required for chromatic galaxies (ones with filter response included)
      - The real galaxy images include some initial correlated noise from the original HST
        observation, which would need to be whitened. But we are using parametric galaxies, 
        so this isn't a concern.
    """

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

    M = MPIHelper()

    # Define some parameters we'll use below.
    sbparams = SuperBITParameters(argv=argv)

    # Set up the NFWHalo:
    nfw = galsim.NFWHalo(mass=sbparams.mass,
                         conc=sbparams.nfw_conc,
                         redshift=sbparams.nfw_z_halo,
                         omega_m=sbparams.omega_m,
                         omega_lam=sbparams.omega_lam)

    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.
    cosmos_cat = galsim.COSMOSCatalog(sbparams.cat_file_name,
                                      dir=sbparams.cosmosdir)
    fitcat = Table.read(
        os.path.join(os.path.join(sbparams.cosmosdir, sbparams.fit_file_name)))
    logger.info('Read in %d galaxies from catalog and associated fit info',
                cosmos_cat.nobjects)

    cluster_cat = galsim.COSMOSCatalog(sbparams.cluster_cat_name)
    print('Read in %d cluster galaxies from catalog' % cosmos_cat.nobjects)

    ### Now create PSF. First, define Zernicke polynomial component
    ### note: aberrations were definined for lam = 550, and close to the
    ### center of the camera. The PSF degrades at the edge of the FOV
    lam_over_diam = sbparams.lam * 1.e-9 / sbparams.tel_diam  # radians
    lam_over_diam *= 206265.

    aberrations = numpy.zeros(38)  # Set the initial size.
    aberrations[0] = 0.  # First entry must be zero
    aberrations[1] = -0.00305127
    aberrations[4] = -0.02474205  # Noll index 4 = Defocus
    aberrations[11] = -0.01544329  # Noll index 11 = Spherical
    aberrations[22] = 0.00199235
    aberrations[26] = 0.00000017
    aberrations[37] = 0.00000004
    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)

    # will store the Zernicke component of the PSF
    optics = galsim.OpticalPSF(lam=sbparams.lam,
                               diam=sbparams.tel_diam,
                               obscuration=sbparams.obscuration,
                               nstruts=sbparams.nstruts,
                               strut_angle=sbparams.strut_angle,
                               strut_thick=sbparams.strut_thick,
                               aberrations=aberrations)

    logger.info('Made telescope PSF profile')

    # load SuperBIT bandpass
    bandpass = galsim.Bandpass(sbparams.bp_file,
                               wave_type='nm',
                               blue_limit=310,
                               red_limit=1100)

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ### WITHIN EACH PSF, ITERATE n TIMES TO MAKE n SEPARATE IMAGES
    ###

    #all_psfs=glob.glob(sbparams.psf_path+"/*121*.psf")
    logger.info('Beginning loop over jitter/optical psfs')

    for im in np.arange(1):

        for i in numpy.arange(1, sbparams.nexp + 1):
            # get MPI processes in sync at start of each image
            M.barrier()
            logger.info('Beginning loop %d' % i)

            #rng = galsim.BaseDeviate(sbparams.noise_seed+i)

            try:
                timescale = str(sbparams.exp_time)
                outname = ''.join(
                    ['superbit_gaussPSF_',
                     str(i).zfill(3), '.fits'])
                truth_file_name = ''.join([
                    sbparams.outdir, '/truth_gaussPSF_',
                    str(i).zfill(3), '.dat'
                ])
                file_name = os.path.join(sbparams.outdir, outname)

            except galsim.errors.GalSimError:
                print("naming failed, check path")
                pdb.set_trace()

            # Setting up a truth catalog
            names = [
                'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas',
                'g2_meas', 'nfw_mu', 'redshift', 'flux', 'truth_fwhm',
                'truth_mom'
            ]
            types = [
                int, float, float, float, float, float, float, float, float,
                float, float, float
            ]
            truth_catalog = galsim.OutputCatalog(names, types)

            # Set up the image:
            full_image = galsim.ImageF(sbparams.image_xsize,
                                       sbparams.image_ysize)
            sky_level = sbparams.exp_time * sbparams.sky_bkg
            # fill with sky_level moved until after MPI results summed
            full_image.fill(sky_level)
            full_image.setOrigin(0, 0)

            # We keep track of how much noise is already in the image from the RealGalaxies.
            noise_image = galsim.ImageF(sbparams.image_xsize,
                                        sbparams.image_ysize)
            noise_image.setOrigin(0, 0)

            # If you wanted to make a non-trivial WCS system, could set theta to a non-zero number
            theta = 0.0 * galsim.degrees
            dudx = numpy.cos(theta) * sbparams.pixel_scale
            dudy = -numpy.sin(theta) * sbparams.pixel_scale
            dvdx = numpy.sin(theta) * sbparams.pixel_scale
            dvdy = numpy.cos(theta) * sbparams.pixel_scale
            image_center = full_image.true_center
            affine = galsim.AffineTransform(dudx,
                                            dudy,
                                            dvdx,
                                            dvdy,
                                            origin=full_image.true_center)
            sky_center = galsim.CelestialCoord(ra=sbparams.center_ra,
                                               dec=sbparams.center_dec)

            wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
            full_image.wcs = wcs

            # Now let's read in the PSFEx PSF model.  We read the image directly into an
            # InterpolatedImage GSObject, so we can manipulate it as needed
            psf_wcs = wcs
            #psf = galsim.des.DES_PSFEx(psf_filen,wcs=psf_wcs)
            logger.info('Constructed PSF object from PSFEx file')

            #####
            ## Loop over galaxy objects:
            #####

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nobj)
            for k in range(local_start, local_end):
                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(sbparams.galobj_seed + k + 1)

                try:
                    # make single galaxy object
                    stamp, truth = make_a_galaxy(ud=ud,
                                                 wcs=wcs,
                                                 affine=affine,
                                                 fitcat=fitcat,
                                                 cosmos_cat=cosmos_cat,
                                                 optics=optics,
                                                 nfw=nfw,
                                                 bandpass=bandpass,
                                                 sbparams=sbparams)
                    # Find the overlapping bounds:
                    bounds = stamp.bounds & full_image.bounds

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

                    # noise_image[bounds] += truth.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 positioned relative to center t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)
                except galsim.errors.GalSimError:
                    logger.info('Galaxy %d has failed, skipping...', k)

            #####
            ### Inject cluster galaxy objects:
            #####

            center_coords = galsim.CelestialCoord(sbparams.center_ra,
                                                  sbparams.center_dec)
            centerpix = wcs.toImage(center_coords)

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nclustergal)
            for k in range(local_start, local_end):

                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(sbparams.cluster_seed + k + 1)

                try:
                    # make single galaxy object
                    cluster_stamp, truth = make_cluster_galaxy(
                        ud=ud,
                        wcs=wcs,
                        affine=affine,
                        centerpix=centerpix,
                        cluster_cat=cluster_cat,
                        optics=optics,
                        bandpass=bandpass,
                        sbparams=sbparams)
                    # Find the overlapping bounds:
                    bounds = cluster_stamp.bounds & full_image.bounds

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

                    #noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += cluster_stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Cluster galaxy %d positioned relative to center t=%f s',
                        k, tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)
                except galsim.errors.GalSimError:
                    logger.info('Cluster galaxy %d has failed, skipping...', k)

            #####
            ### Now repeat process for stars!
            #####

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nstars)
            for k in range(local_start, local_end):
                time1 = time.time()
                ud = galsim.UniformDeviate(sbparams.stars_seed + k + 1)

                star_stamp, truth = make_a_star(ud=ud,
                                                wcs=wcs,
                                                affine=affine,
                                                optics=optics,
                                                sbparams=sbparams)
                bounds = star_stamp.bounds & full_image.bounds

                # Add the stamp to the full image.
                try:
                    full_image[bounds] += star_stamp[bounds]

                    time2 = time.time()
                    tot_time = time2 - time1

                    logger.info(
                        'Star %d: positioned relative to center, t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(star_stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)

                except galsim.errors.GalSimError:
                    logger.info('Star %d has failed, skipping...', k)

            # Gather results from MPI processes, reduce to single result on root
            # Using same names on left and right sides is hiding lots of MPI magic
            full_image = M.gather(full_image)
            truth_catalog = M.gather(truth_catalog)
            #noise_image = M.gather(noise_image)
            if M.is_mpi_root():
                full_image = reduce(combine_images, full_image)
                truth_catalog = reduce(combine_catalogs, truth_catalog)
                #noise_image = reduce(combine_images, noise_image)
            else:
                # do the adding of noise and writing to disk entirely on root
                # root and the rest meet again at barrier at start of loop
                continue

            # The first thing to do is to make the Gaussian noise uniform across the whole image.
            # If real-type COSMOS galaxies are used, the noise across the image won't be uniform. Since this code is
            # using parametric-type galaxies, the following section is commented out.
            #         max_current_variance = numpy.max(noise_image.array)
            #         noise_image = max_current_variance - noise_image

            # The first thing to do is to make the Gaussian noise uniform across the whole image.

            # Add dark current

            logger.info('Adding Dark current')

            dark_noise = sbparams.dark_current * sbparams.exp_time
            # np.random.normal(
            #     sbparams.dark_current, sbparams.dark_current_std,
            #     size=(sbparams.image_ysize, sbparams.image_xsize)) * sbparams.exp_time
            # dark_noise = np.clip(dark_noise, a_min=0, a_max=2**16)

            full_image += dark_noise

            # Add ccd noise; removed rng in noise

            logger.info('Adding CCD noise')
            noise = galsim.CCDNoise(sky_level=0,
                                    gain=1 / sbparams.gain,
                                    read_noise=sbparams.read_noise)
            full_image.addNoise(noise)

            logger.debug('Added noise to final output image')
            if not os.path.exists(os.path.dirname(file_name)):
                os.makedirs(os.path.dirname(file_name))
            full_image.write(file_name)

            # Write truth catalog to file.
            truth_catalog.write(truth_file_name)
            logger.info('Wrote image to %r', file_name)

            logger.info(' ')
            logger.info('completed run %d', im)
            i = i + 1
            logger.info(' ')

        logger.info(' ')
        logger.info('completed all images')
        logger.info(' ')
Example #9
0
def AddNoiseCCD(noise, config, draw_method, rng, im, weight_im, current_var,
                sky, logger):

    # This process goes a lot like the Poisson routine.  There are just two differences.
    # The Poisson noise is in the electron, not ADU, and now we allow for a gain = e-/ADU,
    # so we need to account for that properly.  And we also allow for an additional Gaussian
    # read noise.

    # Get how much extra sky to assume from the image.noise attribute.
    opt = {'gain': float, 'read_noise': float}
    # The noise sky_level is only required here if the image doesn't have any.
    if sky:
        opt['sky_level'] = float
        opt['sky_level_pixel'] = float
        single = []
    else:
        single = [{'sky_level': float, 'sky_level_pixel': float}]
    params = galsim.config.GetAllParams(noise,
                                        'noise',
                                        config,
                                        opt=opt,
                                        single=single,
                                        ignore=noise_ignore)[0]
    gain = params.get('gain', 1.0)
    read_noise = params.get('read_noise', 0.0)
    read_noise_var = read_noise**2
    if 'sky_level' in params:
        if 'sky_level_pixel' in params:
            raise AttributeError(
                "Only one of sky_level and sky_level_pixel is allowed for "
                "noise.type = CCD")
        sky_level = params['sky_level']
        if im.wcs.isUniform():
            extra_sky = sky_level * im.wcs.pixelArea()
        elif 'image_pos' in config:
            extra_sky = sky_level * im.wcs.pixelArea(config['image_pos'])
        else:
            extra_sky = galsim.Image(im.bounds, wcs=im.wcs)
            im.wcs.makeSkyImage(extra_sky, sky_level)
    elif 'sky_level_pixel' in params:
        extra_sky = params['sky_level_pixel']
    else:
        extra_sky = 0.

    # If we are saving the noise level in a weight image, do that now.
    if weight_im:
        # Check if a weight image should include the object variance.
        # Note: For the phot case, we don't actually have an exact value for the variance in each
        # pixel, but the drawn image before adding the Poisson noise is our best guess for the
        # variance from the object's flux, so if we want the object variance included, this is
        # still the best we can do.
        include_obj_var = False
        if ('output' in config and 'weight' in config['output']
                and 'include_obj_var' in config['output']['weight']):
            include_obj_var = galsim.config.ParseValue(
                config['output']['weight'], 'include_obj_var', config, bool)[0]
        if include_obj_var:
            # The image right now has the object variance in each pixel.  So before going on with
            # the noise, copy these over to the weight image.  (We invert this later...)
            weight_im.copyFrom(im)

            # Account for the gain and read noise
            if gain != 1.0:
                import math
                weight_im /= math.sqrt(gain)
            if read_noise != 0.0:
                weight_im += read_noise_var
        else:
            # Otherwise, just add in the current sky noise:
            if sky or read_noise != 0.0:
                weight_im += sky / gain + read_noise_var

        # And add in the extra sky noise:
        if extra_sky: weight_im += extra_sky

    # If we already have some variance in the image (from whitening), then we try to subtract it
    # from the read noise if possible.  If now, we subtract the rest off of the sky level.  It's
    # not precisely accurate, since the existing variance is Gaussian, rather than Poisson, but
    # it's the best we can do.
    if current_var:
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            test = ((sky + extra_sky).image.array / gain + read_noise_var <
                    current_var).any()
        else:
            test = (sky + extra_sky) / gain + read_noise_var < current_var
        if test:
            raise RuntimeError(
                "Whitening already added more noise than requested CCD noise.")
        if read_noise_var >= current_var:
            # First try to take away from the read_noise, since this one is actually Gaussian.
            import math
            read_noise_var -= current_var
            read_noise = math.sqrt(read_noise_var)
        else:
            # Take read_noise down to zero, since already have at least that much already.
            current_var -= read_noise_var
            read_noise = 0
            read_noise_var = 0
            # Take the rest away from the sky level
            extra_sky -= current_var * gain

    # At this point, there is a slight difference between fft and phot. For photon shooting, the
    # galaxy already has Poisson noise, so we want to make sure not to add that again!
    if draw_method == 'phot':
        # Add in the noise from the sky.
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            noise_im = sky + extra_sky
            if gain != 1.0: noise_im *= gain
            noise_im.addNoise(galsim.PoissonNoise(rng))
            if gain != 1.0: noise_im /= gain
            if sky:
                noise_im -= sky
            if extra_sky:
                noise_im -= extra_sky
            # noise_im should now have zero mean, but with the noise of the total sky level.
            im += noise_im
        else:
            total_sky = sky + extra_sky
            if total_sky > 0.:
                if gain != 1.0: im *= gain
                im.addNoise(
                    galsim.DeviateNoise(
                        galsim.PoissonDeviate(rng, mean=total_sky * gain)))
                if gain != 1.0: im /= gain
                im -= total_sky
        # And add the read noise
        if read_noise != 0.:
            im.addNoise(galsim.GaussianNoise(rng, sigma=read_noise))
    else:
        # Do the normal CCDNoise calculation.
        im += extra_sky
        im.addNoise(galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))
        im -= extra_sky

    if logger:
        logger.debug(
            'image %d, obj %d: Added CCD noise with sky = %f, ' +
            'gain = %f, read_noise = %f', config['image_num'],
            config['obj_num'], sky, gain, read_noise)
Example #10
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.
    """
    # We do some fancier logging for demo3, just to demonstrate that we can:
    # - we log to both stdout and to a log file
    # - the log file has a lot more (mostly redundant) information
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    if not os.path.isdir('output'):
        os.mkdir('output')
    logFile = logging.FileHandler(os.path.join("output", "script3.log"))
    logFile.setFormatter(logging.Formatter("%(name)s[%(levelname)s] %(asctime)s: %(message)s"))
    logging.getLogger("demo3").addHandler(logFile)
    logger = logging.getLogger("demo3") 

    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_re = 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  

    logger.info('Starting demo script 3 using:')
    logger.info('    - Galaxy is bulge plus disk, flux = %.1e',gal_flux)
    logger.info('       - Bulge is Sersic (n = %.1f, re = %.2f), frac = %.1f',
                bulge_n,bulge_re,bulge_frac)
    logger.info('       - Disk is Sersic (n = %.1f, re = %.2f), frac = %.1f',
                disk_n,disk_re,1-bulge_frac)
    logger.info('       - Shape is q,beta (%.2f,%.2f deg)', gal_q, gal_beta)
    logger.info('    - Atmospheric PSF is Kolmogorov with fwhm = %.2f',atmos_fwhm)
    logger.info('       - Shape is e,beta (%.2f,%.2f rad)', atmos_e, atmos_beta)
    logger.info('    - Optical PSF has defocus = %.2f, astigmatism = (%.2f,%.2f),',
                opt_defocus, opt_a1, opt_a2)
    logger.info('          coma = (%.2f,%.2f), lambda = %.0f nm, D = %.1f m', 
                opt_c1, opt_c2, lam, tel_diam)
    logger.info('          obscuration linear size = %.1f',opt_obscuration)
    logger.info('    - pixel scale = %.2f,',pixel_scale)
    logger.info('    - WCS distortion = (%.2f,%.2f),',wcs_g1,wcs_g2)
    logger.info('    - Poisson noise (sky level = %.1e, gain = %.1f).',sky_level, gain)
    logger.info('    - Gaussian read noise (sigma = %.2f).',read_noise)

    # Initialize the (pseudo-)random number generator that we will be using below.
    rng = galsim.BaseDeviate(random_seed)
 
    # Define the galaxy profile.
    bulge = galsim.Sersic(bulge_n, half_light_radius=bulge_re)
    disk = galsim.Sersic(disk_n, half_light_radius=disk_re)

    # 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.setFlux(bulge_frac) , disk.setFlux(1-bulge_frac) ])
    # Both syntaxes work with more than two summands as well.

    # Set the overall flux of the combined object.
    gal.setFlux(gal_flux)
    # Since the total flux of the components was 1, we could also have written:
    #   gal *= gal_flux
    # The setFlux method will always set the flux to the given value, while `gal *= flux`
    # will multiply whatever the current flux is by the given factor.

    # Set the shape of the galaxy according to axis ratio and position angle
    # Note: All angles in GalSim must have explicit units.  Options are:
    #       galsim.radians
    #       galsim.degrees
    #       galsim.arcmin
    #       galsim.arcsec
    #       galsim.hours
    gal_shape = galsim.Shear(q=gal_q, beta=gal_beta*galsim.degrees)
    gal.applyShear(gal_shape)
    logger.debug('Made galaxy profile')

    # 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.applyShear(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, which needs to be in arcsec,
    # so do the calculation:
    lam_over_diam = lam * 1.e-9 / tel_diam # radians
    lam_over_diam *= 206265  # 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')

    # Next we will convolve the psf and galaxy profiles. 
    # Note: it's important to make sure the physical effects happen in the right order.
    # The PSF and galaxy profiles should be convolved before the optical (WCS) distortion.  
    # Then the pixelization is applied after that.
    psf = galsim.Convolve([atmos, optics])
    nopix = galsim.Convolve([psf, gal])
    
    # Now we can apply the WCS distortion (specified as g1,g2 in this case).
    # We may eventually have a somewhat more seamless way to handle things like a WCS
    # that would potentially vary across the image and include more than just a distortion
    # term.  But for now, we just apply a given distortion to the unpixellated profile.
    nopix.applyShear(g1=wcs_g1, g2=wcs_g2)
    psf.applyShear(g1=wcs_g1, g2=wcs_g2)
    logger.debug('Applied WCS distortion')

    # While Pixels are usually square, you can make them rectangular if you want
    # by specifying xw and yw separately.  Here they are still the same value, but if you
    # wanted non-square pixels, just specify a different value for xw and yw.
    pix = galsim.Pixel(xw=pixel_scale, yw=pixel_scale)
    logger.debug('Made pixel profile')

    # The final profile is the convolution of the WCS-sheared (psf+gal) profile with the pixel.
    final = galsim.Convolve([nopix, pix])
    final_epsf = galsim.Convolve([psf, pix])
    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 draw 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 a particular pixel scale.
    final.draw(image=image, dx=pixel_scale)

    # Also draw the effective PSF by itself and the optical PSF component alone.
    image_epsf = galsim.ImageF(image_size, image_size)
    final_epsf.draw(image_epsf, dx=pixel_scale)
    # Note: we 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.
    image_opticalpsf = optics.draw(dx=lam_over_diam/2.)
    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 image to a file
    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)

    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 (RHS) term initially followed by the
    # first (LHS).
    # So wcs needs to be LHS and galaxy shape RHS.
    total_shape = galsim.Shear(g1=wcs_g1, g2=wcs_g2) + gal_shape
    logger.info('    e1, e2 = %.3f, %.3f', total_shape.e1, total_shape.e2)
Example #11
0
def AddNoiseFFT(im, weight_im, noise, base, rng, sky_level_pixel, logger=None):
    """
    Add noise to an image according to the noise specifications in the noise dict
    appropriate for an image that has been drawn using the FFT method.
    """
    if not isinstance(noise, dict):
        raise AttributeError("image.noise is not a dict.")

    if 'type' not in noise:
        noise['type'] = 'Poisson'  # Default is Poisson
    type = noise['type']

    # First add the background sky level, if provided
    if sky_level_pixel:
        im += sky_level_pixel

    # Check if a weight image should include the object variance.
    if weight_im:
        include_obj_var = False
        if ('output' in base and 'weight' in base['output']
                and 'include_obj_var' in base['output']['weight']):
            include_obj_var = galsim.config.ParseValue(
                base['output']['weight'], 'include_obj_var', base, bool)[0]

    # Then add the correct kind of noise
    if type == 'Gaussian':
        single = [{'sigma': float, 'variance': float}]
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            single=single)[0]

        if 'sigma' in params:
            sigma = params['sigma']
        else:
            import math
            sigma = math.sqrt(params['variance'])
        im.addNoise(galsim.GaussianNoise(rng, sigma=sigma))

        if weight_im:
            weight_im += sigma * sigma
        if logger:
            logger.debug('   Added Gaussian noise with sigma = %f', sigma)

    elif type == 'Poisson':
        opt = {}
        single = []
        if sky_level_pixel:
            # The noise sky_level is only required here if the image doesn't have any.
            opt['sky_level'] = float
            opt['sky_level_pixel'] = float
        else:
            single = [{'sky_level': float, 'sky_level_pixel': float}]
            sky_level_pixel = 0.
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            opt=opt,
                                            single=single)[0]
        if 'sky_level' in params and 'sky_level_pixel' in params:
            raise AttributeError(
                "Only one of sky_level and sky_level_pixel is allowed for "
                "noise.type = %s" % type)
        extra_sky_level_pixel = 0.
        if 'sky_level' in params:
            pixel_scale = im.scale
            extra_sky_level_pixel = params['sky_level'] * pixel_scale**2
        if 'sky_level_pixel' in params:
            extra_sky_level_pixel = params['sky_level_pixel']
        sky_level_pixel += extra_sky_level_pixel

        if weight_im:
            import math
            if include_obj_var:
                # The image right now has the variance in each pixel.  So before going on with the
                # noise, copy these over to the weight image.  (We invert this later...)
                weight_im.copyFrom(im)
            else:
                # Otherwise, just add the sky and read_noise:
                weight_im += sky_level_pixel

        im.addNoise(galsim.PoissonNoise(rng, sky_level=extra_sky_level_pixel))
        if logger:
            logger.debug('   Added Poisson noise with sky_level_pixel = %f',
                         sky_level_pixel)

    elif type == 'CCD':
        opt = {'gain': float, 'read_noise': float}
        single = []
        if sky_level_pixel:
            # The noise sky_level is only required here if the image doesn't have any.
            opt['sky_level'] = float
            opt['sky_level_pixel'] = float
        else:
            single = [{'sky_level': float, 'sky_level_pixel': float}]
            sky_level_pixel = 0.
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            opt=opt,
                                            single=single)[0]
        gain = params.get('gain', 1.0)
        read_noise = params.get('read_noise', 0.0)
        if 'sky_level' in params and 'sky_level_pixel' in params:
            raise AttributeError(
                "Only one of sky_level and sky_level_pixel is allowed for "
                "noise.type = %s" % type)
        extra_sky_level_pixel = 0.
        if 'sky_level' in params:
            pixel_scale = im.scale
            extra_sky_level_pixel = params['sky_level'] * pixel_scale**2
        if 'sky_level_pixel' in params:
            extra_sky_level_pixel = params['sky_level_pixel']
        sky_level_pixel += extra_sky_level_pixel

        if weight_im:
            import math
            if include_obj_var:
                # The image right now has the variance in each pixel.  So before going on with the
                # noise, copy these over to the weight image and invert.
                weight_im.copyFrom(im)
                if gain != 1.0:
                    weight_im /= math.sqrt(gain)
                if read_noise != 0.0:
                    weight_im += read_noise * read_noise
            else:
                # Otherwise, just add the sky and read_noise:
                weight_im += sky_level_pixel / gain + read_noise * read_noise

        #print 'CCDNoise with ',extra_sky_level_pixel,gain,read_noise
        im.addNoise(
            galsim.CCDNoise(rng,
                            sky_level=extra_sky_level_pixel,
                            gain=gain,
                            read_noise=read_noise))
        if logger:
            logger.debug(
                '   Added CCD noise with sky_level_pixel = %f, ' +
                'gain = %f, read_noise = %f', sky_level_pixel, gain,
                read_noise)

    elif type == 'COSMOS':
        req = {'file_name': str}
        opt = {'dx_cosmos': float, 'variance': float}

        kwargs = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            req=req,
                                            opt=opt)[0]

        # Build the correlated noise
        cn = galsim.correlatednoise.getCOSMOSNoise(rng, **kwargs)
        im.addNoise(cn)

        # Then add the variance to the weight image, using the zero-lag correlation function value
        if weight_im:
            weight_im += cn.getVariance()
        if logger:
            logger.debug('   Added COSMOS correlated noise with variance = %f',
                         cn.getVariance())

    else:
        raise AttributeError("Invalid type %s for noise" % type)
Example #12
0
def main(argv):
    # For the file names, I pick a particular exposure.  The directory structure corresponds
    # to where the files are stored on folio at UPenn.

    root = 'DECam_00154912'

    # Directories in the Galsim repo
    img_dir = 'des_data'
    wl_dir = 'des_data'

    # Directories on Mike's laptop
    #img_dir = '/Users/Mike/Astro/des/SV/DECam_00154912_wl'
    #wl_dir = '/Users/Mike/Astro/des/SV/DECam_00154912_wl'

    # Directories on folio
    #img_dir = '/data3/DECAM/SV/DECam_154912'
    #wl_dir = '/data3/DECAM/wl/DECam_00154912_wl'

    # Set which chips to run on
    first_chip = 1
    last_chip = 62
    #first_chip = 12
    #last_chip = 12

    out_dir = 'output'

    # The random seed, so the results are deterministic
    random_seed = 1339201

    x_col = 'X_IMAGE'
    y_col = 'Y_IMAGE'
    flux_col = 'FLUX_AUTO'
    flag_col = 'FLAGS'

    xsize_key = 'NAXIS1'
    ysize_key = 'NAXIS2'
    pixel_scale_key = 'PIXSCAL1'
    sky_level_key = 'SKYBRITE'
    sky_sigma_key = 'SKYSIGMA'

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

    for chipnum in range(first_chip, last_chip + 1):
        print 'Start chip ', chipnum

        # Setup the file names
        image_file = '%s_%02d.fits.fz' % (root, chipnum)
        cat_file = '%s_%02d_cat.fits' % (root, chipnum)
        psfex_file = '%s_%02d_psfcat.psf' % (root, chipnum)
        fitpsf_file = '%s_%02d_fitpsf.fits' % (root, chipnum)
        psfex_image_file = '%s_%02d_psfex_image.fits' % (root, chipnum)
        fitpsf_image_file = '%s_%02d_fitpsf_image.fits' % (root, chipnum)

        # Get some parameters about the image from the data image header information
        image_header = galsim.FitsHeader(image_file, dir=img_dir)
        xsize = image_header[xsize_key]
        ysize = image_header[ysize_key]
        pixel_scale = image_header[pixel_scale_key]
        sky_sigma = image_header[
            sky_sigma_key]  # This is sqrt(variance) / pixel
        sky_level = image_header[sky_level_key]  # This is in ADU / pixel
        gain = sky_level / sky_sigma**2  # an approximation, since gain is missing.

        # Setup the images:
        psfex_image = galsim.ImageF(xsize, ysize)
        psfex_image.scale = pixel_scale
        fitpsf_image = galsim.ImageF(xsize, ysize)
        fitpsf_image.scale = pixel_scale

        # Read the other input files
        cat = galsim.Catalog(cat_file, hdu=2, dir=img_dir)
        psfex = galsim.des.DES_PSFEx(psfex_file, dir=wl_dir)
        fitpsf = galsim.des.DES_Shapelet(fitpsf_file, dir=wl_dir)

        nobj = cat.nobjects
        print 'Catalog has ', nobj, ' objects'

        for k in range(nobj):
            # The usual random number generator using a different seed for each galaxy.
            # I'm not actually using the rng for object creation (everything comes from the
            # input files), but the rng that matches the config version is here just in case.
            rng = galsim.BaseDeviate(random_seed + k)

            # Skip objects with a non-zero flag
            flag = cat.getInt(k, flag_col)
            if flag: continue

            # Get the position from the galaxy catalog
            x = cat.getFloat(k, x_col)
            y = cat.getFloat(k, y_col)
            ix = int(math.floor(x + 0.5))
            iy = int(math.floor(y + 0.5))
            dx = x - ix
            dy = y - iy
            image_pos = galsim.PositionD(x, y)
            print '    pos = ', image_pos

            # Also get the flux of the galaxy from the catalog
            flux = cat.getFloat(k, flux_col)

            # Define the pixel
            pix = galsim.Pixel(pixel_scale)

            # First do the PSFEx image:
            if True:
                # Define the PSF profile
                psf = psfex.getPSF(image_pos, pixel_scale)
                psf.setFlux(flux)

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

                # Apply partial-pixel shift
                final.applyShift(dx * pixel_scale, dy * pixel_scale)

                # Draw the postage stamp image
                stamp = final.draw(dx=pixel_scale)

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

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

            # Next do the ShapeletPSF image:
            # If the position is not within the interpolation bounds, fitpsf will
            # raise an exception telling us to skip this object.  Easier to check here.
            if fitpsf.bounds.includes(image_pos):
                # Define the PSF profile
                psf = fitpsf.getPSF(image_pos)
                psf.setFlux(flux)

                # Galsim doesn't have WCS functionality yet.
                # But for the shapelet PSF, it is important, since it really describes the
                # PSF in sky coordinates, not pixel coordinates.  But to first order,
                # the DES WCS is 90 degrees rotated from the sky, so for now, just apply
                # a 90 degree rotation to get the images to look approximately correct.
                # Eventually, we'll want to have a DES_WCS that can read the full WCS from
                # the fits header and account for all of the field distortion correctly.
                psf.applyRotation(-90 * galsim.degrees)

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

                # Apply partial-pixel shift
                final.applyShift(dx * pixel_scale, dy * pixel_scale)

                # Draw the postage stamp image
                stamp = final.draw(dx=pixel_scale)

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

                # Find overlapping bounds
                bounds = stamp.bounds & fitpsf_image.bounds
                fitpsf_image[bounds] += stamp[bounds]
            else:
                pass
                #print '...not in fitpsf.bounds'

        # Add background level
        psfex_image += sky_level
        fitpsf_image += sky_level

        # Add noise
        rng = galsim.BaseDeviate(random_seed + nobj)
        noise = galsim.CCDNoise(rng, gain=gain)
        psfex_image.addNoise(noise)
        # Reset the random seed to match the action of the yaml version
        # Note: the different between seed and reset matters here.
        # reset would sever the connection between this rng instance and the one stored in noise.
        # seed changes the seed while keeping the connection between them.
        rng.seed(random_seed + nobj)
        fitpsf_image.addNoise(noise)

        # Now write the images to disk.
        psfex_image.write(psfex_image_file, dir=out_dir)
        fitpsf_image.write(fitpsf_image_file, dir=out_dir)
        print 'Wrote images to %s and %s' % (os.path.join(
            out_dir, psfex_image_file), os.path.join(out_dir,
                                                     fitpsf_image_file))
Example #13
0
def test_ccdnoise():
    """Test CCD Noise generator
    """
    # Start with some regression tests where we have known values that we expect to generate:

    types = (np.int16, np.int32, np.float32, np.float64)
    typestrings = ("S", "I", "F", "D")

    testseed = 1000
    gain = 3.
    read_noise = 5.
    sky = 50

    # Tabulated results for the above settings and testseed value.
    cResultS = np.array([[44, 47], [50, 49]], dtype=np.int16)
    cResultI = np.array([[44, 47], [50, 49]], dtype=np.int32)
    cResultF = np.array(
        [[44.45332718, 47.79725266], [50.67744064, 49.58272934]],
        dtype=np.float32)
    cResultD = np.array([[44.453328440057618, 47.797254142519577],
                         [50.677442088335162, 49.582730949808081]],
                        dtype=np.float64)

    for i in range(4):
        prec = eval("precision" + typestrings[i])
        cResult = eval("cResult" + typestrings[i])

        rng = galsim.BaseDeviate(testseed)
        ccdnoise = galsim.CCDNoise(rng, gain=gain, read_noise=read_noise)
        testImage = galsim.Image((np.zeros((2, 2)) + sky).astype(types[i]))
        ccdnoise.applyTo(testImage)
        np.testing.assert_array_almost_equal(
            testImage.array,
            cResult,
            prec,
            err_msg="Wrong CCD noise random sequence generated for Image" +
            typestrings[i] + ".")

        # Check that reseeding the rng reseeds the internal deviate in CCDNoise
        rng.seed(testseed)
        testImage.fill(sky)
        ccdnoise.applyTo(testImage)
        np.testing.assert_array_almost_equal(
            testImage.array,
            cResult,
            prec,
            err_msg="Wrong CCD noise random sequence generated for Image" +
            typestrings[i] + " after seed")

        # Check using addNoise
        rng.seed(testseed)
        testImage.fill(sky)
        testImage.addNoise(ccdnoise)
        np.testing.assert_array_almost_equal(
            testImage.array,
            cResult,
            prec,
            err_msg="Wrong CCD noise random sequence generated for Image" +
            typestrings[i] + " using addNoise")

        # Test filling an image with Fortran ordering
        rng.seed(testseed)
        testImageF = galsim.Image(np.zeros((2, 2)).T, dtype=types[i])
        testImageF.fill(sky)
        testImageF.addNoise(ccdnoise)
        np.testing.assert_array_almost_equal(
            testImageF.array,
            cResult,
            prec,
            err_msg="Wrong CCD noise generated for Fortran-ordered Image" +
            typestrings[i])

        # Now include sky_level in ccdnoise
        rng.seed(testseed)
        ccdnoise = galsim.CCDNoise(rng,
                                   sky_level=sky,
                                   gain=gain,
                                   read_noise=read_noise)
        testImage.fill(0)
        ccdnoise.applyTo(testImage)
        np.testing.assert_array_almost_equal(
            testImage.array,
            cResult - sky,
            prec,
            err_msg="Wrong CCD noise random sequence generated for Image" +
            typestrings[i] + " with sky_level included in noise")

        rng.seed(testseed)
        testImage.fill(0)
        testImage.addNoise(ccdnoise)
        np.testing.assert_array_almost_equal(
            testImage.array,
            cResult - sky,
            prec,
            err_msg="Wrong CCD noise random sequence generated for Image" +
            typestrings[i] +
            " using addNoise with sky_level included in noise")

    # Check CCDNoise variance:
    var1 = sky / gain + (read_noise / gain)**2
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        var1,
        precision,
        err_msg="CCDNoise getVariance returns wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level,
        sky,
        precision,
        err_msg="CCDNoise sky_level returns wrong value")
    np.testing.assert_almost_equal(ccdnoise.gain,
                                   gain,
                                   precision,
                                   err_msg="CCDNoise gain returns wrong value")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        read_noise,
        precision,
        err_msg="CCDNoise read_noise returns wrong value")

    # Check that the noise model really does produce this variance.
    # NB. If default float32 is used here, older versions of numpy will compute the variance
    # in single precision, and with 2048^2 values, the final answer comes out significantly
    # wrong (19.33 instead of 19.42, which gets compared to the nominal value of 19.44).
    big_im = galsim.Image(2048, 2048, dtype=float)
    big_im.addNoise(ccdnoise)
    var = np.var(big_im.array)
    print('variance = ', var)
    print('getVar = ', ccdnoise.getVariance())
    np.testing.assert_almost_equal(
        var,
        ccdnoise.getVariance(),
        1,
        err_msg='Realized variance for CCDNoise did not match getVariance()')

    # Check that CCDNoise adds to the image, not overwrites the image.
    gal = galsim.Exponential(half_light_radius=2.3, flux=0.3)
    # Note: again, flux/size^2 needs to be << sky_level or it will mess up the statistics.
    gal.drawImage(image=big_im)
    big_im.addNoise(ccdnoise)
    gal.withFlux(-0.3).drawImage(image=big_im, add_to_image=True)
    var = np.var(big_im.array)
    np.testing.assert_almost_equal(
        var,
        ccdnoise.getVariance(),
        1,
        err_msg='CCDNoise wrong when already an object drawn on the image')

    # Check using a non-integer sky level which does some slightly different calculations.
    rng.seed(testseed)
    big_im_int = galsim.Image(2048, 2048, dtype=int)
    ccdnoise = galsim.CCDNoise(rng, sky_level=34.42, gain=1.6, read_noise=11.2)
    big_im_int.fill(0)
    big_im_int.addNoise(ccdnoise)
    var = np.var(big_im_int.array)
    np.testing.assert_almost_equal(
        var / ccdnoise.getVariance(),
        1.,
        decimal=2,
        err_msg='CCDNoise wrong when sky_level is not an integer')

    # Using gain=0 means the read_noise is in ADU, not e-
    rng.seed(testseed)
    ccdnoise = galsim.CCDNoise(rng, gain=0., read_noise=read_noise)
    var2 = read_noise**2
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        var2,
        precision,
        err_msg="CCDNoise getVariance returns wrong variance with gain=0")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level,
        0.,
        precision,
        err_msg="CCDNoise sky_level returns wrong value with gain=0")
    np.testing.assert_almost_equal(
        ccdnoise.gain,
        0.,
        precision,
        err_msg="CCDNoise gain returns wrong value with gain=0")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        read_noise,
        precision,
        err_msg="CCDNoise read_noise returns wrong value with gain=0")
    big_im.fill(0)
    big_im.addNoise(ccdnoise)
    var = np.var(big_im.array)
    np.testing.assert_almost_equal(var,
                                   ccdnoise.getVariance(),
                                   1,
                                   err_msg='CCDNoise wrong when gain=0')

    # Check withVariance
    ccdnoise = galsim.CCDNoise(rng,
                               sky_level=sky,
                               gain=gain,
                               read_noise=read_noise)
    ccdnoise = ccdnoise.withVariance(9.)
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        9.,
        precision,
        err_msg="CCDNoise withVariance results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level, (9. / var1) * sky,
        precision,
        err_msg="CCDNoise withVariance results in wrong sky_level")
    np.testing.assert_almost_equal(
        ccdnoise.gain,
        gain,
        precision,
        err_msg="CCDNoise withVariance results in wrong gain")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        np.sqrt(9. / var1) * read_noise,
        precision,
        err_msg="CCDNoise withVariance results in wrong ReadNoise")

    # Check withScaledVariance
    ccdnoise = ccdnoise.withScaledVariance(4.)
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        36.,
        precision,
        err_msg="CCDNoise withVariance results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level, (36. / var1) * sky,
        precision,
        err_msg="CCDNoise withVariance results in wrong sky_level")
    np.testing.assert_almost_equal(
        ccdnoise.gain,
        gain,
        precision,
        err_msg="CCDNoise withVariance results in wrong gain")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        np.sqrt(36. / var1) * read_noise,
        precision,
        err_msg="CCDNoise withVariance results in wrong ReadNoise")

    # Check arithmetic
    ccdnoise = ccdnoise.withVariance(0.5)
    ccdnoise2 = ccdnoise * 3
    np.testing.assert_almost_equal(
        ccdnoise2.getVariance(),
        1.5,
        precision,
        err_msg="CCDNoise ccdnoise*3 results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        0.5,
        precision,
        err_msg=
        "CCDNoise ccdnoise*3 results in wrong variance for original ccdnoise")
    ccdnoise2 = 5 * ccdnoise
    np.testing.assert_almost_equal(
        ccdnoise2.getVariance(),
        2.5,
        precision,
        err_msg="CCDNoise 5*ccdnoise results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        0.5,
        precision,
        err_msg=
        "CCDNoise 5*ccdnoise results in wrong variance for original ccdnoise")
    ccdnoise2 = ccdnoise / 2
    np.testing.assert_almost_equal(
        ccdnoise2.getVariance(),
        0.25,
        precision,
        err_msg="CCDNoise ccdnoise/2 results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        0.5,
        precision,
        err_msg=
        "CCDNoise 5*ccdnoise results in wrong variance for original ccdnoise")
    ccdnoise *= 3
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        1.5,
        precision,
        err_msg="CCDNoise ccdnoise*=3 results in wrong variance")
    ccdnoise /= 2
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        0.75,
        precision,
        err_msg="CCDNoise ccdnoise/=2 results in wrong variance")

    # Check starting with CCDNoise()
    ccdnoise = galsim.CCDNoise()
    ccdnoise = ccdnoise.withVariance(9.)
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        9.,
        precision,
        err_msg="CCDNoise().withVariance results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level,
        9.,
        precision,
        err_msg="CCDNoise().withVariance results in wrong sky_level")
    np.testing.assert_almost_equal(
        ccdnoise.gain,
        1.,
        precision,
        err_msg="CCDNoise().withVariance results in wrong gain")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        0.,
        precision,
        err_msg="CCDNoise().withVariance results in wrong ReadNoise")
    ccdnoise = ccdnoise.withScaledVariance(4.)
    np.testing.assert_almost_equal(
        ccdnoise.getVariance(),
        36.,
        precision,
        err_msg="CCDNoise().withScaledVariance results in wrong variance")
    np.testing.assert_almost_equal(
        ccdnoise.sky_level,
        36.,
        precision,
        err_msg="CCDNoise().withScaledVariance results in wrong sky_level")
    np.testing.assert_almost_equal(
        ccdnoise.gain,
        1.,
        precision,
        err_msg="CCDNoise().withScaledVariance results in wrong gain")
    np.testing.assert_almost_equal(
        ccdnoise.read_noise,
        0.,
        precision,
        err_msg="CCDNoise().withScaledVariance results in wrong ReadNoise")

    # Check picklability
    do_pickle(ccdnoise, lambda x:
              (x.rng.serialize(), x.sky_level, x.gain, x.read_noise))
    do_pickle(ccdnoise, drawNoise)
    do_pickle(ccdnoise)

    # Check copy, eq and ne
    ccdnoise = galsim.CCDNoise(rng, sky, gain, read_noise)
    ccdnoise2 = galsim.CCDNoise(ccdnoise.rng.duplicate(),
                                gain=gain,
                                read_noise=read_noise,
                                sky_level=sky)
    ccdnoise3 = ccdnoise.copy()
    ccdnoise4 = ccdnoise.copy(rng=galsim.BaseDeviate(11))
    ccdnoise5 = galsim.CCDNoise(ccdnoise.rng,
                                gain=2 * gain,
                                read_noise=read_noise,
                                sky_level=sky)
    ccdnoise6 = galsim.CCDNoise(ccdnoise.rng,
                                gain=gain,
                                read_noise=2 * read_noise,
                                sky_level=sky)
    ccdnoise7 = galsim.CCDNoise(ccdnoise.rng,
                                gain=gain,
                                read_noise=read_noise,
                                sky_level=2 * sky)
    assert ccdnoise == ccdnoise2
    assert ccdnoise == ccdnoise3
    assert ccdnoise != ccdnoise4
    assert ccdnoise != ccdnoise5
    assert ccdnoise != ccdnoise6
    assert ccdnoise != ccdnoise7
    assert ccdnoise.rng.raw() == ccdnoise2.rng.raw()
    assert ccdnoise == ccdnoise2
    assert ccdnoise == ccdnoise3
    ccdnoise.rng.raw()
    assert ccdnoise != ccdnoise2
    assert ccdnoise == ccdnoise3
Example #14
0
def simImage(sourceDir,imFile,catFile,psfFile,outFile):

    """ 

    Create a simulated image using PSFEx modelled PSFs and noise properties of the source image 

    Input: sourceDir: input directory data
           imFile: imput image file name
           catFile: catalogue file (output from SeXtractor)
           psfFile: psf model (output from PSFEx)
           outFile: name of output file for image

    Output: writes to fits file. 

    The catFile must contain the fields X_IMAGE, Y_IMAGE, FLUX_APER (or the code to 
    be changed to equivalent for positions of sources and integrated flux). 

    """
    #load necessary stuff from files.

    #NOte that the MCS image files have two HDUs, one with
    #the WCS information, one with the image information. 
    
    galHdr1 = galsim.FitsHeader(imFile, dir=sourceDir, hdu=0)
    galHdr2 = galsim.FitsHeader(imFile, dir=sourceDir, hdu=1)
    cat = galsim.Catalog(catFile, hdu=2, dir=sourceDir, file_type="FITS")
    psfex=des.DES_PSFEx(psfFile,imFile,dir=sourceDir)
    image=galsim.fits.read(imFile,sourceDir,hdu=1)
    
    #get setup the image. match the (currently trivial) WCS with the image, and
    #create a blank image
    
    wcs = galsim.FitsWCS(header=galHdr1)
    xSize=galHdr2['NAXIS1']
    ySize=galHdr2['NAXIS2']
    simImage = galsim.Image(xSize, ySize, wcs=wcs)

    #some definitions for extracting catalogue columsn
    xCol="X_IMAGE"
    yCol="Y_IMAGE"
    fluxCol="FLUX_APER"

    #get noise statistics. Read in the catalogue positions to estimate the centre
    #of the image in whatever rotation it has. This is so we get the noise statistics
    #from teh mask region, excludin the rest of the image. 
    
    xVals=cat.data[xCol]
    yVals=cat.data[yCol]
    xMean=int(xVals.mean())
    yMean=int(yVals.mean())
    radius=1800

    subIm=image.array[int(xMean-radius):int(xMean+radius),int(yMean-radius):int(yMean+radius)]
    im,a,b=sigmaclip(subIm.ravel(),5,5)

    skyLevel=im.mean()
    skySigma=im.std()
    gain = skyLevel / skySigma**2  #this definition from teh galsim tutorials
    nobj = cat.nobjects

    print('Catalog has ',nobj,' objects. Sky level is ',int(skyLevel),' Sky sigma is ',int(skySigma))

    #now cycle over the catalogue.
    
    for k in range(nobj):

        #get position and flux
        x = cat.getFloat(k,xCol)
        y = cat.getFloat(k,yCol)
        flux = cat.getFloat(k,fluxCol)*5

        #some position calculation for the galsim routines
        # + 0.5 to account for even-size postage stamps
        x=x+0.5
        y=y+0.5
        ix = int(math.floor(x+0.5))
        iy = int(math.floor(y+0.5))
        dx = x-ix
        dy = y-iy

        imagePos = galsim.PositionD(x,y)
        offset = galsim.PositionD(dx,dy)

        #calculate PSF for given position and flux
        psf=psfex.getPSF(imagePos).withFlux(flux)

        #make image
        stamp = psf.drawImage(wcs=wcs.local(imagePos), offset=offset, method='no_pixel')
        stamp.setCenter(ix,iy)

        #and place on image, taking into consideration edges
        bounds = stamp.bounds & simImage.bounds
        simImage[bounds] += stamp[bounds]

    #now that we've done all the spots, add noise

    #background sky level
    simImage += skyLevel

    #CCD noise
    random_seed = 1339201
    rng=galsim.BaseDeviate(random_seed)
    noise = galsim.CCDNoise(rng, gain=gain)

    #poisonnian noise
    simImage.addNoise(noise)
    noise = galsim.PoissonNoise(rng, sky_level=skyLevel)
    simImage.addNoise(noise)

    #and dump to a file. Will overwrite existing file.
    simImage.write(outFile,clobber=True)
Example #15
0
# get adaptive moments some number of times so we can average over the calls to get an average speed
im_obj = galsim.ImageF(imsize, imsize)
im_epsf = galsim.ImageF(imsize, imsize)
im_obj = obj.draw(image=im_obj, dx=pixel_scale)
im_epsf = epsf.draw(image=im_epsf, dx=pixel_scale)
sky_level_pix = sky_level * pixel_scale**2
sn_meas = math.sqrt(numpy.sum(im_obj.array**2) / sky_level_pix)
flux_ratio = sn_noisy / sn_meas
ud = galsim.UniformDeviate(seed)
tot_time_meas = 0.0
mean_sigma = 0.
mean_e1 = 0.
mean_e2 = 0.
for i in range(ntest):
    tmp_im = flux_ratio * im_obj + sky_level_pix
    tmp_im.addNoise(galsim.CCDNoise(ud))
    tmp_im -= sky_level_pix
    if save_im == 1 and i == 0:
        tmp_im.write('tmp_im_first.fits')
    if save_im == 1 and i == ntest - 1:
        tmp_im.write('tmp_im_last.fits')
    t1 = time.time()
    res = tmp_im.FindAdaptiveMom(strict=False)
    t2 = time.time()
    tot_time_meas += (t2 - t1)
    mean_sigma += res.moments_sigma
    mean_e1 += res.observed_shape.e1
    mean_e2 += res.observed_shape.e2
time_per_call = tot_time_meas / ntest
mean_sigma /= ntest
mean_e1 /= ntest
Example #16
0
def drawimg(catalog,
            simgalimgfilepath="test.fits",
            simtrugalimgfilepath=None,
            simpsfimgfilepath=None,
            gsparams=None,
            sersiccut=None):
    """
	Turns a catalog as obtained from drawcat into FITS images.
	Only the position jitter and the pixel noise are randomized. All the other info is taken from the input catalog.
	So simply call me several times for the same input to get different realizations of the same galaxies.
	To specify the PSFs, add a meta["psf"] ImageInfo object to the catalog.
		
	:param catalog: an input catalog of galaxy shape parameters, as returned by drawcat.
		The corresponding stampsize must be provided as catalog.meta["stampsize"].
		If you specify a psf image in catalog.meta["psf"], your catalog must of course also contain
		PSF coordinates for that image.
		If no PSF stamps are specifed, the code will look for Gaussian PSF parameters in the catalog.
		If such parameters are not given, no PSF convolution is done.
		
	:param simgalimgfilepath: where I write my output image of simulated and noisy galaxies
	:param simtrugalimgfilepath: (optional) where I write the image without convolution and noise
	:param simpsfimgfilepath: (optional) where I write the PSFs
	
	:param sersiccut: cuts the sersic profile at this number of rad
	
	.. note::
		See this function in MomentsML v4 (great3) for attempts to speed up galsim by playing with fft params, accuracy, etc...
	
	.. note::
		About speed, if you specify trunc, better express the scale radius.
		But scale radius is crazy dependent on n, so I keep half-light-radius
		http://galsim-developers.github.io/GalSim/classgalsim_1_1base_1_1_sersic.html
		
		
	.. note::
		To use the hacks, give "metadict":{"hack":"nicobackgals"} as drawcatkwargs to sim.run.multi()...
	
	"""
    starttime = datetime.now()

    hack = catalog.meta.get("hack", None)

    if hack is None:  # The default behavior, without specific gsparams or tricks.

        if gsparams is None:
            gsparams = galsim.GSParams(maximum_fft_size=10240)

        if "nx" not in catalog.meta.keys() or "ny" not in catalog.meta.keys():
            raise RuntimeError(
                "Provide nx and ny in the meta data of the input catalog to drawimg."
            )
        if "stampsize" not in catalog.meta.keys():
            raise RuntimeError(
                "Provide stampsize in the meta data of the input catalog to drawimg."
            )

        nx = catalog.meta["nx"]
        ny = catalog.meta["ny"]
        stampsize = catalog.meta["stampsize"]  # The stamps I'm going to draw

        logger.info("Drawing images of %i galaxies on a %i x %i grid..." %
                    (len(catalog), nx, ny))
        logger.info("The stampsize for the simulated galaxies is %i." %
                    (stampsize))

        # A special function checks the combination of settings in the provided catalog:
        todo = checkcat(catalog)

        if "loadpsfimg" in todo:
            psfimg = catalog.meta["psf"].load(
            )  # Loading the actual GalSim Image
            psfinfo = catalog.meta["psf"]

        if "tru_pixel" in todo:
            # This is only if you want "effective pixels" larger than the actual pixels (related to SBE, normally you don't want this).
            pix = galsim.Pixel(
                catalog["tru_pixel"]
                [0])  # We have checked in checkcat that all values are equal.

        # Galsim random number generators
        rng = galsim.BaseDeviate()
        ud = galsim.UniformDeviate()  # This gives a random float in [0, 1)

        # We prepare the big images :
        gal_image = galsim.ImageF(stampsize * nx, stampsize * ny)
        trugal_image = galsim.ImageF(stampsize * nx, stampsize * ny)
        psf_image = galsim.ImageF(stampsize * nx, stampsize * ny)

        gal_image.scale = 1.0  # we use pixels as units. Note that if you change something here, you also have to change the jitter.
        trugal_image.scale = 1.0
        psf_image.scale = 1.0

        # And loop through the catalog:
        for row in catalog:

            # Some simplistic progress indication:
            fracdone = float(row.index) / len(catalog)
            if row.index % 500 == 0:
                logger.info("%6.2f%% done (%i/%i) " %
                            (fracdone * 100.0, row.index, len(catalog)))

            # We will draw this galaxy in a postage stamp, but first we need the bounds of this stamp.
            ix = int(row["ix"])
            iy = int(row["iy"])
            assert ix < nx and iy < ny
            bounds = galsim.BoundsI(
                ix * stampsize + 1, (ix + 1) * stampsize, iy * stampsize + 1,
                (iy + 1) *
                stampsize)  # Default Galsim convention, index starts at 1.
            gal_stamp = gal_image[bounds]
            trugal_stamp = trugal_image[bounds]
            psf_stamp = psf_image[bounds]

            # We draw the desired profile
            profile_type = params.profile_types[row["tru_type"]]

            if profile_type == "Sersic":
                if sersiccut is None:
                    trunc = 0
                else:
                    trunc = float(row["tru_rad"]) * sersiccut
                gal = galsim.Sersic(n=float(row["tru_sersicn"]),
                                    half_light_radius=float(row["tru_rad"]),
                                    flux=float(row["tru_flux"]),
                                    gsparams=gsparams,
                                    trunc=trunc)
                # We make this profile elliptical
                gal = gal.shear(g1=row["tru_g1"], g2=row["tru_g2"]
                                )  # This adds the ellipticity to the galaxy

            elif profile_type == "Gaussian":

                gal = galsim.Gaussian(flux=float(row["tru_flux"]),
                                      sigma=float(row["tru_sigma"]),
                                      gsparams=gsparams)
                # We make this profile elliptical
                gal = gal.shear(g1=row["tru_g1"], g2=row["tru_g2"]
                                )  # This adds the ellipticity to the galaxy

            elif profile_type == "EBulgeDisk":

                # A more advanced Bulge + Disk model
                # It needs GalSim version master, as of April 2017 (probably 1.5).

                # Get a Sersic bulge:
                bulge = galsim.Sersic(n=row["tru_bulge_sersicn"],
                                      half_light_radius=row["tru_bulge_hlr"],
                                      flux=row["tru_bulge_flux"])
                # Make it elliptical:
                bulge_ell = galsim.Shear(g=row["tru_bulge_g"],
                                         beta=row["tru_theta"] *
                                         galsim.degrees)
                bulge = bulge.shear(bulge_ell)

                # Get a disk
                scale_radius = row[
                    "tru_disk_hlr"] / galsim.Exponential._hlr_factor
                disk = galsim.InclinedExponential(
                    inclination=row["tru_disk_tilt"] * galsim.degrees,
                    scale_radius=scale_radius,
                    flux=row["tru_disk_flux"],
                    scale_h_over_r=row["tru_disk_scale_h_over_r"])
                # Rotate it in the same orientation as the bulge:
                disk = disk.rotate(row["tru_theta"] * galsim.degrees)

                # And we add those profiles, as done in GalSim demo3.py :
                gal = bulge + disk

            else:
                raise RuntimeError("Unknown galaxy profile!")

            # And now we add lensing, if s1, s2 and mu are different from no lensing...
            if row["tru_s1"] != 0 or row["tru_s2"] != 0 or row["tru_mu"] != 1:
                gal = gal.lens(float(row["tru_s1"]), float(row["tru_s2"]),
                               float(row["tru_mu"]))
            else:
                pass
                #logger.info("No lensing!")

            # We apply some jitter to the position of this galaxy
            xjitter = ud(
            ) - 0.5  # This is the minimum amount -- should we do more, as real galaxies are not that well centered in their stamps ?
            yjitter = ud() - 0.5
            gal = gal.shift(xjitter, yjitter)

            # We draw the pure unconvolved galaxy
            if simtrugalimgfilepath != None:
                gal.drawImage(
                    trugal_stamp,
                    method="auto")  # Will convolve by the sampling pixel.

            # We prepare/get the PSF and do the convolution:

            # Should the final operation skip the convolution by the pixel (because the PSF already is in large pixels) ?
            skip_pixel_conv = False

            if "usegausspsf" in todo:

                if row["tru_psf_sigma"] < 0.0:
                    raise RuntimeError("Unknown hack!")
                else:
                    psf = galsim.Gaussian(flux=1., sigma=row["tru_psf_sigma"])
                    psf = psf.shear(g1=row["tru_psf_g1"], g2=row["tru_psf_g2"])

                    # Let's apply some jitter to the position of the PSF (not sure if this is required, but should not harm ?)
                    psf_xjitter = ud() - 0.5
                    psf_yjitter = ud() - 0.5
                    psf = psf.shift(psf_xjitter, psf_yjitter)

                if simpsfimgfilepath != None:
                    psf.drawImage(
                        psf_stamp,
                        method="auto")  # Will convolve by the sampling pixel.

                if "tru_pixel" in todo:  # Not sure if this should only apply to gaussian PSFs, but so far this seems OK.
                    # Remember that this is an "additional" pixel convolution, not the usual sampling-related convolution that happens in drawImage.
                    galconv = galsim.Convolve([gal, psf, pix])

                else:
                    galconv = galsim.Convolve([gal, psf])

            elif "loadpsfimg" in todo:

                (inputpsfstamp,
                 flag) = tools.image.getstamp(row[psfinfo.xname],
                                              row[psfinfo.yname], psfimg,
                                              psfinfo.stampsize)
                psfpixelscale = getattr(
                    psfinfo, "pixelscale", 1.0
                )  # Using getattr so that it works with old objects as well
                if psfpixelscale > 0.5:
                    #logger.warning("You seem to be using a sampled PSF with large pixels (e.g., observed stars). I'll do my best and skip the pixel conv, but this might well lead to errors.")
                    skip_pixel_conv = True
                if flag != 0:
                    raise RuntimeError("Could not extract a %ix%i stamp at (%.2f, %.2f) from the psfimg %s" %\
                     (psfinfo.stampsize, psfinfo.stampsize, row[psfinfo.xname], row[psfinfo.yname], psfinfo.name))
                psf = galsim.InterpolatedImage(inputpsfstamp,
                                               flux=1.0,
                                               scale=psfpixelscale)
                if simpsfimgfilepath != None:
                    psf.drawImage(
                        psf_stamp, method="no_pixel"
                    )  # psf_stamp has a different size than inputpsfstamp, so this could lead to problems one day.

                #galconv = galsim.Convolve([gal,psf], real_space=False)
                galconv = galsim.Convolve([gal, psf])

            elif "nopsf" in todo:
                # Nothing to do
                galconv = gal

            else:
                raise RuntimeError("Bug in todo.")

            # Draw the convolved galaxy
            if skip_pixel_conv == False:
                galconv.drawImage(
                    gal_stamp, method="auto"
                )  # This will convolve by the image sampling pixel. Don't do this yourself ahead!
            else:
                #logger.warning("NOT computing any pixel convolution")
                galconv.drawImage(
                    gal_stamp, method="no_pixel"
                )  # Simply uses pixel-center values. Know what you are doing, see doc of galsim.

            # And add noise to the convolved galaxy:
            gal_stamp.addNoise(
                galsim.CCDNoise(rng,
                                sky_level=float(row["tru_sky_level"]),
                                gain=float(row["tru_gain"]),
                                read_noise=float(row["tru_read_noise"])))

        logger.info("Done with drawing, now writing output FITS files ...")

        gal_image.write(simgalimgfilepath)

        if simtrugalimgfilepath != None:
            trugal_image.write(simtrugalimgfilepath)

        if simpsfimgfilepath != None:
            psf_image.write(simpsfimgfilepath)

    elif hack == "nicobackgals":
        """
		This is taken and/or adapted from Nico's simulation code, to be sure to do the same thing.
			- Nico uses GalSim in units of arcsec (instaed of pixels), and we suspect that this affects the convolution.
			- Nico has different gsparams
			- Nico truncates Sersic profiles
			
		"""
        logger.info("Using special hack for nicobackgals")

        gsparams = galsim.GSParams(xvalue_accuracy=2.e-4,
                                   kvalue_accuracy=2.e-4,
                                   maxk_threshold=5.e-3,
                                   folding_threshold=1.e-2)
        pixel_scale = 0.1

        if "nx" not in catalog.meta.keys() or "ny" not in catalog.meta.keys():
            raise RuntimeError(
                "Provide nx and ny in the meta data of the input catalog to drawimg."
            )
        if "stampsize" not in catalog.meta.keys():
            raise RuntimeError(
                "Provide stampsize in the meta data of the input catalog to drawimg."
            )

        nx = catalog.meta["nx"]
        ny = catalog.meta["ny"]
        stampsize = catalog.meta["stampsize"]  # The stamps I'm going to draw

        logger.info("Drawing images of %i galaxies on a %i x %i grid..." %
                    (len(catalog), nx, ny))
        logger.info("The stampsize for the simulated galaxies is %i." %
                    (stampsize))

        # Galsim random number generators
        rng = galsim.BaseDeviate()
        ud = galsim.UniformDeviate()  # This gives a random float in [0, 1)

        # We prepare the big image, and set the pixel scale
        gal_image = galsim.ImageF(stampsize * nx,
                                  stampsize * ny,
                                  scale=pixel_scale)


        psf = galsim.Airy(lam=800, diam=1.2, obscuration=0.3, scale_unit=galsim.arcsec, flux=1./3) + \
         galsim.Airy(lam=700, diam=1.2, obscuration=0.3, scale_unit=galsim.arcsec, flux=1./3) + \
         galsim.Airy(lam=600, diam=1.2, obscuration=0.3, scale_unit=galsim.arcsec, flux=1./3)

        for row in catalog:

            # Some simplistic progress indication:
            fracdone = float(row.index) / len(catalog)
            if row.index % 500 == 0:
                logger.info("%6.2f%% done (%i/%i) " %
                            (fracdone * 100.0, row.index, len(catalog)))

            # We will draw this galaxy in a postage stamp, but first we need the bounds of this stamp.
            ix = int(row["ix"])
            iy = int(row["iy"])
            assert ix < nx and iy < ny
            bounds = galsim.BoundsI(
                ix * stampsize + 1, (ix + 1) * stampsize, iy * stampsize + 1,
                (iy + 1) *
                stampsize)  # Default Galsim convention, index starts at 1.
            gal_stamp = gal_image[bounds]

            # Drawing the galaxy
            half_light_radius = float(row["tru_rad"]) * pixel_scale
            gal = galsim.Sersic(n=float(row["tru_sersicn"]),
                                half_light_radius=half_light_radius,
                                flux=float(row["tru_flux"]),
                                gsparams=gsparams,
                                trunc=half_light_radius * 4.5)
            # We make this profile elliptical
            gal = gal.shear(
                g1=row["tru_g1"],
                g2=row["tru_g2"])  # This adds the ellipticity to the galaxy

            # And now we add lensing, if s1, s2 and mu are different from no lensing...
            if row["tru_s1"] != 0 or row["tru_s2"] != 0 or row["tru_mu"] != 1:
                gal = gal.lens(float(row["tru_s1"]), float(row["tru_s2"]),
                               float(row["tru_mu"]))
            else:
                pass
                #logger.info("No lensing!")

            # We apply some jitter to the position of this galaxy # it seems this has to be given in the image scale...
            xjitter = (ud() - 0.5) * pixel_scale
            yjitter = (ud() - 0.5) * pixel_scale
            gal = gal.shift(xjitter, yjitter)

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

            final.drawImage(gal_stamp)

            # And add noise to the convolved galaxy:
            gal_stamp.addNoise(
                galsim.CCDNoise(rng,
                                sky_level=float(row["tru_sky_level"]),
                                gain=float(row["tru_gain"]),
                                read_noise=float(row["tru_read_noise"])))

        logger.info("Done with drawing, now writing output FITS files ...")

        gal_image.write(simgalimgfilepath)

    else:
        raise RuntimeError("Unknown hack")

    endtime = datetime.now()
    logger.info("This drawing took %s" % (str(endtime - starttime)))
Example #17
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.
    """
    # We do some fancier logging for demo3, just to demonstrate that we can:
    # - we log to both stdout and to a log file
    # - the log file has a lot more (mostly redundant) information
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    if not os.path.isdir('output'):
        os.mkdir('output')
    logFile = logging.FileHandler(os.path.join("output", "script3.log"))
    logFile.setFormatter(logging.Formatter("%(name)s[%(levelname)s] %(asctime)s: %(message)s"))
    logging.getLogger("demo3").addHandler(logFile)
    logger = logging.getLogger("demo3")

    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             # e- / ADU
                           # Note: here we assume 1 photon -> 1 e-, ignoring QE.  If you wanted,
                           # you could include the QE factor as part of the gain.
    read_noise = 0.3       # e- / pixel

    random_seed = 1314662

    logger.info('Starting demo script 3 using:')
    logger.info('    - Galaxy is bulge plus disk, flux = %.1e',gal_flux)
    logger.info('       - Bulge is Sersic (n = %.1f, re = %.2f), frac = %.1f',
                bulge_n,bulge_re,bulge_frac)
    logger.info('       - Disk is Sersic (n = %.1f, r0 = %.2f), frac = %.1f',
                disk_n,disk_r0,1-bulge_frac)
    logger.info('       - Shape is q,beta (%.2f,%.2f deg)', gal_q, gal_beta)
    logger.info('    - Atmospheric PSF is Kolmogorov with fwhm = %.2f',atmos_fwhm)
    logger.info('       - Shape is e,beta (%.2f,%.2f rad)', atmos_e, atmos_beta)
    logger.info('    - Optical PSF has defocus = %.2f, astigmatism = (%.2f,%.2f),',
                opt_defocus, opt_a1, opt_a2)
    logger.info('          coma = (%.2f,%.2f), lambda = %.0f nm, D = %.1f m',
                opt_c1, opt_c2, lam, tel_diam)
    logger.info('          obscuration linear size = %.1f',opt_obscuration)
    logger.info('    - pixel scale = %.2f,',pixel_scale)
    logger.info('    - WCS distortion = (%.2f,%.2f),',wcs_g1,wcs_g2)
    logger.info('    - Poisson noise (sky level = %.1e, gain = %.1f).',sky_level, gain)
    logger.info('    - Gaussian read noise (sigma = %.2f).',read_noise)

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

    # 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)
    # Since the total flux of the components was 1, we could also have written:
    #   gal *= gal_flux
    # The withFlux method will always set the flux to the given value, while `gal *= flux`
    # will multiply whatever the current flux is by the given factor.

    # Set the shape of the galaxy according to axis ratio and position angle
    # Note: All angles in GalSim must have explicit units.  Options are:
    #       galsim.radians
    #       galsim.degrees
    #       galsim.arcmin
    #       galsim.arcsec
    #       galsim.hours
    gal_shape = galsim.Shear(q=gal_q, beta=gal_beta*galsim.degrees)
    gal = gal.shear(gal_shape)
    logger.debug('Made galaxy profile')

    # 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 e-/ADU.  Technically, one should also account for quantum efficiency
    # (QE) of the detector. An ideal CCD has one electron per incident photon, but real CCDs have
    # QE 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 e-/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)
Example #18
0
gal_only_stamp = gal.drawImage(scale=pixel_scale, nx=32, ny=32)
psf_stamp = psf.drawImage(scale=pixel_scale, nx=32, ny=32)

obs_stamp = final.drawImage(scale=pixel_scale, nx=32, ny=32)

## Create noisy observations

image_obslist = []
for obs in range(n_obs):
    ##
    ## Define noise, add it to stamps
    ##
    this_gal_stamp = final.drawImage(scale=pixel_scale, nx=50, ny=50)
    ud = galsim.UniformDeviate(seed + obs + 1)
    noise = galsim.CCDNoise(rng=ud,
                            sky_level=sky_level,
                            gain=gain,
                            read_noise=read_noise)
    this_gal_stamp.addNoise(noise)
    image_cutout = this_gal_stamp.array
    image_obslist.append(image_cutout)
    del (this_gal_stamp)

####################################################
## Uncomment if using script in interactive mode
"""
## Inspect single galaxy observation before noise
plt.figure()
plt.imshow(obs_stamp.array)
plt.title('PSF-convolved galaxy\n%d s observation (noise-free)' % int(exp_time))
plt.colorbar()
plt.savefig('diagnostics_plots/m24_noisefree_gal.png')
Example #19
0
def main(argv):

    root = 'DECam_00154912'

    data_dir = 'des_data'

    if not os.path.exists(data_dir):
        print('You will need to download some DES data to use this script.')
        print('Run the following commands from the directory GalSim/examples/des:')
        print()
        print('    wget http://www.sas.upenn.edu/~mjarvis/des_data.tar.gz')
        print('    tar xfz des_data.tar.gz')
        print()
        print('Then try running this script again.  It should work now.')
        sys.exit()

    # Set which chips to run on
    first_chip = 1
    last_chip = 62
    #first_chip = 12
    #last_chip = 12

    # quick and dirty command line parsing.
    for var in argv:
        if var.startswith('first='): first_chip = int(var[6:])
        if var.startswith('last='): last_chip = int(var[5:])
    print('Processing chips %d .. %d'%(first_chip, last_chip))

    out_dir = 'output'

    # The random seed, so the results are deterministic
    random_seed = 1339201

    x_col = 'X_IMAGE'
    y_col = 'Y_IMAGE'
    flux_col = 'FLUX_AUTO'
    flag_col = 'FLAGS'

    xsize_key = 'NAXIS1'
    ysize_key = 'NAXIS2'
    sky_level_key = 'SKYBRITE'
    sky_sigma_key = 'SKYSIGMA'

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

    for chipnum in range(first_chip,last_chip+1):
        print('Start chip ',chipnum)

        # Setup the file names
        image_file = '%s_%02d.fits.fz'%(root,chipnum)
        cat_file = '%s_%02d_cat.fits'%(root,chipnum)
        psfex_file = '%s_%02d_psfcat.psf'%(root,chipnum)
        fitpsf_file = '%s_%02d_fitpsf.fits'%(root,chipnum)
        psfex_image_file = '%s_%02d_psfex_image.fits'%(root,chipnum)
        fitpsf_image_file = '%s_%02d_fitpsf_image.fits'%(root,chipnum)

        # Get some parameters about the image from the data image header information
        image_header = galsim.FitsHeader(image_file, dir=data_dir)
        xsize = image_header[xsize_key]
        ysize = image_header[ysize_key]
        sky_sigma = image_header[sky_sigma_key]  # This is sqrt(variance) / pixel
        sky_level = image_header[sky_level_key]  # This is in ADU / pixel
        gain = sky_level / sky_sigma**2  # an approximation, since gain is missing.

        # Read the WCS
        wcs = galsim.FitsWCS(header=image_header)

        # Setup the images:
        psfex_image = galsim.Image(xsize, ysize, wcs=wcs)
        fitpsf_image = galsim.Image(xsize, ysize, wcs=wcs)

        # Read the other input files
        cat = galsim.Catalog(cat_file, hdu=2, dir=data_dir)
        psfex = galsim.des.DES_PSFEx(psfex_file, image_file, dir=data_dir)
        fitpsf = galsim.des.DES_Shapelet(fitpsf_file, dir=data_dir)

        nobj = cat.nobjects
        print('Catalog has ',nobj,' objects')

        for k in range(nobj):
            sys.stdout.write('.')
            sys.stdout.flush()

            # Skip objects with a non-zero flag
            flag = cat.getInt(k,flag_col)
            if flag: continue

            # Get the position from the galaxy catalog
            x = cat.getFloat(k,x_col)
            y = cat.getFloat(k,y_col)
            image_pos = galsim.PositionD(x,y)
            #print '    pos = ',image_pos
            x += 0.5   # + 0.5 to account for even-size postage stamps
            y += 0.5
            ix = int(math.floor(x+0.5))  # round to nearest pixel
            iy = int(math.floor(y+0.5))
            dx = x-ix
            dy = y-iy
            offset = galsim.PositionD(dx,dy)

            # Also get the flux of the galaxy from the catalog
            flux = cat.getFloat(k,flux_col)
            #print '    flux = ',flux
            #print '    wcs = ',wcs.local(image_pos)

            # First do the PSFEx image:
            if True:
                # Define the PSF profile
                psf = psfex.getPSF(image_pos).withFlux(flux)
                #print '    psfex psf = ',psf

                # Draw the postage stamp image
                # Note: Use no_pixel method, since the PSFEx estimate of the PSF already includes
                # the pixel response.
                stamp = psf.drawImage(wcs=wcs.local(image_pos), offset=offset, method='no_pixel')

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

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


            # Next do the ShapeletPSF image:
            # If the position is not within the interpolation bounds, fitpsf will
            # raise an exception telling us to skip this object.  Easier to check here.
            if fitpsf.bounds.includes(image_pos):
                # Define the PSF profile
                psf = fitpsf.getPSF(image_pos).withFlux(flux)
                #print '    fitpsf psf = ',psf

                # Draw the postage stamp image
                # Again, the PSF already includes the pixel response.
                stamp = psf.drawImage(wcs=wcs.local(image_pos), offset=offset, method='no_pixel')

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

                # Find overlapping bounds
                bounds = stamp.bounds & fitpsf_image.bounds
                fitpsf_image[bounds] += stamp[bounds]
            else:
                pass
                #print '...not in fitpsf.bounds'
        print()

        # Add background level
        psfex_image += sky_level
        fitpsf_image += sky_level

        # Add noise
        rng = galsim.BaseDeviate(random_seed)
        noise = galsim.CCDNoise(rng, gain=gain)
        psfex_image.addNoise(noise)
        # Reset the random seed to match the action of the yaml version
        # Note: the difference between seed and reset matters here.
        # reset would sever the connection between this rng instance and the one stored in noise.
        # seed changes the seed while keeping the connection between them.
        rng.seed(random_seed)
        fitpsf_image.addNoise(noise)

        # Now write the images to disk.
        psfex_image.write(psfex_image_file, dir=out_dir)
        fitpsf_image.write(fitpsf_image_file, dir=out_dir)
        print('Wrote images to %s and %s'%(
                os.path.join(out_dir,psfex_image_file),
                os.path.join(out_dir,fitpsf_image_file)))

        # Increment the random seed by the number of objects in the file
        random_seed += nobj
Example #20
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles.  
      - Using parametric galaxies so that filter responses/system throughput can be convolved
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    global logger
    logger = logging.getLogger("mock_superbit_data")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.
    global pixel_scale
    pixel_scale = 0.206  # arcsec/pixel
    global image_xsize
    image_xsize = 6665  # size of image in pixels
    global image_ysize
    image_ysize = 4453  # size of image in pixels
    global image_xsize_arcsec
    image_xsize_arcsec = image_xsize * pixel_scale  # size of big image in each dimension (arcsec)
    global image_ysize_arcsec
    image_ysize_arcsec = image_ysize * pixel_scale  # size of big image in each dimension (arcsec)

    gain = 3.33
    dark_current = 0.33
    read_noise = 5

    global nobj
    nobj = 10500  # number of galaxies in entire field
    global nstars
    nstars = 320  # number of stars in the entire field
    global flux_scaling  # Let's figure out the flux for a 0.5 m class telescope
    global tel_diam
    tel_diam = 0.5
    global lam
    lam = 625  # Central wavelength for an airy disk
    global exp_time
    global noise_variance
    global sky_level

    psf_path = '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/output-real/psfex_output'
    global nfw  # will store the NFWHalo information
    global cosmos_cat  # will store the COSMOS catalog from which we draw objects

    # Set up the NFWHalo:
    mass = 5E14  # Cluster mass (Msol/h)
    nfw_conc = 4  # Concentration parameter = virial radius / NFW scale radius
    nfw_z_halo = 0.17  # redshift of the halo
    nfw_z_source = 0.6  # redshift of the lensed sources
    omega_m = 0.3  # Omega matter for the background cosmology.
    omega_lam = 0.7  # Omega lambda for the background cosmology.

    nfw = galsim.NFWHalo(mass=mass,
                         conc=nfw_conc,
                         redshift=nfw_z_halo,
                         omega_m=omega_m,
                         omega_lam=omega_lam)
    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.

    cat_file_name = 'real_galaxy_catalog_23.5.fits'
    fdir = 'data/COSMOS_23.5_training_sample'
    fit_file_name = 'real_galaxy_catalog_23.5_fits.fits'

    cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=fdir)
    fitcat = Table.read(os.path.join(fdir, fit_file_name))
    logger.info('Read in %d galaxies from catalog and associated fit info',
                cosmos_cat.nobjects)

    cluster_cat = galsim.COSMOSCatalog(
        'data/real_galaxy_catalog_23.5_example.fits')
    logger.info('Read in %d cluster galaxies from catalog',
                cosmos_cat.nobjects)

    # 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.
    # Will also multiply by the gain and relative pixel scales...

    hst_eff_area = 2.4**2
    sbit_eff_area = tel_diam**2

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ###

    all_psfs = glob.glob(
        '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/output-real/psfex_output/dwb_image_ifc*WCS_cat.psf'
    )
    logger.info('Beginning loop over jitter/optical psfs')
    random_seed = 7839234
    i = 0
    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...' % psf_filen)
        rng = galsim.BaseDeviate(random_seed)

        # This is specific to empirical PSFs

        try:
            timescale = psf_filen.split('target_')[1].split('_WCS')[0]
        except:
            timescale = psf_filen.split('sci_')[1].split('_WCS')[0]

        outname = ''.join(
            ['mockSuperbit_nodilate_', timescale, '_',
             str(i), '.fits'])
        truth_file_name = ''.join(
            ['./output/truth_nodilate_', timescale, '_',
             str(i), '.dat'])
        file_name = os.path.join('output', outname)

        # Set up the image:
        if timescale == '150':
            print(
                "Automatically detecting a 150s exposure image, setting flux scale and sky accordingly"
            )
            sky_level = 51  # ADU
            exp_time = 150.

        else:
            print(
                "Automatically detecting a 300s exposure image, setting flux scale and sky accordingly"
            )
            sky_level = 102  # ADU
            exp_time = 300.

        flux_scaling = (sbit_eff_area / hst_eff_area
                        ) * exp_time * gain  #* (pixel_scale/0.05)#**2

        # Setting up a truth catalog

        names = [
            'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas', 'g2_meas',
            'fwhm', 'mom_size', 'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift',
            'flux', 'stamp_sum'
        ]
        types = [
            int, float, float, float, float, float, float, float, float, float,
            float, float, float, float, float
        ]
        truth_catalog = galsim.OutputCatalog(names, types)

        # Set up the image:

        full_image = galsim.ImageF(image_xsize, image_ysize)
        full_image.setOrigin(0, 0)
        full_image.fill(sky_level)

        wcs = get_wcs_info(psf_filen)
        affine = wcs.affine(full_image.true_center)
        full_image.wcs = wcs

        # Now let's read in the PSFEx PSF model.
        psf_wcs = wcs
        psf_file = os.path.join(psf_path, psf_filen)
        psf = galsim.des.DES_PSFEx(psf_file, wcs=psf_wcs)
        logger.info('Constructed PSF object from PSFEx file')

        # Loop over galaxy 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)

            try:
                # make single galaxy object
                logger.debug("about to make stamp...")
                stamp, truth = make_a_galaxy(ud=ud,
                                             wcs=wcs,
                                             psf=psf,
                                             affine=affine,
                                             fitcat=fitcat)
                logger.debug("stamp is made")
                # Find the overlapping bounds:
                bounds = stamp.bounds & full_image.bounds

                # Finally, add the stamp to the full image.
                full_image[bounds] += stamp[bounds]
                logger.debug("stamp added to full image")
                time2 = time.time()
                tot_time = time2 - time1
                logger.info('Galaxy %d positioned relative to center t=%f s',
                            k, tot_time)
                g1_real = stamp.FindAdaptiveMom().observed_shape.g1
                g2_real = stamp.FindAdaptiveMom().observed_shape.g2
                sum_flux = numpy.sum(stamp.array)
                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_flux
                ]
                truth_catalog.addRow(row)
                logger.debug("row added to truth catalog")

            except:
                logger.info('Galaxy %d has failed, skipping...', k)
                pdb.set_trace()

        #####
        ### Inject cluster galaxy objects:
        ### - Note that this "cluster" is just for aesthetics
        ### - So, 'n_cluster_gals' is arbitrary
        ### - You could concievably create a method to base the number of galaxies injected
        ###   using some scaling relation between (NFW) mass and richness to set n_cluster_gals
        ###   to something based in reality (though these are poorly constrained at low mass!).
        #####

        n_cluster_gals = 40

        for k in range(n_cluster_gals):
            time1 = time.time()

            # The usual random number generator using a different seed for each galaxy.
            ud = galsim.UniformDeviate(random_seed + k + 1)

            try:
                # make single galaxy object
                cluster_stamp, truth = make_cluster_galaxy(
                    ud=ud,
                    wcs=wcs,
                    affine=affine,
                    psf=psf,
                    cluster_cat=cluster_cat,
                    fitcat=fitcat)

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

                # Finally, add the stamp to the full image.

                full_image[bounds] += cluster_stamp[bounds]
                time2 = time.time()
                tot_time = time2 - time1
                logger.info(
                    'Cluster galaxy %d positioned relative to center t=%f s',
                    k, tot_time)
                sum_flux = numpy.sum(cluster_stamp.array)
                g1_real = cluster_stamp.FindAdaptiveMom().observed_shape.g1
                g2_real = cluster_stamp.FindAdaptiveMom().observed_shape.g2

                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_flux
                ]
                truth_catalog.addRow(row)
            except:
                logger.info('Cluster galaxy %d has failed, skipping...', k)
                pdb.set_trace()

        ####
        ### Now repeat process for stars!
        ####

        random_seed_stars = 3221987

        for k in range(nstars):
            time1 = time.time()
            ud = galsim.UniformDeviate(random_seed_stars + k + 1)
            try:

                star_stamp, truth = make_a_star(ud=ud,
                                                wcs=wcs,
                                                psf=psf,
                                                affine=affine)
                bounds = star_stamp.bounds & full_image.bounds

                # Add the stamp to the full image.
                full_image[bounds] += star_stamp[bounds]

                time2 = time.time()
                tot_time = time2 - time1

                logger.info('Star %d: positioned relative to center, t=%f s',
                            k, tot_time)

                g1_real = star_stamp.FindAdaptiveMom().observed_shape.g1
                g2_real = star_stamp.FindAdaptiveMom().observed_shape.g2
                #g1_real = -9999.
                #g2_real = -9999.
                sum_flux = numpy.sum(star_stamp.array)
                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_flux
                ]
                truth_catalog.addRow(row)

            except:
                logger.info('Star %d has failed, skipping...', k)
                pdb.set_trace()

        # Add ccd noise
        logger.info('Adding CCD noise')
        noise = galsim.CCDNoise(rng,
                                sky_level=0,
                                gain=1 / gain,
                                read_noise=read_noise)
        full_image.addNoise(noise)

        logger.info('Added noise to final output image')

        # Now write the image to disk.
        full_image.write(file_name)

        # Add a FLUXSCL keyword for later stacking
        this_hdu = astropy.io.fits.open(file_name)
        this_hdu[0].header['FLXSCALE'] = 300.0 / exp_time
        this_hdu.writeto(file_name, overwrite='True')
        logger.info('Wrote image to %r', file_name)

        # Write truth catalog to file.
        truth_catalog.write(truth_file_name)

        i = i + 1
        logger.info('completed run %d for psf %s', i, psf_filen)

    logger.info('completed all images')