def finalize(self, config, base, logger): # Make the OutputCatalog cols = config['columns'] types = self.scratch.pop('types') self.cat = galsim.OutputCatalog(names=cols.keys(), types=types) # Add all the rows in order to the OutputCatalog # Note: types was popped above, so only the obj_num keys are left. obj_nums = sorted(self.scratch.keys()) for obj_num in obj_nums: row = self.scratch[obj_num] self.cat.addRow(row)
def finalize(self, config, base, main_data, logger): # Make the OutputCatalog cols = config['columns'] # Note: Provide a default here, because if all items were skipped it would otherwise # lead to a KeyError. types = self.scratch.pop('types', [float] * len(cols)) self.cat = galsim.OutputCatalog(names=cols.keys(), types=types) # Add all the rows in order to the OutputCatalog # Note: types was popped above, so only the obj_num keys are left. obj_nums = sorted(self.scratch.keys()) for obj_num in obj_nums: row = self.scratch[obj_num] self.cat.addRow(row) return self.cat
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 choose 40% of the galaxies to use the images, and the other 60% to use the parametric fits - The real galaxy images include some initial correlated noise from the original HST observation. However, we whiten the noise of the final image so the final image has stationary Gaussian noise, rather than correlated noise. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("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) global center_ra center_ra = 19.3 * galsim.hours # The RA, Dec of the center of the image on the sky global center_dec center_dec = -33.1 * galsim.degrees global exp_time exp_time = 3000 # exposing for 1500 seconds to match real, observed galaxy/flux count. global noise_variance noise_variance = 1.8e3 # ADU^2 (Just use simple Gaussian noise here.) -->150s #noise_variance = 2.55e3 # ADU^2 (Just use simple Gaussian noise here.) -->300s global sky_level sky_level = 51 # ADU / arcsec^2 -->150s #sky_level = 106 # ADU / arcsec^2 -->300s global nobj nobj = 1700 # number of galaxies in entire field -- an adjustment to ensure ~1100 detections global nstars nstars = 370 # 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 = 587 # Central wavelength psf_path = '/Users/jemcclea/Research/GalSim/examples/data/fpsc_flight_jitter_psf_oversampled_fixed_10x' global optics # will store the Zernicke component of the PSF 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.3 # 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. field_g1 = 0.03 # The field shear is some cosmic shear applied to the whole field, field_g2 = 0.01 # taken to be behind the foreground NFW halo (not needed for now) 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 if True: # The catalog we distribute with the GalSim code only has 100 galaxies. # The galaxies will typically be reused several times here. cat_file_name = 'real_galaxy_catalog_23.5_example.fits' dir = 'data' cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir) else: # If you've run galsim_download_cosmos, you can leave out the cat_file_name and dir # to use the full COSMOS catalog with 56,000 galaxies in it. cosmos_cat = galsim.COSMOSCatalog() logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects) # 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. hst_eff_area = 2.4**2 * (1. - 0.33**2) sbit_eff_area = tel_diam**2 * ( 1. - 0.10**2 ) # For want of something better, operating with 10% obscuration flux_scaling = (sbit_eff_area / hst_eff_area) * exp_time ### Now create PSF. First, define Zernicke polynomial component lam_over_diam = lam * 1.e-9 / tel_diam # radians lam_over_diam *= 206265 # arcsec aberrations = [0.0] * 12 # Set the initial size. aberrations[4] = -0.00725859 # Noll index 4 = Defocus aberrations[5:7] = [0.0, -0.00] # Noll index 5,6 = Astigmatism aberrations[7:9] = [0.07, 0.00] # Noll index 7,8 = Coma aberrations[11] = 0.00133254 # Noll index 11 = Spherical logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam) optics = galsim.OpticalPSF(lam_over_diam, obscuration=0.10, aberrations=aberrations) logger.info('Made telescope PSF profile') ### ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES ### all_psfs = glob.glob(psf_path + "/*.psf") logger.info('Beginning loop over jitter/optical psfs') for psf_filen in all_psfs: logger.info('Beginning PSF %s...' % psf_filen) for i in numpy.arange(1, 6): logger.info('Beginning loop %d' % i) random_seed = scipy.random.randint(low=10000000, high=99999999) rng = galsim.BaseDeviate(random_seed) # This is specific to Javier mock PSFs try: root = psf_filen.split('data/')[1].split('/')[0] timescale = psf_filen.split('_10x/')[1].split('.')[0] outname = ''.join([ 'mock_superbit_', root, timescale, str(i).zfill(3), '.fits' ]) truth_file_name = ''.join([ './output/truth_', root, timescale, str(i).zfill(3), '.dat' ]) file_name = os.path.join('output', outname) except: pdb.set_trace() # Setting up a truth catalog names = [ 'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas', 'g2_meas', 'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift', 'flux' ] 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(image_xsize, image_ysize) 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(image_xsize, image_ysize) noise_image.setOrigin(0, 0) # Make a slightly non-trivial WCS. We'll use a slightly rotated coordinate system # and center it at the image center. theta = 0.17 * galsim.degrees dudx = numpy.cos(theta) * pixel_scale dudy = -numpy.sin(theta) * pixel_scale dvdx = numpy.sin(theta) * pixel_scale dvdy = numpy.cos(theta) * pixel_scale image_center = full_image.true_center affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center) sky_center = galsim.CelestialCoord(ra=center_ra, dec=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_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 stamp, truth = make_a_galaxy(ud=ud, wcs=wcs, psf=psf, affine=affine) # 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) #g1_real=stamp.FindAdaptiveMom().observed_shape.g1 #g2_real=stamp.FindAdaptiveMom().observed_shape.g2 g1_real = -9999. g2_real = -9999. this_flux = numpy.sum(stamp.array) row = [ k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real, truth.g1, truth.g2, truth.mu, truth.z, this_flux ] truth_catalog.addRow(row) except: logger.info('Galaxy %d has failed, skipping...', k) #### ### Now repeat process for stars! #### random_seed_stars = scipy.random.randint(low=10000000, high=99999999) for k in range(nstars): time1 = time.time() ud = galsim.UniformDeviate(random_seed_stars + k + 1) 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. 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) #g1_real=star_stamp.FindAdaptiveMom().observed_shape.g1 --> no longer positive definite :-? #g2_real=star_stamp.FindAdaptiveMom().observed_shape.g2 g1_real = -9999. g2_real = -9999. this_flux = numpy.sum(star_stamp.array) row = [ k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real, truth.g1, truth.g2, truth.mu, truth.z, this_flux ] truth_catalog.addRow(row) except: logger.info('Star %d has failed, skipping...', k) # We already have some noise in the image, but it isn't uniform. So the first thing to do is # to make the Gaussian noise uniform across the whole image. max_current_variance = numpy.max(noise_image.array) noise_image = max_current_variance - noise_image vn = galsim.VariableGaussianNoise(rng, noise_image) full_image.addNoise(vn) # Now max_current_variance is the noise level across the full image. We don't want to add that # twice, so subtract off this much from the intended noise that we want to end up in the image. noise_variance -= max_current_variance # Now add Gaussian noise with this variance to the final image. noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance)) full_image.addNoise(noise) logger.info('Added noise to final large image') # Now write the image to disk. It is automatically compressed with Rice compression, # since the filename we provide ends in .fz. full_image.write(file_name) logger.info('Wrote image to %r', file_name) # Write truth catalog to file. truth_catalog.write(truth_file_name) # Compute some sky positions of some of the pixels to compare with the values of RA, Dec # that ds9 reports. ds9 always uses (1,1) for the lower left pixel, so the pixel coordinates # of these pixels are different by 1, but you can check that the RA and Dec values are # the same as what GalSim calculates. ra_str = center_ra.hms() dec_str = center_dec.dms() logger.info( 'Center of image is at RA %sh %sm %ss, DEC %sd %sm %ss', ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3], dec_str[3:5], dec_str[5:]) for (x, y) in [(0, 0), (0, image_xsize - 1), (image_ysize - 1, 0), (image_xsize - 1, image_ysize - 1)]: world_pos = wcs.toWorld(galsim.PositionD(x, y)) ra_str = world_pos.ra.hms() dec_str = world_pos.dec.dms() logger.info( 'Pixel (%4d, %4d) is at RA %sh %sm %ss, DEC %sd %sm %ss', x, y, ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3], dec_str[3:5], dec_str[5:]) logger.info( 'ds9 reports these pixels as (1,1), (1,2048), etc. with the same RA, Dec.' ) i = i + 1 logger.info(' ') logger.info('completed run %d for psf %s', i, psf_filen) logger.info('completed all images')
def main(argv): """ Make images using variable PSF and shear: - The main image is 10 x 10 postage stamps. - Each postage stamp is 48 x 48 pixels. - The second HDU has the corresponding PSF image. - Applied shear is from a power spectrum P(k) ~ k^1.8. - Galaxies are real galaxies oriented in a ring test of 20 each. - The PSF is Gaussian with FWHM, ellipticity and position angle functions of (x,y) - Noise is Poisson using a nominal sky value of 1.e6. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo10") # Define some parameters we'll use below. # Normally these would be read in from some parameter file. n_tiles = 10 # number of tiles in each direction. stamp_size = 48 # pixels pixel_scale = 0.44 # arcsec / pixel sky_level = 1.e6 # ADU / arcsec^2 # The random seed is used for both the power spectrum realization and the random properties # of the galaxies. random_seed = 3339201 # Make output directory if not already present. if not os.path.isdir('output'): os.mkdir('output') file_name = os.path.join('output', 'power_spectrum.fits') # These will be created for each object below. The values we'll use will be functions # of (x,y) relative to the center of the image. (r = sqrt(x^2+y^2)) # psf_fwhm = 0.9 + 0.5 * (r/100)^2 -- arcsec # psf_e = 0.4 * (r/100)^1.5 -- large value at the edge, so visible by eye. # psf_beta = atan2(y/x) + pi/2 -- tangential pattern gal_dilation = 3 # Make the galaxies a bit larger than their original size. gal_signal_to_noise = 100 # Pretty high. psf_signal_to_noise = 1000 # Even higher. logger.info('Starting demo script 10') # Read in galaxy catalog cat_file_name = 'real_galaxy_catalog_23.5_example.fits' dir = 'data' real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir) logger.info('Read in %d real galaxies from catalog', real_galaxy_catalog.nobjects) # List of IDs to use. We select 5 particularly irregular galaxies for this demo. # Then we'll choose randomly from this list. id_list = [106416, 106731, 108402, 116045, 116448] # Make the 5 galaxies we're going to use here rather than remake them each time. # This means the Fourier transforms of the real galaxy images don't need to be recalculated # each time, so it's a bit more efficient. gal_list = [ galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list ] # Grab the index numbers before we transform them and lose the index attribute. cosmos_index = [gal.index for gal in gal_list] # Make the galaxies a bit larger than their original observed size. gal_list = [gal.dilate(gal_dilation) for gal in gal_list] # Setup the PowerSpectrum object we'll be using: ps = galsim.PowerSpectrum(lambda k: k**1.8) # The argument here is "e_power_function" which defines the E-mode power to use. # There is also a b_power_function if you want to include any B-mode power: # ps = galsim.PowerSpectrum(e_power_function, b_power_function) # You may even omit the e_power_function argument and have a pure B-mode power spectrum. # ps = galsim.PowerSpectrum(b_power_function = b_power_function) # All the random number generator classes derive from BaseDeviate. # When we construct another kind of deviate class from any other # kind of deviate class, the two share the same underlying random number # generator. Sometimes it can be clearer to just construct a BaseDeviate # explicitly and then construct anything else you need from that. # Note: A BaseDeviate cannot be used to generate any values. It can # only be used in the constructor for other kinds of deviates. # The seeds for the objects are random_seed+1..random_seed+nobj. # The seeds for things at the image or file level use random_seed itself. nobj = n_tiles * n_tiles rng = galsim.BaseDeviate(random_seed) # Have the PowerSpectrum object build a grid of shear values for us to use. grid_g1, grid_g2 = ps.buildGrid(grid_spacing=stamp_size * pixel_scale, ngrid=n_tiles, rng=rng) # Setup the images: gal_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles) psf_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles) # Update the image WCS to use the image center as the origin of the WCS. # The class that acts like a PixelScale except for this offset is called OffsetWCS. im_center = gal_image.true_center wcs = galsim.OffsetWCS(scale=pixel_scale, origin=im_center) gal_image.wcs = wcs psf_image.wcs = wcs # We will place the tiles in a random order. To do this, we make two lists for the # ix and iy values. Then we apply a random permutation to the lists (in tandem). ix_list = [] iy_list = [] for ix in range(n_tiles): for iy in range(n_tiles): ix_list.append(ix) iy_list.append(iy) # This next function will use the given random number generator, rng, and use it to # randomly permute any number of lists. All lists will have the same random permutation # applied. galsim.random.permute(rng, ix_list, iy_list) # Initialize the OutputCatalog for the truth values names = [ 'gal_num', 'x_image', 'y_image', 'psf_e1', 'psf_e2', 'psf_fwhm', 'cosmos_id', 'cosmos_index', 'theta', 'g1', 'g2', 'shift_x', 'shift_y' ] types = [ int, float, float, float, float, float, str, int, float, float, float, float, float ] truth_catalog = galsim.OutputCatalog(names, types) # Build each postage stamp: for k in range(nobj): # The usual random number generator using a different seed for each galaxy. rng = galsim.BaseDeviate(random_seed + k + 1) # Determine the bounds for this stamp and its center position. ix = ix_list[k] iy = iy_list[k] b = galsim.BoundsI(ix * stamp_size + 1, (ix + 1) * stamp_size, iy * stamp_size + 1, (iy + 1) * stamp_size) sub_gal_image = gal_image[b] sub_psf_image = psf_image[b] pos = wcs.toWorld(b.true_center) # The image comes out as about 211 arcsec across, so we define our variable # parameters in terms of (r/100 arcsec), so roughly the scale size of the image. rsq = (pos.x**2 + pos.y**2) r = math.sqrt(rsq) psf_fwhm = 0.9 + 0.5 * rsq / 100**2 # arcsec psf_e = 0.4 * (r / 100.)**1.5 psf_beta = (math.atan2(pos.y, pos.x) + math.pi / 2) * galsim.radians # Define the PSF profile psf = galsim.Gaussian(fwhm=psf_fwhm) psf_shape = galsim.Shear(e=psf_e, beta=psf_beta) psf = psf.shear(psf_shape) # Define the galaxy profile: # For this demo, we are doing a ring test where the same galaxy profile is drawn at many # orientations stepped uniformly in angle, making a ring in e1-e2 space. # We're drawing each profile at 20 different orientations and then skipping to the # next galaxy in the list. So theta steps by 1/20 * 360 degrees: theta_deg = (k % 20) * 360. / 20 theta = theta_deg * galsim.degrees # The index needs to increment every 20 objects so we use k/20 using integer math. index = k // 20 gal = gal_list[index] # This makes a new copy so we're not changing the object in the gal_list. gal = gal.rotate(theta) # Apply the shear from the power spectrum. We should either turn the gridded shears # grid_g1[iy, ix] and grid_g2[iy, ix] into gridded reduced shears using a utility called # galsim.lensing.theoryToObserved, or use ps.getShear() which by default gets the reduced # shear. ps.getShear() is also more flexible because it can get the shear at positions that # are not on the original grid, as long as they are contained within the bounds of the full # grid. So in this example we'll use ps.getShear(). alt_g1, alt_g2 = ps.getShear(pos) gal = gal.shear(g1=alt_g1, g2=alt_g2) # Apply half-pixel shift in a random direction. shift_r = pixel_scale * 0.5 ud = galsim.UniformDeviate(rng) t = ud() * 2. * math.pi dx = shift_r * math.cos(t) dy = shift_r * math.sin(t) gal = gal.shift(dx, dy) # Make the final image, convolving with the psf final = galsim.Convolve([psf, gal]) # Draw the image final.drawImage(sub_gal_image) # For the PSF image, we don't match the galaxy shift. Rather, we use the offset # parameter to drawImage to apply a random offset of up to 0.5 pixels in each direction. # Note the difference in units between shift and offset. The shift is applied to the # surface brightness profile, so it is in sky coordinates (as all dimension are for # GSObjects), which are arcsec here. The offset though is applied to the image itself, # so it is in pixels. Hence, we don't multiply by pixel_scale. psf_dx = ud() - 0.5 psf_dy = ud() - 0.5 psf_offset = galsim.PositionD(psf_dx, psf_dy) # Draw the PSF image: # We use real space integration over the pixels to avoid some of the # artifacts that can show up with Fourier convolution. # The level of the artifacts is quite low, but when drawing with # so little noise, they are apparent with ds9's zscale viewing. psf.drawImage(sub_psf_image, method='real_space', offset=psf_offset) # Build the noise model: Poisson noise with a given sky level. sky_level_pixel = sky_level * pixel_scale**2 noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel) # Add noise to the PSF image, using the normal noise model, but scaling the # PSF flux high enough to reach the desired signal-to-noise. # See demo5.py for more info about how this works. sub_psf_image.addNoiseSNR(noise, psf_signal_to_noise) # And also to the galaxy image using its signal-to-noise. sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise) # Add the truth values to the truth catalog row = [ k, b.true_center.x, b.true_center.y, psf_shape.e1, psf_shape.e2, psf_fwhm, id_list[index], cosmos_index[index], (theta_deg % 360.), alt_g1, alt_g2, dx, dy ] truth_catalog.addRow(row) logger.info('Galaxy (%d,%d): position relative to center = %s', ix, iy, str(pos)) logger.info('Done making images of postage stamps') # In this case, we'll attach the truth catalog as an additional HDU in the same file as # the image data. truth_hdu = truth_catalog.writeFitsHdu() # Now write the images to disk. images = [gal_image, psf_image, truth_hdu] # Any items in the "images" list that is already an hdu is just used directly. # The actual images are converted to FITS hdus that contain the image data. galsim.fits.writeMulti(images, file_name) logger.info('Wrote image to %r', file_name)
def build_file(seed, file_name, mass, nobj, rng, truth_file_name, halo_id, first_obj_id): """A function that does all the work to build a single file. Returns the total time taken. """ t1 = time.time() # Build the image onto which we will draw the galaxies. full_image = galsim.ImageF(image_size, image_size) # The "true" center of the image is allowed to be halfway between two pixels, as is the # case for even-sized images. full_image.bounds.center() is an integer position, # which would be 1/2 pixel up and to the right of the true center in this case. im_center = full_image.bounds.trueCenter() # For the WCS, this time we use UVFunction, which lets you define arbitrary u(x,y) # and v(x,y) functions. We use a simple cubic radial function to create a # pincushion distortion. This is a typical kind of telescope distortion, although # we exaggerate the magnitude of the effect to make it more apparent. # The pixel size in the center of the image is 0.05, but near the corners (r=362), # the pixel size is approximately 0.075, which is much more distortion than is # normally present in typical telescopes. But it makes the effect of the variable # pixel area obvious when you look at the weight image in the output files. ufunc1 = lambda x, y: 0.05 * x * (1. + 2.e-6 * (x**2 + y**2)) vfunc1 = lambda x, y: 0.05 * y * (1. + 2.e-6 * (x**2 + y**2)) # It's not required to provide the inverse functions. However, if we don't, then # you will only be able to do toWorld operations, not the inverse toImage. # The inverse function does not have to be exact either. For example, you could provide # a function that does some kind of iterative solution to whatever accuracy you care # about. But in this case, we can do the exact inverse. # # Let w = sqrt(u**2 + v**2) and r = sqrt(x**2 + y**2). Then the solutions are: # x = (u/w) r and y = (u/w) r, and we use Cardano's method to solve for r given w: # See http://en.wikipedia.org/wiki/Cubic_function#Cardano.27s_method # # w = 0.05 r + 2.e-6 * 0.05 * r**3 # r = 100 * ( ( 5 sqrt(w**2 + 5.e3/27) + 5 w )**(1./3.) - # - ( 5 sqrt(w**2 + 5.e3/27) - 5 w )**(1./3.) ) def xfunc1(u, v): import math wsq = u * u + v * v if wsq == 0.: return 0. else: w = math.sqrt(wsq) temp = 5. * math.sqrt(wsq + 5.e3 / 27) r = 100. * ((temp + 5 * w)**(1. / 3.) - (temp - 5 * w)**(1. / 3)) return u * r / w def yfunc1(u, v): import math wsq = u * u + v * v if wsq == 0.: return 0. else: w = math.sqrt(wsq) temp = 5. * math.sqrt(wsq + 5.e3 / 27) r = 100. * ((temp + 5 * w)**(1. / 3.) - (temp - 5 * w)**(1. / 3)) return v * r / w # You could pass the above functions to UVFunction, and normally we would do that. # The only down side to doing so is that the specification of the WCS in the FITS # file is rather ugly. GalSim is able to turn the python byte code into strings, # but they are basically a really ugly mess of random-looking characters. GalSim # will be able to read it back in, but human readers will have no idea what WCS # function was used. To see what they look like, uncomment this line and comment # out the later wcs line. #wcs = galsim.UVFunction(ufunc1, vfunc1, xfunc1, yfunc1, origin=im_center) # If you provide the functions as strings, then those strings will be preserved # in the FITS header in a form that is more legible to human readers. # It also has the extra benefit of matching the output from demo9.yaml, which we # always try to do. The config file has no choice but to specify the functions # as strings. ufunc = '0.05 * x * (1. + 2.e-6 * (x**2 + y**2))' vfunc = '0.05 * y * (1. + 2.e-6 * (x**2 + y**2))' xfunc = ( '( lambda w: ( 0 if w==0 else ' + '100.*u/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' + '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )' ) yfunc = ( '( lambda w: ( 0 if w==0 else ' + '100.*v/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' + '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )' ) # The origin parameter defines where on the image should be considered (x,y) = (0,0) # in the WCS functions. wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc, origin=im_center) # Assign this wcs to full_image full_image.wcs = wcs # The weight image will hold the inverse variance for each pixel. # We can set the wcs directly on construction with the wcs parameter. weight_image = galsim.ImageF(image_size, image_size, wcs=wcs) # It is common for astrometric images to also have a bad pixel mask. We don't have any # defect simulation currently, so our bad pixel masks are currently all zeros. # But someday, we plan to add defect functionality to GalSim, at which point, we'll # be able to mark those defects on a bad pixel mask. # Note: the S in ImageS means to use "short int" for the data type. # This is a typical choice for a bad pixel image. badpix_image = galsim.ImageS(image_size, image_size, wcs=wcs) # We also draw a PSF image at the location of every galaxy. This isn't normally done, # and since some of the PSFs overlap, it's not necessarily so useful to have this kind # of image. But in this case, it's fun to look at the psf image, especially with # something like log scaling in ds9 to see how crazy an aberrated OpticalPSF with # struts can look when there is no atmospheric component to blur it out. psf_image = galsim.ImageF(image_size, image_size, wcs=wcs) # We will also write some truth information to an output catalog. # In real simulations, it is often useful to have a catalog of the truth values # to compare to measurements either directly or as cuts on the galaxy sample to # find where systematic errors are largest. # For now, we just make an empty OutputCatalog object with the names and types of the # columns. names = [ 'object_id', 'halo_id', 'flux', 'radius', 'h_over_r', 'inclination.rad', 'theta.rad', 'mu', 'redshift', 'shear.g1', 'shear.g2', 'pos.x', 'pos.y', 'image_pos.x', 'image_pos.y', 'halo_mass', 'halo_conc', 'halo_redshift' ] types = [ int, int, float, float, float, float, float, float, float, float, float, float, float, float, float, float, float, float ] truth_cat = galsim.OutputCatalog(names, types) # Setup the NFWHalo stuff: nfw = galsim.NFWHalo(mass=mass, conc=nfw_conc, redshift=nfw_z_halo, omega_m=omega_m, omega_lam=omega_lam) # Note: the last two are optional. If they are omitted, then (omega_m=0.3, omega_lam=0.7) # are actually the defaults. If you only specify one of them, the other is set so that # the total is 1. But you can define both values so that the total is not 1 if you want. # Radiation is assumed to be zero and dark energy equation of state w = -1. # If you want to include either radiation or more complicated dark energy models, # you can define your own cosmology class that defines the functions a(z), E(a), and # Da(z_source, z_lens). Then you can pass this to NFWHalo as a `cosmo` parameter. # Make the PSF profile outside the loop to minimize the (significant) OpticalPSF # construction overhead. psf = galsim.OpticalPSF(lam=psf_lam, diam=psf_D, obscuration=psf_obsc, nstruts=psf_nstruts, strut_thick=psf_strut_thick, strut_angle=psf_strut_angle, defocus=psf_defocus, astig1=psf_astig1, astig2=psf_astig2, coma1=psf_coma1, coma2=psf_coma2, trefoil1=psf_trefoil1, trefoil2=psf_trefoil2) for k in range(nobj): # Initialize the random number generator we will be using for this object: ud = galsim.UniformDeviate(seed + k + 1) # Determine where this object is going to go. # We choose points randomly within a donut centered at the center of the main image # in order to avoid placing galaxies too close to the halo center where the lensing # is not weak. We use an inner radius of 3 arcsec and an outer radius of 12 arcsec, # which takes us essentially to the edge of the image. radius = 12 inner_radius = 3 max_rsq = radius**2 min_rsq = inner_radius**2 while True: # (This is essentially a do..while loop.) x = (2. * ud() - 1) * radius y = (2. * ud() - 1) * radius rsq = x**2 + y**2 if rsq >= min_rsq and rsq <= max_rsq: break pos = galsim.PositionD(x, y) # We also need the position in pixels to determine where to place the postage # stamp on the full image. image_pos = wcs.toImage(pos) # For even-sized postage stamps, the nominal center (returned by stamp.bounds.center()) # cannot be at the true center (returned by stamp.bounds.trueCenter()) of the postage # stamp, since the nominal center values have to be integers. Thus, the nominal center # is 1/2 pixel up and to the right of the true center. # If we used odd-sized postage stamps, we wouldn't need to do this. x_nominal = image_pos.x + 0.5 y_nominal = image_pos.y + 0.5 # Get the integer values of these which will be the actual nominal center of the # postage stamp image. ix_nominal = int(math.floor(x_nominal + 0.5)) iy_nominal = int(math.floor(y_nominal + 0.5)) # The remainder will be accounted for in an offset when we draw. dx = x_nominal - ix_nominal dy = y_nominal - iy_nominal offset = galsim.PositionD(dx, dy) # Draw the flux from a power law distribution: N(f) ~ f^-1.5 # For this, we use the class DistDeviate which can draw deviates from an arbitrary # probability distribution. This distribution can be defined either as a functional # form as we do here, or as tabulated lists of x and p values, from which the # function is interpolated. flux_dist = galsim.DistDeviate(ud, function=lambda x: x**-1.5, x_min=gal_flux_min, x_max=gal_flux_max) flux = flux_dist() # We introduce here another surface brightness profile, called InclinedExponential. # It represents a typical 3D galaxy disk profile inclined at an arbitrary angle # relative to face on. # # inclination = 0 degrees corresponds to a face-on disk, which is equivalent to # the regular Exponential profile. # inclination = 90 degrees corresponds to an edge-on disk. # # A random orientation corresponds to the inclination angle taking the probability # distribution: # # P(inc) = 0.5 sin(inc) # # so we again use a DistDeviate to generate these values. inc_dist = galsim.DistDeviate(ud, function=lambda x: 0.5 * math.sin(x), x_min=0, x_max=math.pi) inclination = inc_dist() * galsim.radians # The parameters scale_radius and scale_height give the scale distances in the # 3D distribution: # # I(R,z) = I_0 / (2 scale_height) * sech^2(z/scale_height) * exp(-r/scale_radius) # # These values can be given separately if desired. However, it is often easier to # give the ratio scale_h_over_r as an independent value, since the radius and height # values are correlated, while h/r is approximately independent of h or r. h_over_r = ud() * (gal_h_over_r_max - gal_h_over_r_min) + gal_h_over_r_min radius = ud() * (gal_r_max - gal_r_min) + gal_r_min # The inclination is around the x-axis, so we want to rotate the galaxy by a # random angle. theta = ud() * math.pi * 2. * galsim.radians # Make the galaxy profile with these values: gal = galsim.InclinedExponential(scale_radius=radius, scale_h_over_r=h_over_r, inclination=inclination, flux=flux) gal = gal.rotate(theta) # Now apply the appropriate lensing effects for this position from # the NFW halo mass. try: g1, g2 = nfw.getShear(pos, nfw_z_source) nfw_shear = galsim.Shear(g1=g1, g2=g2) except: # This shouldn't happen, since we exclude the inner 10 arcsec, but it's a # good idea to use the try/except block here anyway. import warnings warnings.warn( "Warning: NFWHalo shear is invalid -- probably strong lensing! " + "Using shear = 0.") nfw_shear = galsim.Shear(g1=0, g2=0) nfw_mu = nfw.getMagnification(pos, nfw_z_source) if nfw_mu < 0: import warnings warnings.warn( "Warning: mu < 0 means strong lensing! Using mu=25.") nfw_mu = 25 elif nfw_mu > 25: import warnings warnings.warn( "Warning: mu > 25 means strong lensing! Using mu=25.") nfw_mu = 25 # Calculate the total shear to apply # Since shear addition is not commutative, it is worth pointing out that # the order is in the sense that the second shear is applied first, and then # the first shear. i.e. The field shear is taken to be behind the cluster. # Kind of a cosmic shear contribution between the source and the cluster. # However, this is not quite the same thing as doing: # gal.shear(field_shear).shear(nfw_shear) # since the shear addition ignores the rotation that would occur when doing the # above lines. This is normally ok, because the rotation is not observable, but # it is worth keeping in mind. total_shear = nfw_shear + field_shear # Apply the magnification and shear to the galaxy gal = gal.magnify(nfw_mu) gal = gal.shear(total_shear) # Build the final object final = galsim.Convolve([psf, gal]) # Draw the stamp image # To draw the image at a position other than the center of the image, you can # use the offset parameter, which applies an offset in pixels relative to the # center of the image. # We also need to provide the local wcs at the current position. local_wcs = wcs.local(image_pos) stamp = final.drawImage(wcs=local_wcs, offset=offset) # Recenter the stamp at the desired position: stamp.setCenter(ix_nominal, iy_nominal) # Find overlapping bounds bounds = stamp.bounds & full_image.bounds full_image[bounds] += stamp[bounds] # Also draw the PSF psf_stamp = galsim.ImageF( stamp.bounds) # Use same bounds as galaxy stamp psf.drawImage(psf_stamp, wcs=local_wcs, offset=offset) psf_image[bounds] += psf_stamp[bounds] # Add the truth information for this object to the truth catalog row = ((first_obj_id + k), halo_id, flux, radius, h_over_r, inclination.rad(), theta.rad(), nfw_mu, nfw_z_source, total_shear.g1, total_shear.g2, pos.x, pos.y, image_pos.x, image_pos.y, mass, nfw_conc, nfw_z_halo) truth_cat.addRow(row) # Add Poisson noise to the full image # Note: The normal calculation of Poission noise isn't quite correct right now. # The pixel area is variable, which means the amount of sky flux that enters each # pixel is also variable. The wcs classes have a function `makeSkyImage` which # will fill an image with the correct amount of sky flux given the sky level # in units of ADU/arcsec^2. We use the weight image as our work space for this. wcs.makeSkyImage(weight_image, sky_level) # Add this to the current full_image (temporarily). full_image += weight_image # Add Poisson noise, given the current full_image. # The config parser uses a different random number generator for file-level and # image-level values than for the individual objects. This makes it easier to # parallelize the calculation if desired. In fact, this is why we've been adding 1 # to each seed value all along. The seeds for the objects take the values # random_seed+1 .. random_seed+nobj. The seed for the image is just random_seed, # which we built already (below) when we calculated how many objects need to # be in each file. Use the same rng again here, since this is also at image scope. full_image.addNoise(galsim.PoissonNoise(rng)) # Subtract the sky back off. full_image -= weight_image # The weight image is nominally the inverse variance of the pixel noise. However, it is # common to exclude the Poisson noise from the objects themselves and only include the # noise from the sky photons. The variance of the noise is just the sky level, which is # what is currently in the weight_image. (If we wanted to include the variance from the # objects too, then we could use the full_image before we added the PoissonNoise to it.) # So all we need to do now is to invert the values in weight_image. weight_image.invertSelf() # Write the file to disk: galsim.fits.writeMulti( [full_image, badpix_image, weight_image, psf_image], file_name) # And write the truth catalog file truth_cat.write(truth_file_name) t2 = time.time() return t2 - t1
def test_output_catalog(): """Test basic operations on Catalog.""" import time t1 = time.time() names = [ 'float1', 'float2', 'int1', 'int2', 'bool1', 'bool2', 'str1', 'str2', 'str3', 'str4', 'angle', 'posi', 'posd', 'shear' ] types = [ float, float, int, int, bool, bool, str, str, str, str, galsim.Angle, galsim.PositionI, galsim.PositionD, galsim.Shear ] out_cat = galsim.OutputCatalog(names, types) out_cat.addRow([ 1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"', 1.2 * galsim.degrees, galsim.PositionI(5, 6), galsim.PositionD(0.3, -0.4), galsim.Shear(g1=0.2, g2=0.1) ]) out_cat.addRow((2.345, -900, 0.0, 8, False, 0, "bleedin'", '"bereft', 'of', 'life"', 11 * galsim.arcsec, galsim.PositionI(-35, 106), galsim.PositionD(23.5, 55.1), galsim.Shear(e1=-0.1, e2=0.15))) out_cat.addRow([ 3.4560001, 8.e3, -4, 17.0, 1, 0, 'demised!', '"kicked', 'the', 'bucket"', 0.4 * galsim.radians, galsim.PositionI(88, 99), galsim.PositionD(-0.99, -0.88), galsim.Shear() ]) # First the ASCII version out_cat.write(dir='output', file_name='catalog.dat') cat = galsim.Catalog(dir='output', file_name='catalog.dat') np.testing.assert_equal(cat.ncols, 17) np.testing.assert_equal(cat.nobjects, 3) np.testing.assert_equal(cat.isFits(), False) np.testing.assert_almost_equal(cat.getFloat(1, 0), 2.345) np.testing.assert_almost_equal(cat.getFloat(2, 1), 8000.) np.testing.assert_equal(cat.getInt(0, 2), 9) np.testing.assert_equal(cat.getInt(2, 3), 17) np.testing.assert_equal(cat.getInt(2, 4), 1) np.testing.assert_equal(cat.getInt(0, 5), 1) np.testing.assert_equal(cat.get(2, 6), 'demised!') np.testing.assert_equal(cat.get(1, 7), '"bereft') np.testing.assert_equal(cat.get(0, 8), 'to') np.testing.assert_equal(cat.get(2, 9), 'bucket"') np.testing.assert_almost_equal(cat.getFloat(0, 10), 1.2 * galsim.degrees / galsim.radians) np.testing.assert_almost_equal(cat.getInt(1, 11), -35) np.testing.assert_almost_equal(cat.getInt(1, 12), 106) np.testing.assert_almost_equal(cat.getFloat(2, 13), -0.99) np.testing.assert_almost_equal(cat.getFloat(2, 14), -0.88) np.testing.assert_almost_equal(cat.getFloat(0, 15), 0.2) np.testing.assert_almost_equal(cat.getFloat(0, 16), 0.1) # Next the FITS version out_cat.write(dir='output', file_name='catalog.fits') cat = galsim.Catalog(dir='output', file_name='catalog.fits') np.testing.assert_equal(cat.ncols, 17) np.testing.assert_equal(cat.nobjects, 3) np.testing.assert_equal(cat.isFits(), True) np.testing.assert_almost_equal(cat.getFloat(1, 'float1'), 2.345) np.testing.assert_almost_equal(cat.getFloat(2, 'float2'), 8000.) np.testing.assert_equal(cat.getInt(0, 'int1'), 9) np.testing.assert_equal(cat.getInt(2, 'int2'), 17) np.testing.assert_equal(cat.getInt(2, 'bool1'), 1) np.testing.assert_equal(cat.getInt(0, 'bool2'), 1) np.testing.assert_equal(cat.get(2, 'str1'), 'demised!') np.testing.assert_equal(cat.get(1, 'str2'), '"bereft') np.testing.assert_equal(cat.get(0, 'str3'), 'to') np.testing.assert_equal(cat.get(2, 'str4'), 'bucket"') np.testing.assert_almost_equal(cat.getFloat(0, 'angle.rad'), 1.2 * galsim.degrees / galsim.radians) np.testing.assert_equal(cat.getInt(1, 'posi.x'), -35) np.testing.assert_equal(cat.getInt(1, 'posi.y'), 106) np.testing.assert_almost_equal(cat.getFloat(2, 'posd.x'), -0.99) np.testing.assert_almost_equal(cat.getFloat(2, 'posd.y'), -0.88) np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g1'), 0.2) np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g2'), 0.1) # Check pickling do_pickle(out_cat) out_cat2 = galsim.OutputCatalog(names, types) # No data. do_pickle(out_cat2) t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
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 choose 40% of the galaxies to use the images, and the other 60% to use the parametric fits - The real galaxy images include some initial correlated noise from the original HST observation. However, we whiten the noise of the final image so the final image has stationary Gaussian noise, rather than correlated noise. """ global logger logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) 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) global center_ra center_ra = 19.3 * galsim.hours # The RA, Dec of the center of the image on the sky global center_dec center_dec = -33.1 * galsim.degrees global exp_time exp_time = 300 global sky_bkg # mean sky background from AG's paper sky_bkg = 0.32 # ADU / s / pix global sky_sigma # standard deviation of sky background sky_sigma = 0.16 # ADU / s / pix global nobj nobj = 22 # number of galaxies in entire field global nstars nstars = 300 # number of stars in the entire field global flux_scaling global tel_diam tel_diam = 0.5 global lam lam = 625 # Central wavelength for Airy disk global optics psf_path = '/Users/jemcclea/Research/GalSim/examples/data/fpsc_flight_jitter_psf_oversampled_fixed_10x' global optics # will store the Zernicke component of the PSF global nfw # will store the NFWHalo information global cosmos_cat # will store the COSMOS catalog from which we draw objects global example_cat # also a COSMOS catalog which will contain cluster galaxies # 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 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 cat_file_name = 'real_galaxy_catalog_25.2.fits' dir = 'data/COSMOS_25.2_training_sample/' cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir) logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects) # Also read in example catalog example_cat_file_name = 'data/real_galaxy_catalog_23.5_example.fits' example_cat = galsim.COSMOSCatalog(example_cat_file_name) # The catalog returns objects that are appropriate for HST in 1 second exposures. So for our # telescope we scale up by the relative area, exposure time and pixel scale hst_eff_area = 2.4**2 * (1. - 0.33**2) sbit_eff_area = tel_diam**2 * (1. - 0.380**2) flux_scaling = (sbit_eff_area / hst_eff_area) * exp_time * (pixel_scale / .05)**2 ### Now create PSF. First, define Zernicke polynomial component lam_over_diam = lam * 1.e-9 / tel_diam # radians lam_over_diam *= 206265 # arcsec aberrations = [0.0] * 12 # Set the initial size. aberrations[4] = -0.00725859 # Noll index 4 = Defocus aberrations[5:7] = [0.0, -0.00] # Noll index 5,6 = Astigmatism aberrations[7:9] = [0.07, 0.00] # Noll index 7,8 = Coma aberrations[11] = 0.00133254 # Noll index 11 = Spherical logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam) optics = galsim.OpticalPSF(lam_over_diam, obscuration=0.380, aberrations=aberrations) logger.info('Made telescope PSF profile') ### ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES ### all_psfs = glob.glob(psf_path + "/*247530*.psf") # this is 121s logger.info('Beginning loop over jitter/optical psfs') for psf_filen in all_psfs: logger.info('Beginning PSF %s...' % psf_filen) for i in numpy.arange(1, 2): logger.info('Beginning loop %d' % i) random_seed = 23058923781 rng = galsim.BaseDeviate(random_seed) # This is specific to Javier mock PSFs try: root = psf_filen.split('data/')[1].split('/')[0] timescale = psf_filen.split('_10x/')[1].split('.')[0] outname = ''.join([ 'mock_superbit_', root, timescale, str(i).zfill(3), '.fits' ]) truth_file_name = ''.join([ './output/truth_', root, timescale, str(i).zfill(3), '.dat' ]) file_name = os.path.join('output', outname) except: 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' ] types = [ int, 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) sky_level = exp_time * sky_bkg 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(image_xsize, image_ysize) noise_image.setOrigin(0, 0) # Make a slightly non-trivial WCS. We'll use a slightly rotated coordinate system # and center it at the image center. theta = 0.17 * galsim.degrees dudx = numpy.cos(theta) * pixel_scale dudy = -numpy.sin(theta) * pixel_scale dvdx = numpy.sin(theta) * pixel_scale dvdy = numpy.cos(theta) * pixel_scale image_center = full_image.true_center affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center) sky_center = galsim.CelestialCoord(ra=center_ra, dec=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_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 stamp, truth = make_a_galaxy(ud=ud, wcs=wcs, psf=psf, affine=affine) # 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_catalog.addRow(row) except: logger.info('Galaxy %d has failed, skipping...', k) pdb.set_trace() ###### Inject cluster galaxy objects: random_seed = 892465352 for k in range(50): 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, psf=psf, affine=affine) # 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_catalog.addRow(row) except: logger.info('Cluster galaxy %d has failed, skipping...', k) pdb.set_trace() #### ### Now repeat process for stars! #### random_seed_stars = 2308173501873 for k in range(nstars): time1 = time.time() ud = galsim.UniformDeviate(random_seed_stars + k + 1) 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. 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_catalog.addRow(row) except: logger.info('Star %d has failed, skipping...', k) pass # 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. # # The first thing to do is to make the Gaussian noise uniform across the whole image. max_current_variance = numpy.max(noise_image.array) noise_image = max_current_variance - noise_image vn = galsim.VariableGaussianNoise(rng, noise_image) full_image.addNoise(vn) # Now max_current_variance is the noise level across the full image. We don't want to add that # twice, so subtract off this much from the intended noise that we want to end up in the image. sky_sigma -= numpy.sqrt(max_current_variance) # Regardless of galaxy type, add Gaussian noise with this variance to the final image. this_noise_sigma = sky_sigma * exp_time noise = galsim.GaussianNoise(rng, sigma=this_noise_sigma) full_image.addNoise(noise) logger.debug('Added noise to final output image') 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 for psf %s', i, psf_filen) i = i + 1 logger.info(' ') logger.info(' ') logger.info('completed all images') logger.info(' ')
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. - The real galaxy images include some initial correlated noise from the original HST observation. However, we whiten the noise of the final image so the final image has stationary Gaussian noise, rather than correlated noise. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) 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) global center_ra center_ra = 19.3*galsim.hours # The RA, Dec of the center of the image on the sky global center_dec center_dec = -33.1*galsim.degrees global nobj nobj = 30 # number of galaxies in entire field; this number matches empirical global nstars nstars = 1000 # 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 psf_fwhm psf_fwhm = 0.30 global lam lam = 625 # Central wavelength for an airy disk global exp_time exp_time = 300 global noise_variance global sky_level psf_path = '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/outputs/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 --> correct! nfw_z_source = 0.6 # redshift of the lensed sources; COSMOS galaxies don't have any 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 """ cat_file_name = 'real_galaxy_catalog_23.5.fits' dir = 'data/COSMOS_23.5_training_sample' #cat_file_name = 'real_galaxy_catalog_23.5_example.fits' #dir = 'data' """ cat_file_name = 'real_galaxy_catalog_25.2.fits' dir = 'data/COSMOS_25.2_training_sample/' cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir) logger.info('Read in %d 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 * (1.-0.33**2) sbit_eff_area = tel_diam**2 * (1.-0.3840**2) #sbit_eff_area = tel_diam**2 * (1.-0.1**2) ### ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES ### #all_psfs=glob.glob(psf_path+"/*.psf") #all_psfs=glob.glob(psf_path+"/*300*.psf") random_seed = 35609377914 i=0 for psf_filen in range(1): logger.info('Beginning PSF %s...'% psf_filen) rng = galsim.BaseDeviate(random_seed) timescale=str(exp_time) outname=''.join(['debug_0.3FWHM_gaussStar_',timescale,'_',str(i),'.fits']) truth_file_name=''.join(['./output-debug/truth_0.3FWHM_gaussStar_',timescale,'_',str(i),'.dat']) file_name = os.path.join('output-debug',outname) # Set up the image: if timescale=='150': print("Automatically detecting a 150s exposure image, setting flux scale and noise accordingly") #noise_variance=570 # ADU^2 (Just use simple Gaussian noise here.) noise_variance=570 # ADU^2 (Just use simple Gaussian noise here.) sky_level = 51 # ADU exp_time=150. else: print("Automatically detecting a 300s exposure image, setting flux scale and noise accordingly") #noise_variance=400 # ADU^2 (Just use simple Gaussian noise here.) noise_variance=400 # ADU^2 (Just use simple Gaussian noise here.) sky_level = 106 # ADU exp_time=300. flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time * 3.33 * (.206/.05)**2 # Setting up a truth catalog names = [ 'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_nopsf', 'g2_nopsf','g1_meas', 'g2_meas', 'fwhm','final_sigmaSize', 'nopsf_sigmaSize','nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift','flux', 'stamp_sum', 'noisevar'] types = [ int, float, float, float, 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(image_xsize, image_ysize) 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(image_xsize, image_ysize) noise_image.setOrigin(0,0) # Make a slightly non-trivial WCS. We'll use a slightly rotated coordinate system # and center it at the image center. theta = 0.17 * galsim.degrees dudx = numpy.cos(theta) * pixel_scale dudy = -numpy.sin(theta) * pixel_scale dvdx = numpy.sin(theta) * pixel_scale dvdy = numpy.cos(theta) * pixel_scale image_center = full_image.true_center affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center) sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec) wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec) full_image.wcs = wcs # 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 %d...",k) stamp,truth = make_a_galaxy(ud=ud,wcs=wcs,affine=affine) logger.debug("stamp %d is made",k) # 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] logger.debug("stamp %d added to full image",k) time2 = time.time() tot_time = time2-time1 logger.info('Galaxy %d positioned relative to center t=%f s', k, tot_time) try: g1_real=stamp.FindAdaptiveMom().observed_shape.g1 g2_real=stamp.FindAdaptiveMom().observed_shape.g2 except: g1_real=-9999. g2_real=-9999. logger.debug("Galaxy %d made it past g1/g2_real stage",k) sum_flux=numpy.sum(stamp.array) row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1_nopsf, truth.g2_nopsf, g1_real, g2_real, truth.fwhm, truth.final_sigmaSize, truth.nopsf_sigmaSize,truth.g1,truth.g2, truth.mu, truth.z, truth.flux, sum_flux, truth.variance] truth_catalog.addRow(row) logger.debug("row for galaxy %d added to truth catalog\n\n",k) except: logger.info('Galaxy %d has failed, skipping...',k) #pdb.set_trace() pass ###### Inject cluster galaxy objects: random_seed=892465352 center_coords = galsim.CelestialCoord(center_ra,center_dec) centerpix = wcs.toImage(center_coords) for k in range(40): 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,centerpix=centerpix) # 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) except: logger.info('Cluster galaxy %d has failed, skipping...',k) pdb.set_trace() #### ### Now repeat process for stars! #### random_seed_stars=2308173501873 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,affine=affine) bounds = star_stamp.bounds & full_image.bounds logger.debug("star stamp & truth catalog made for star %d" %k) # 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) try: g1_real=star_stamp.FindAdaptiveMom().observed_shape.g1 g2_real=star_stamp.FindAdaptiveMom().observed_shape.g2 except: g1_real = -9999. g2_real = -9999. this_var = -9999. sum_flux=numpy.sum(star_stamp.array) row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1_nopsf, truth.g2_nopsf, g1_real, g2_real, truth.fwhm, truth.final_sigmaSize, truth.nopsf_sigmaSize, truth.g1, truth.g2, truth.mu, truth.z, truth.flux, sum_flux, truth.variance] truth_catalog.addRow(row) except: logger.info('Star %d has failed, skipping...',k) pdb.set_trace() # We already have some noise in the image, but it isn't uniform. So the first thing to do is # to make the Gaussian noise uniform across the whole image. #max_current_variance = numpy.max(noise_image.array) #noise_image = max_current_variance - noise_image vn = galsim.VariableGaussianNoise(rng, noise_image) full_image.addNoise(vn) # Now max_current_variance is the noise level across the full image. We don't want to add that # twice, so subtract off this much from the intended noise that we want to end up in the image. #noise_variance -= max_current_variance # Now add Gaussian noise with this variance to the final image. noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance)) 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')
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(' ')
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(' ')
def test_output_catalog(): """Test basic operations on Catalog.""" names = [ 'float1', 'float2', 'int1', 'int2', 'bool1', 'bool2', 'str1', 'str2', 'str3', 'str4', 'angle', 'posi', 'posd', 'shear' ] types = [ float, 'f8', int, 'i4', bool, 'bool', str, 'str', 'S', 'S0', galsim.Angle, galsim.PositionI, galsim.PositionD, galsim.Shear ] out_cat = galsim.OutputCatalog(names, types) row1 = (1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"', 1.2 * galsim.degrees, galsim.PositionI(5, 6), galsim.PositionD(0.3, -0.4), galsim.Shear(g1=0.2, g2=0.1)) row2 = (2.345, -900, 0.0, 8, False, 0, "bleedin'", '"bereft', 'of', 'life"', 11 * galsim.arcsec, galsim.PositionI(-35, 106), galsim.PositionD(23.5, 55.1), galsim.Shear(e1=-0.1, e2=0.15)) row3 = (3.4560001, 8.e3, -4, 17.0, 1, 0, 'demised!', '"kicked', 'the', 'bucket"', 0.4 * galsim.radians, galsim.PositionI(88, 99), galsim.PositionD(-0.99, -0.88), galsim.Shear()) out_cat.addRow(row1) out_cat.addRow(row2) out_cat.addRow(row3) assert out_cat.names == out_cat.getNames() == names assert out_cat.types == out_cat.getTypes() == types assert len(out_cat) == out_cat.getNObjects() == out_cat.nobjects == 3 assert out_cat.getNCols() == out_cat.ncols == len(names) # Can also set the types after the fact. # MJ: I think this used to be used by the "truth" catalog extra output. # But it doesn't seem to be used there anymore. Probably not by anything then. # I'm not sure how useful it is, I guess it doesn't hurt to leave it in. out_cat2 = galsim.OutputCatalog(names) assert out_cat2.types == [float] * len(names) out_cat2.setTypes(types) assert out_cat2.types == out_cat2.getTypes() == types # Another feature that doesn't seem to be used anymore is you can add the rows out of order # and just give a key to use for sorting at the end. out_cat2.addRow(row3, 3) out_cat2.addRow(row1, 1) out_cat2.addRow(row2, 2) # Check ASCII round trip out_cat.write(dir='output', file_name='catalog.dat') cat = galsim.Catalog(dir='output', file_name='catalog.dat') np.testing.assert_equal(cat.ncols, 17) np.testing.assert_equal(cat.nobjects, 3) np.testing.assert_equal(cat.isFits(), False) np.testing.assert_almost_equal(cat.getFloat(1, 0), 2.345) np.testing.assert_almost_equal(cat.getFloat(2, 1), 8000.) np.testing.assert_equal(cat.getInt(0, 2), 9) np.testing.assert_equal(cat.getInt(2, 3), 17) np.testing.assert_equal(cat.getInt(2, 4), 1) np.testing.assert_equal(cat.getInt(0, 5), 1) np.testing.assert_equal(cat.get(2, 6), 'demised!') np.testing.assert_equal(cat.get(1, 7), '"bereft') np.testing.assert_equal(cat.get(0, 8), 'to') np.testing.assert_equal(cat.get(2, 9), 'bucket"') np.testing.assert_almost_equal(cat.getFloat(0, 10), 1.2 * galsim.degrees / galsim.radians) np.testing.assert_almost_equal(cat.getInt(1, 11), -35) np.testing.assert_almost_equal(cat.getInt(1, 12), 106) np.testing.assert_almost_equal(cat.getFloat(2, 13), -0.99) np.testing.assert_almost_equal(cat.getFloat(2, 14), -0.88) np.testing.assert_almost_equal(cat.getFloat(0, 15), 0.2) np.testing.assert_almost_equal(cat.getFloat(0, 16), 0.1) # Check FITS round trip out_cat.write(dir='output', file_name='catalog.fits') cat = galsim.Catalog(dir='output', file_name='catalog.fits') np.testing.assert_equal(cat.ncols, 17) np.testing.assert_equal(cat.nobjects, 3) np.testing.assert_equal(cat.isFits(), True) np.testing.assert_almost_equal(cat.getFloat(1, 'float1'), 2.345) np.testing.assert_almost_equal(cat.getFloat(2, 'float2'), 8000.) np.testing.assert_equal(cat.getInt(0, 'int1'), 9) np.testing.assert_equal(cat.getInt(2, 'int2'), 17) np.testing.assert_equal(cat.getInt(2, 'bool1'), 1) np.testing.assert_equal(cat.getInt(0, 'bool2'), 1) np.testing.assert_equal(cat.get(2, 'str1'), 'demised!') np.testing.assert_equal(cat.get(1, 'str2'), '"bereft') np.testing.assert_equal(cat.get(0, 'str3'), 'to') np.testing.assert_equal(cat.get(2, 'str4'), 'bucket"') np.testing.assert_almost_equal(cat.getFloat(0, 'angle.rad'), 1.2 * galsim.degrees / galsim.radians) np.testing.assert_equal(cat.getInt(1, 'posi.x'), -35) np.testing.assert_equal(cat.getInt(1, 'posi.y'), 106) np.testing.assert_almost_equal(cat.getFloat(2, 'posd.x'), -0.99) np.testing.assert_almost_equal(cat.getFloat(2, 'posd.y'), -0.88) np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g1'), 0.2) np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g2'), 0.1) # The one that was made out of order should write the same file. out_cat2.write(dir='output', file_name='catalog2.fits') cat2 = galsim.Catalog(dir='output', file_name='catalog2.fits') np.testing.assert_array_equal(cat2.data, cat.data) assert cat2 != cat # Because file_name is different. # Check that it properly overwrites an existing output file. out_cat.addRow([ 1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"', 1.2 * galsim.degrees, galsim.PositionI(5, 6), galsim.PositionD(0.3, -0.4), galsim.Shear(g1=0.2, g2=0.1) ]) assert out_cat.rows[3] == out_cat.rows[0] out_cat.write(dir='output', file_name='catalog.fits') # Same name as above. cat2 = galsim.Catalog(dir='output', file_name='catalog.fits') np.testing.assert_equal(cat2.ncols, 17) np.testing.assert_equal(cat2.nobjects, 4) for key in names[:10]: assert cat2.data[key][3] == cat2.data[key][0] # Check pickling do_pickle(out_cat) out_cat2 = galsim.OutputCatalog(names, types) # No data. do_pickle(out_cat2) # Check errors with assert_raises(galsim.GalSimValueError): out_cat.addRow((1, 2, 3)) # Wrong length with assert_raises(galsim.GalSimValueError): out_cat.write(dir='output', file_name='catalog.txt', file_type='invalid')
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 choose 40% of the galaxies to use the images, and the other 60% to use the parametric fits - The real galaxy images include some initial correlated noise from the original HST observation. However, we whiten the noise of the final image so the final image has stationary Gaussian noise, rather than correlated noise. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("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) global center_ra #center_ra = 19.3*galsim.hours # The RA, Dec of the center of the image on the sky global center_dec #center_dec = -33.1*galsim.degrees global nobj nobj = 1700 # number of galaxies in entire field -- an adjustment to ensure ~1100 detections global nstars nstars = 370 # 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 = 587 # Central wavelength global exp_time global noise_variance global sky_level psf_path = '/Users/jemcclea/Research/SuperBIT_2019/superbit-ngmix/scripts/outputs/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.3 # 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 if True: # The catalog we distribute with the GalSim code only has 100 galaxies. # The galaxies will typically be reused several times here. cat_file_name = 'real_galaxy_catalog_23.5_example.fits' dir = 'data' cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir) else: # If you've run galsim_download_cosmos, you can leave out the cat_file_name and dir # to use the full COSMOS catalog with 56,000 galaxies in it. cosmos_cat = galsim.COSMOSCatalog() logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects) # 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. hst_eff_area = 2.4**2 * (1. - 0.33**2) sbit_eff_area = tel_diam**2 * (1. - 0.3840**2) #flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time ### ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES ### all_psfs = glob.glob(psf_path + "/*150*.psf") logger.info('Beginning loop over jitter/optical psfs') # random_seed = 24783923 random_seed = 247 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_empiricalPSF_', timescale, '_', str(i), '.fits']) truth_file_name = ''.join( ['./output/truth_empiricalPSF_', 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 noise accordingly" ) noise_variance = 1.8e3 # ADU^2 (Just use simple Gaussian noise here.) -->150s sky_level = 51 # ADU / arcsec^2 -->150s exp_time = 150. else: print( "Automatically detecting a 300s exposure image, setting flux scale and noise accordingly" ) noise_variance = 2.55e3 # ADU^2 (Just use simple Gaussian noise here.) -->300s sky_level = 106 # ADU / arcsec^2 -->300s exp_time = 300. # Setting up a truth catalog names = [ 'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas', 'g2_meas', 'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift', 'flux', 'var' ] types = [ int, 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.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(image_xsize, image_ysize) noise_image.setOrigin(0, 0) # Make a slightly non-trivial WCS. We'll use a slightly rotated coordinate system # and center it at the image center. """ theta = 0.17 * galsim.degrees dudx = numpy.cos(theta) * pixel_scale dudy = -numpy.sin(theta) * pixel_scale dvdx = numpy.sin(theta) * pixel_scale dvdy = numpy.cos(theta) * pixel_scale image_center = full_image.true_center affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center) sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec) wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec) """ this_im_wcs = get_wcs_info(psf_filen) affine = this_im_wcs.affine(full_image.true_center) full_image.wcs = this_im_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 = this_im_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 stamp, truth = make_a_galaxy(ud=ud, this_im_wcs=this_im_wcs, psf=psf, affine=affine) # 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) g1_real = stamp.FindAdaptiveMom().observed_shape.g1 g2_real = stamp.FindAdaptiveMom().observed_shape.g2 #g1_real=-9999. #g2_real=-9999. row = [ k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real, truth.g1, truth.g2, truth.mu, truth.z, truth.flux, truth.variance ] truth_catalog.addRow(row) except: logger.info('Galaxy %d has failed, skipping...', k) #pdb.set_trace() pass #### ### 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) star_stamp, truth = make_a_star(ud=ud, this_im_wcs=this_im_wcs, psf=psf, affine=affine) 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) g1_real = star_stamp.FindAdaptiveMom().observed_shape.g1 g2_real = star_stamp.FindAdaptiveMom().observed_shape.g2 #g1_real = -9999. #g2_real = -9999. this_var = -9999. this_flux = numpy.sum(star_stamp.array) row = [ k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real, truth.g1, truth.g2, truth.mu, truth.z, this_flux, this_var ] truth_catalog.addRow(row) except: logger.info('Star %d has failed, skipping...', k) pass # We already have some noise in the image, but it isn't uniform. So the first thing to do is # to make the Gaussian noise uniform across the whole image. max_current_variance = numpy.max(noise_image.array) noise_image = max_current_variance - noise_image vn = galsim.VariableGaussianNoise(rng, noise_image) full_image.addNoise(vn) # Now max_current_variance is the noise level across the full image. We don't want to add that # twice, so subtract off this much from the intended noise that we want to end up in the image. noise_variance -= max_current_variance # Now add Gaussian noise with this variance to the final image. try: noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance)) except: noise = galsim.GaussianNoise(rng, sigma=math.sqrt(1800)) full_image.addNoise(noise) logger.info('Added noise to final large image') # Now write the image to disk. It is automatically compressed with Rice compression, # since the filename we provide ends in .fz. full_image.write(file_name) logger.info('Wrote image to %r', file_name) # Write truth catalog to file. truth_catalog.write(truth_file_name) # Compute some sky positions of some of the pixels to compare with the values of RA, Dec # that ds9 reports. ds9 always uses (1,1) for the lower left pixel, so the pixel coordinates # of these pixels are different by 1, but you can check that the RA and Dec values are # the same as what GalSim calculates. i = i + 1 logger.info(' ') logger.info('completed run %d for psf %s', i, psf_filen) logger.info('completed all images')
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')
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) global center_ra center_ra = 19.3*galsim.hours # The RA, Dec of the center of the image on the sky global center_dec center_dec = -33.1*galsim.degrees global nobj nobj = 2200 # number of galaxies in entire field global nstars nstars = 350 # 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 cat_file_name = 'real_galaxy_catalog_23.5.fits' dir = 'data/COSMOS_23.5_training_sample' cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir) logger.info('Read in %d 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 * (1.-0.33**2) sbit_eff_area = tel_diam**2 * (1.-0.3840**2) ### ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES ### all_psfs=glob.glob(psf_path+"/*300*.psf") logger.info('Beginning loop over jitter/optical psfs') random_seed = 4783923 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_bp_empiricalPSF_',timescale,'_',str(i),'.fits']) truth_file_name=''.join(['./output-bandpass/truth_bp_empiricalPSF_',timescale,'_',str(i),'.dat']) file_name = os.path.join('output-bandpass',outname) # Set up the image: if timescale=='150': print("Automatically detecting a 150s exposure image, setting flux scale and noise accordingly") noise_variance=570 # ADU^2 (Just use simple Gaussian noise here.) sky_level = 51 # ADU exp_time=150. else: print("Automatically detecting a 300s exposure image, setting flux scale and noise accordingly") noise_variance=800 # ADU^2 (Just use simple Gaussian noise here.) sky_level = 106 # ADU exp_time=300. flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time *(.206/.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.fill(sky_level) full_image.setOrigin(0,0) # We keep track of how much noise is already in the image # This is only relevant if "RealGalaxy" type is used noise_image = galsim.ImageF(image_xsize, image_ysize) noise_image.setOrigin(0,0) # Make a slightly non-trivial WCS. We'll use a slightly rotated coordinate system # and center it at the image center. theta = 0.0 * galsim.degrees dudx = numpy.cos(theta) * pixel_scale dudy = -numpy.sin(theta) * pixel_scale dvdx = numpy.sin(theta) * pixel_scale dvdy = numpy.cos(theta) * pixel_scale image_center = full_image.true_center affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center) sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec) wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec) 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) logger.debug("stamp is made") # 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. # Uncomment if using "real" COSMOS galaxy type #noise_image[bounds] += truth.variance # 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. ##### random_seed=892375351 center_coords = galsim.CelestialCoord(center_ra,center_dec) centerpix = wcs.toImage(center_coords) n_cluster_gals = 30 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, centerpix=centerpix,cluster_cat=cluster_cat) # 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. This is more relevant to # "real" galaxy images, not parametric like we have #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_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() # We already have some noise in the image, but it isn't uniform. So the first thing to do is # to make the Gaussian noise uniform across the whole image. #max_current_variance = numpy.max(noise_image.array) #noise_image = max_current_variance - noise_image vn = galsim.VariableGaussianNoise(rng, noise_image) full_image.addNoise(vn) # Now max_current_variance is the noise level across the full image. We don't want to add that # twice, so subtract off this much from the intended noise that we want to end up in the image. #noise_variance -= max_current_variance # Now add Gaussian noise with this variance to the final image. noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance)) 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')