def roman_psf_rotation(): b = galsim.BoundsI(1,32,1,32) WCS1 = wfirst.getWCS(world_pos = galsim.CelestialCoord(ra=100*galsim.degrees, dec=-10*galsim.degrees), PA = 0.*galsim.radians)[1] WCS2 = wfirst.getWCS(world_pos = galsim.CelestialCoord(ra=100*galsim.degrees, dec=-10*galsim.degrees), PA = 30.*galsim.radians)[1] psf1 = wfirst.getPSF(1, 'H158', wcs=WCS1, pupil_bin=4, wavelength=wfirst.getBandpasses(AB_zeropoint=True)['H158'].effective_wavelength) psf2 = wfirst.getPSF(1, 'H158', wcs=WCS2, pupil_bin=4, wavelength=wfirst.getBandpasses(AB_zeropoint=True)['H158'].effective_wavelength) #star_model1 = galsim.DeltaFunction(flux=1.) #star_model2 = galsim.DeltaFunction(flux=1.) star_stamp1 = galsim.Image(b, scale=wfirst.pixel_scale) star_stamp2 = galsim.Image(b, scale=wfirst.pixel_scale) #star_model1 = galsim.Convolve(star_model1, psf1) #star_model2 = galsim.Convolve(star_model2, psf2) psf1.drawImage(image=star_stamp1) psf2.drawImage(image=star_stamp2) np.savetxt("/hpc/group/cosmology/masaya/wfirst_simulation/paper/roman_psf_PA0b.txt", star_stamp1.array) np.savetxt("/hpc/group/cosmology/masaya/wfirst_simulation/paper/roman_psf_PA30b.txt", star_stamp2.array)
def get_psf_SCA(filter_): all_scas = np.array([i for i in range(1,19)]) all_psfs = [] for sca in all_scas: psf_sca = wfirst.getPSF(sca, filter_, SCA_pos=None, pupil_bin=4, wavelength=wfirst.getBandpasses(AB_zeropoint=True)[filter_].effective_wavelength) all_psfs.append(psf_sca) return all_psfs
def __init__(self, dither, sca, filter_, stamp_size, position_angle, random_angle=False): self.dither = dither self.sca = sca self.filter_ = filter_ self.stamp_size = stamp_size self.random_angle = random_angle self.position_angle = position_angle ## create reference ra, dec self.bpass = wfirst.getBandpasses(AB_zeropoint=True)[self.filter_] d = fio.FITS('observing_sequence_hlsonly_5yr.fits')[-1][self.dither] self.ra = d['ra'] * np.pi / 180. self.dec = d['dec'] * np.pi / 180. self.date = Time(d['date'], format='mjd').datetime
def main(argv): # Where to find and output data. path, filename = os.path.split(__file__) outpath = os.path.abspath(os.path.join(path, "output/")) # Just use a few galaxies, to save time. Note that we are going to put 4000 galaxy images into # our big image, so if we have n_use=10, each galaxy will appear 400 times. Users who want a # more interesting image with greater variation in the galaxy population can change `n_use` to # something larger (but it should be <=100, the number of galaxies in this small example # catalog). With 4000 galaxies in a 4k x 4k image with the Roman pixel scale, the effective # galaxy number density is 74/arcmin^2. This is not the number density that is expected for a # sample that is so bright (I<23.5) but it makes the image more visually interesting. One could # think of it as what you'd get if you added up several images at once, making the images for a # sample that is much deeper have the same S/N as that for an I<23.5 sample in a single image. n_use = 10 n_tot = 4000 # Default is to use all filters. Specify e.g. 'YJH' to only do Y106, J129, and H158. use_filters = None # quick and dirty command line parsing. for var in argv: if var.startswith('data='): datapath = var[5:] if var.startswith('out='): outpath = var[4:] if var.startswith('nuse='): n_use = int(var[5:]) if var.startswith('ntot='): n_tot = int(var[5:]) if var.startswith('filters='): use_filters = var[8:].upper() # Make output directory if not already present. if not os.path.isdir(outpath): os.mkdir(outpath) # In non-script code, use getLogger(__name__) at module scope instead. logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo13") # Initialize (pseudo-)random number generator. random_seed = 123456 rng = galsim.BaseDeviate(random_seed) # Generate a Poisson noise model. poisson_noise = galsim.PoissonNoise(rng) logger.info('Poisson noise model created.') # Read in the Roman filters, setting an AB zeropoint appropriate for this telescope given its # diameter and (since we didn't use any keyword arguments to modify this) using the typical # exposure time for Roman images. By default, this routine truncates the parts of the # bandpasses that are near 0 at the edges, and thins them by the default amount. filters = roman.getBandpasses(AB_zeropoint=True) logger.debug('Read in Roman imaging filters.') logger.info('Reading from a parametric COSMOS catalog.') # Read in a galaxy catalog - just a random subsample of 100 galaxies for F814W<23.5 from COSMOS. cat_file_name = 'real_galaxy_catalog_23.5_example_fits.fits' dir = 'data' # Use the routine that can take COSMOS real or parametric galaxy information, and tell it we # want parametric galaxies that represent an I<23.5 sample. cat = galsim.COSMOSCatalog(cat_file_name, dir=dir, use_real=False) logger.info('Read in %d galaxies from catalog' % cat.nobjects) # Here we carry out the initial steps that are necessary to get a fully chromatic PSF. We use # the getPSF() routine in the Roman module, which knows all about the telescope parameters # (diameter, bandpasses, obscuration, etc.). Note that we arbitrarily choose a single SCA # (Sensor Chip Assembly) rather than all of them, for faster calculations, and use a simple # representation of the struts for faster calculations. To do a more exact calculation of the # chromaticity, remove the `n_waves` keyword from the call to # getPSF(): use_SCA = 7 # This could be any number from 1...18 logger.info('Doing expensive pre-computation of PSF.') t1 = time.time() # Need to make a separate PSF for each filter. We are, however, ignoring the # position-dependence of the PSF within each SCA, just using the PSF at the center of the SCA # (default kwargs). PSFs = {} for filter_name, filter_ in filters.items(): logger.info('PSF pre-computation for SCA %d, filter %s.' % (use_SCA, filter_name)) PSFs[filter_name] = roman.getPSF(use_SCA, filter_name, n_waves=10) t2 = time.time() logger.info('Done PSF precomputation in %.1f seconds!' % (t2 - t1)) # Define the size of the postage stamp that we use for each individual galaxy within the larger # image, and for the PSF images. stamp_size = 256 # We choose a particular (RA, dec) location on the sky for our observation. ra_targ = 90. * galsim.degrees dec_targ = -10. * galsim.degrees targ_pos = galsim.CelestialCoord(ra=ra_targ, dec=dec_targ) # Get the WCS for an observation at this position. We are not supplying a date, so the routine # will assume it's the vernal equinox. We are also not supplying a position angle for the # observatory, which means that it will just find the optimal one (the one that has the solar # panels pointed most directly towards the Sun given this targ_pos and date). The output of # this routine is a dict of WCS objects, one for each SCA. We then take the WCS for the SCA # that we are using. wcs_list = roman.getWCS(world_pos=targ_pos, SCAs=use_SCA) wcs = wcs_list[use_SCA] # We need to find the center position for this SCA. We'll tell it to give us a CelestialCoord # corresponding to (X, Y) = (roman.n_pix/2, roman.n_pix/2). SCA_cent_pos = wcs.toWorld( galsim.PositionD(roman.n_pix / 2, roman.n_pix / 2)) # We randomly distribute points in (X, Y) on the CCD. # If we had a real galaxy catalog with positions in terms of RA, dec we could use wcs.toImage() # to find where those objects should be in terms of (X, Y). pos_rng = galsim.UniformDeviate(random_seed) # Make a list of (X, Y, F814W magnitude, n_rot, flip) values. # (X, Y) give the position of the galaxy centroid (or the center of the postage stamp into which # we draw the galaxy) in the big image. # F814W magnitudes are randomly drawn from the catalog, and are used to create a more realistic # flux distribution for the galaxies instead of just having the 10 flux values for the galaxies # we have chosen to draw. # n_rot says how many 90 degree rotations to include for a given realization of each galaxy, so # it doesn't appear completely identical each time we put it in the image. # flip is a random number that will determine whether we include an x-y flip for this appearance # of the galaxy or not. x_stamp = [] y_stamp = [] mag_stamp = [] n_rot_stamp = [] flip_stamp = [] for i_gal in range(n_tot): x_stamp.append(pos_rng() * roman.n_pix) y_stamp.append(pos_rng() * roman.n_pix) # Note that we could use wcs.toWorld() to get the (RA, dec) for these (x, y) positions. Or, # if we had started with (RA, dec) positions, we could have used wcs.toImage() to get the # CCD coordinates for those positions. mag_stamp.append(cat.param_cat['mag_auto'][int(pos_rng() * cat.nobjects)]) n_rot_stamp.append(int(4 * pos_rng())) flip_stamp.append(pos_rng()) # Make the 2-component parametric GSObjects for each object, including chromaticity (roughly # appropriate SEDs per galaxy component, at the appropriate galaxy redshift). Note that since # the PSF is position-independent within the SCA, we can simply do the convolution with that PSF # now instead of using a different one for each position. We also have to include the correct # flux scaling: The catalog returns objects that would be observed by HST in 1 second # exposures. So for our telescope we scale up by the relative area and exposure time. Note that # what is important is the *effective* area after taking into account obscuration. logger.info( 'Processing the objects in the catalog to get GSObject representations' ) # Choose a random set of unique indices in the catalog (will be the same each time script is # run, due to use of the same random seed): rand_indices = [] while len(rand_indices) < n_use: tmp_ind = int(pos_rng() * cat.nobjects) if tmp_ind not in rand_indices: rand_indices.append(tmp_ind) obj_list = cat.makeGalaxy(rand_indices, chromatic=True, gal_type='parametric') hst_eff_area = 2.4**2 * (1. - 0.33**2) roman_eff_area = galsim.roman.diameter**2 * (1. - galsim.roman.obscuration**2) flux_scaling = (roman_eff_area / hst_eff_area) * roman.exptime mag_list = [] for ind in range(len(obj_list)): # First, let's check what magnitude this object has in F814W. We want to do this because # (to inject some variety into our images) we are going to rescale the fluxes in all bands # for different instances of this galaxy in the final image in order to get a reasonable S/N # distribution. So we need to save the original magnitude in F814W, to compare with a # randomly drawn one from the catalog. This is not something that most users would need to # do. mag_list.append( cat.param_cat['mag_auto'][cat.orig_index[rand_indices[ind]]]) # Calculate the sky level for each filter, and draw the PSF and the galaxies through the # filters. for filter_name, filter_ in filters.items(): if use_filters is not None and filter_name[0] not in use_filters: logger.info('Skipping filter {0}.'.format(filter_name)) continue logger.info('Beginning work for {0}.'.format(filter_name)) # Drawing PSF. Note that the PSF object intrinsically has a flat SED, so if we convolve it # with a galaxy, it will properly take on the SED of the galaxy. For the sake of this demo, # we will simply convolve with a 'star' that has a flat SED and unit flux in this band, so # that the PSF image will be normalized to unit flux. This does mean that the PSF image # being drawn here is not quite the right PSF for the galaxy. Indeed, the PSF for the # galaxy effectively varies within it, since it differs for the bulge and the disk. To make # a real image, one would have to choose SEDs for stars and convolve with a star that has a # reasonable SED, but we just draw with a flat SED for this demo. out_filename = os.path.join(outpath, 'demo13_PSF_{0}.fits'.format(filter_name)) # Generate a point source. point = galsim.DeltaFunction(flux=1.) # Use a flat SED here, but could use something else. A stellar SED for instance. # Or a typical galaxy SED. Depending on your purpose for drawing the PSF. star_sed = galsim.SED(lambda x: 1, 'nm', 'flambda').withFlux( 1., filter_) # Give it unit flux in this filter. star = galsim.Convolve(point * star_sed, PSFs[filter_name]) img_psf = galsim.ImageF(64, 64) star.drawImage(bandpass=filter_, image=img_psf, scale=roman.pixel_scale) img_psf.write(out_filename) logger.debug( 'Created PSF with flat SED for {0}-band'.format(filter_name)) # Set up the full image that will contain all the individual galaxy images, with information # about WCS: final_image = galsim.ImageF(roman.n_pix, roman.n_pix, wcs=wcs) # Draw the galaxies into the image. for i_gal in range(n_use): logger.info( 'Drawing image for the object at row %d in the input catalog' % i_gal) # We want to only draw the galaxy once (for speed), not over and over with different # sub-pixel offsets. For this reason we ignore the sub-pixel offset entirely. Note # that we are setting the postage stamp to have the average Roman pixel scale. This is # simply an approximation for the purpose of speed; really, one should draw directly # into final_image, which has the appropriate WCS for Roman. In that case, the image # of the galaxy might look different in different parts of the detector due to the WCS # (including distortion), and we would have to re-draw each time. To keep the demo # relatively quick, we are just using the approximate average pixel scale and drawing # once. stamp = galsim.Image(stamp_size, stamp_size, scale=roman.pixel_scale) # Convolve the chromatic galaxy and the chromatic PSF for this bandpass, and rescale flux. final = galsim.Convolve(flux_scaling * obj_list[ind], PSFs[filter_name]) final.drawImage(filter_, image=stamp) # Have to find where to place it: for i_gal_use in range(i_gal * n_tot // n_use, (i_gal + 1) * n_tot // n_use): # Account for the fractional part of the position: ix = int(math.floor(x_stamp[i_gal_use] + 0.5)) iy = int(math.floor(y_stamp[i_gal_use] + 0.5)) # We don't actually use this offset. offset = galsim.PositionD(x_stamp[i_gal] - ix, y_stamp[i_gal] - iy) # Create a nominal bound for the postage stamp given the integer part of the # position. stamp_bounds = galsim.BoundsI(ix - 0.5 * stamp_size, ix + 0.5 * stamp_size - 1, iy - 0.5 * stamp_size, iy + 0.5 * stamp_size - 1) # Find the overlapping bounds between the large image and the individual postage # stamp. bounds = stamp_bounds & final_image.bounds # Just to inject a bit of variety into the image, so it isn't *quite* as obvious # that we've repeated the same 10 objects over and over, we randomly rotate the # postage stamp by some factor of 90 degrees and possibly include a random flip. if flip_stamp[i_gal_use] > 0.5: new_arr = numpy.ascontiguousarray( numpy.rot90(stamp.array, n_rot_stamp[i_gal_use])) else: new_arr = numpy.ascontiguousarray( numpy.fliplr( numpy.rot90(stamp.array, n_rot_stamp[i_gal_use]))) stamp_rot = galsim.Image(new_arr, scale=stamp.scale) stamp_rot.setOrigin( galsim.PositionI(stamp_bounds.xmin, stamp_bounds.ymin)) # Rescale the flux to match that of a randomly chosen galaxy in the galaxy, but # keeping the same SED as for this particular galaxy. This gives a bit more # variety in the flux values and SNR of the galaxies in the image without having # to render images of many more objects. flux_scaling = 10**(-0.4 * (mag_stamp[i_gal_use] - mag_list[i_gal])) # Copy the image into the right place in the big image. final_image[bounds] += flux_scaling * stamp_rot[bounds] # Now we're done with the per-galaxy drawing for this image. The rest will be done for the # entire image at once. logger.info( 'Postage stamps of all galaxies drawn on a single big image for this filter.' ) logger.info('Adding the sky level, noise and detector non-idealities.') # First we get the amount of zodaical light for a position corresponding to the center of # this SCA. The results are provided in units of e-/arcsec^2, using the default Roman # exposure time since we did not explicitly specify one. Then we multiply this by a factor # >1 to account for the amount of stray light that is expected. If we do not provide a date # for the observation, then it will assume that it's the vernal equinox (sun at (0,0) in # ecliptic coordinates) in 2025. sky_level = roman.getSkyLevel(filters[filter_name], world_pos=SCA_cent_pos) sky_level *= (1.0 + roman.stray_light_fraction) # Make a image of the sky that takes into account the spatially variable pixel scale. Note # that makeSkyImage() takes a bit of time. If you do not care about the variable pixel # scale, you could simply compute an approximate sky level in e-/pix by multiplying # sky_level by roman.pixel_scale**2, and add that to final_image. sky_image = final_image.copy() wcs.makeSkyImage(sky_image, sky_level) # This image is in units of e-/pix. Finally we add the expected thermal backgrounds in this # band. These are provided in e-/pix/s, so we have to multiply by the exposure time. sky_image += roman.thermal_backgrounds[filter_name] * roman.exptime # Adding sky level to the image. final_image += sky_image # Now that all sources of signal (from astronomical objects and background) have been added # to the image, we can start adding noise and detector effects. There is a utility, # galsim.roman.allDetectorEffects(), that can apply ALL implemented noise and detector # effects in the proper order. Here we step through the process and explain these in a bit # more detail without using that utility. # First, we include the expected Poisson noise: final_image.addNoise(poisson_noise) # At this point in the image generation process, an integer number of photons gets # detected, unless any of the pre-noise values were > 2^30. That's when our Poisson # implementation switches over to the Gaussian approximation, which won't necessarily # produce integers. This situation does not arise in practice for this demo, but if it did, # we could use final_image.quantize() to enforce integer pixel values. # The subsequent steps account for the non-ideality of the detectors. # 1) Reciprocity failure: # Reciprocity, in the context of photography, is the inverse relationship between the # incident flux (I) of a source object and the exposure time (t) required to produce a given # response(p) in the detector, i.e., p = I*t. However, in NIR detectors, this relation does # not hold always. The pixel response to a high flux is larger than its response to a low # flux. This flux-dependent non-linearity is known as 'reciprocity failure', and the # approximate amount of reciprocity failure for the Roman detectors is known, so we can # include this detector effect in our images. if diff_mode: # Save the image before applying the transformation to see the difference save_image = final_image.copy() # If we had wanted to, we could have specified a different exposure time than the default # one for Roman, but otherwise the following routine does not take any arguments. roman.addReciprocityFailure(final_image) logger.debug('Included reciprocity failure in {0}-band image'.format( filter_name)) if diff_mode: # Isolate the changes due to reciprocity failure. diff = final_image - save_image out_filename = os.path.join( outpath, 'demo13_RecipFail_{0}.fits'.format(filter_name)) final_image.write(out_filename) out_filename = os.path.join( outpath, 'demo13_diff_RecipFail_{0}.fits'.format(filter_name)) diff.write(out_filename) # 2) Adding dark current to the image: # Even when the detector is unexposed to any radiation, the electron-hole pairs that # are generated within the depletion region due to finite temperature are swept by the # high electric field at the junction of the photodiode. This small reverse bias # leakage current is referred to as 'dark current'. It is specified by the average # number of electrons reaching the detectors per unit time and has an associated # Poisson noise since it is a random event. dark_current = roman.dark_current * roman.exptime dark_noise = galsim.DeviateNoise( galsim.PoissonDeviate(rng, dark_current)) final_image.addNoise(dark_noise) # NOTE: Sky level and dark current might appear like a constant background that can be # simply subtracted. However, these contribute to the shot noise and matter for the # non-linear effects that follow. Hence, these must be included at this stage of the # image generation process. We subtract these backgrounds in the end. # 3) Applying a quadratic non-linearity: # In order to convert the units from electrons to ADU, we must use the gain factor. The gain # has a weak dependency on the charge present in each pixel. This dependency is accounted # for by changing the pixel values (in electrons) and applying a constant nominal gain # later, which is unity in our demo. # Save the image before applying the transformation to see the difference: if diff_mode: save_image = final_image.copy() # Apply the Roman nonlinearity routine, which knows all about the nonlinearity expected in # the Roman detectors. roman.applyNonlinearity(final_image) # Note that users who wish to apply some other nonlinearity function (perhaps for other NIR # detectors, or for CCDs) can use the more general nonlinearity routine, which uses the # following syntax: # final_image.applyNonlinearity(NLfunc=NLfunc) # with NLfunc being a callable function that specifies how the output image pixel values # should relate to the input ones. logger.debug( 'Applied nonlinearity to {0}-band image'.format(filter_name)) if diff_mode: diff = final_image - save_image out_filename = os.path.join( outpath, 'demo13_NL_{0}.fits'.format(filter_name)) final_image.write(out_filename) out_filename = os.path.join( outpath, 'demo13_diff_NL_{0}.fits'.format(filter_name)) diff.write(out_filename) # Save this image to do the diff after applying IPC. save_image = final_image.copy() # 4) Including Interpixel capacitance: # The voltage read at a given pixel location is influenced by the charges present in the # neighboring pixel locations due to capacitive coupling of sense nodes. This interpixel # capacitance effect is modeled as a linear effect that is described as a convolution of a # 3x3 kernel with the image. The Roman IPC routine knows about the kernel already, so the # user does not have to supply it. roman.applyIPC(final_image) logger.debug('Applied interpixel capacitance to {0}-band image'.format( filter_name)) if diff_mode: # Isolate the changes due to the interpixel capacitance effect. diff = final_image - save_image out_filename = os.path.join( outpath, 'demo13_IPC_{0}.fits'.format(filter_name)) final_image.write(out_filename) out_filename = os.path.join( outpath, 'demo13_diff_IPC_{0}.fits'.format(filter_name)) diff.write(out_filename) # 5) Adding read noise: # Read noise is the noise due to the on-chip amplifier that converts the charge into an # analog voltage. We already applied the Poisson noise due to the sky level, so read noise # should just be added as Gaussian noise: read_noise = galsim.GaussianNoise(rng, sigma=roman.read_noise) final_image.addNoise(read_noise) logger.debug('Added readnoise to {0}-band image'.format(filter_name)) # We divide by the gain to convert from e- to ADU. Currently, the gain value in the Roman # module is just set to 1, since we don't know what the exact gain will be, although it is # expected to be approximately 1. Eventually, this may change when the camera is assembled, # and there may be a different value for each SCA. For now, there is just a single number, # which is equal to 1. final_image /= roman.gain # Finally, the analog-to-digital converter reads in an integer value. final_image.quantize() # Note that the image type after this step is still a float. If we want to actually # get integer values, we can do new_img = galsim.Image(final_image, dtype=int) # Since many people are used to viewing background-subtracted images, we provide a # version with the background subtracted (also rounding that to an int). sky_image.quantize() tot_sky_image = (sky_image + round(dark_current)) / roman.gain tot_sky_image.quantize() final_image -= tot_sky_image logger.debug( 'Subtracted background for {0}-band image'.format(filter_name)) # Write the final image to a file. out_filename = os.path.join(outpath, 'demo13_{0}.fits'.format(filter_name)) final_image.write(out_filename) logger.info('Completed {0}-band image.'.format(filter_name)) logger.info( 'You can display the output in ds9 with a command line that looks something like:' ) logger.info('ds9 -zoom 0.5 -scale limits -500 1000 -rgb ' + '-red output/demo13_H158.fits ' + '-green output/demo13_J129.fits ' + '-blue output/demo13_Y106.fits')
def main(argv): ## fixed parameters random_seed = int(sys.argv[7]) rng = galsim.BaseDeviate(random_seed) random_dir = galsim.UniformDeviate(rng) poisson_noise = galsim.PoissonNoise(rng) dither_file = '/hpc/group/cosmology/phy-lsst/dc2_truth/dc2_cen1deg.txt' reference_dither = 30592 SCA = 1 filter_ = 'H158' stamp_size = 32 hlr = 1.0 bpass = wfirst.getBandpasses(AB_zeropoint=True)[filter_] galaxy_sed_n = galsim.SED('Mrk_33_spec.dat', wave_type='Ang', flux_type='flambda') ## variable arguments gal_num = int(sys.argv[1]) #PA1 = int(sys.argv[2]) #PA2 = int(sys.argv[3]) gal_prof = sys.argv[2] psf_prof = sys.argv[3] shape = sys.argv[4] basis = sys.argv[5] output_name = sys.argv[6] #PA_list, D_list = find_pa(dither_file) #position_angless = np.array(PA_list)[np.random.choice(len(PA_list), 6)] #selected_dithers = np.array(D_list)[np.random.choice(len(D_list), 6)] position_angles = [20, 50] selected_dithers = [22535, 22535] # when using more galaxies than the length of truth file. res_noshear = np.zeros(gal_num, dtype=[('ind', int), ('flux', float), ('g1', float), ('g2', float), ('e1', float), ('e2', float), ('snr', float), ('hlr', float), ('flags', int)]) res_1p = np.zeros(gal_num, dtype=[('ind', int), ('flux', float), ('g1', float), ('g2', float), ('e1', float), ('e2', float), ('snr', float), ('hlr', float), ('flags', int)]) res_1m = np.zeros(gal_num, dtype=[('ind', int), ('flux', float), ('g1', float), ('g2', float), ('e1', float), ('e2', float), ('snr', float), ('hlr', float), ('flags', int)]) res_2p = np.zeros(gal_num, dtype=[('ind', int), ('flux', float), ('g1', float), ('g2', float), ('e1', float), ('e2', float), ('snr', float), ('hlr', float), ('flags', int)]) res_2m = np.zeros(gal_num, dtype=[('ind', int), ('flux', float), ('g1', float), ('g2', float), ('e1', float), ('e2', float), ('snr', float), ('hlr', float), ('flags', int)]) if shape == 'metacal': res_tot = [res_noshear, res_1p, res_1m, res_2p, res_2m] elif shape == 'noboot': res_tot = [res_noshear, res_1p, res_1m, res_2p, res_2m] elif shape == 'ngmix': res_tot = [res_noshear] # save time. if psf_prof == "Gaussian": psf = galsim.Gaussian(fwhm=0.178) t0 = time.time() for i_gal in range(gal_num): if i_gal % size != rank: continue if i_gal % 100 == 0: print('rank', rank, 'object number, ', i_gal) sca_center = Pointing(selected_dithers[0], SCA, filter_, stamp_size, position_angles[0], random_angle=False).find_sca_center() offsets = [] gals = [] psfs = [] skys = [] for exp in range(len(selected_dithers)): gal_model = None st_model = None gal_stamp = None psf_stamp = None if psf_prof == 'wfirst': psf_wcs, sk = Pointing(selected_dithers[exp], SCA, filter_, stamp_size, position_angles[exp], random_angle=False).get_wcs() psf = wfirst.getPSF(SCA, filter_, wcs=psf_wcs, SCA_pos=None, pupil_bin=4, wavelength=bpass.effective_wavelength) profile = Model(cat, gal_prof, psf, SCA, filter_, bpass, hlr, i_gal) gal_model, g1, g2 = profile.draw_galaxy(basis) st_model = profile.draw_star() pointing = Pointing(selected_dithers[exp], SCA, filter_, stamp_size, position_angles[exp], random_angle=False) image = Image(i_gal, stamp_size, gal_model, st_model, pointing, sca_center, real_wcs=True) translation = False if translation == True: offset = image.translational_dithering() gal_stamp, psf_stamp, offset = image.draw_image( gal_model, st_model) gal_stamp, sky_stamp = image.add_noise(rng, gal_stamp) if psf_stamp.wcs != galsim.PixelScale(0.11): gal_stamp, psf_stamp = image.wcs_approx(gal_stamp, psf_stamp) gal_stamp.write('gal_rotation_' + str(exp) + '.fits') psf_stamp.write('psf_rotation_' + str(exp) + '.fits') offsets.append(offset) gals.append(gal_stamp) psfs.append(psf_stamp) skys.append(sky_stamp) res_tot = shape_measurement(cat, gals, psfs, skys, offsets, i_gal, g1, g2, hlr, shape, res_tot).get_coadd_shape() sys.exit() ## send and receive objects from one processor to others if rank != 0: # send res_tot to rank 0 processor comm.send(res_tot, dest=0) else: for i in range(comm.size): if i == 0: continue # for other processors, receive res_tot. res_ = comm.recv(source=i) for j in range(len(res_tot)): for col in res_tot[j].dtype.names: res_tot[j][col] += res_[j][col] if rank == 0: dirr = output_name for i in range(len(res_tot)): fio.write(dirr + '_sim_' + str(i) + '.fits', res_tot[i]) if rank == 0: bias = residual_bias(res_tot[0], res_tot[1], res_tot[2], res_tot[3], res_tot[4], 'metacal') #final = residual_bias_correction(res_tot,R11,R22,R12,R21) print(time.time() - t0) return None
def main(argv): # Where to find and output data. path, filename = os.path.split(__file__) outpath = os.path.abspath(os.path.join(path, "output/")) # Just use a few galaxies, to save time. Note that we are going to put 4000 galaxy images into # our big image, so if we have n_use=10, each galaxy will appear 400 times. Users who want a # more interesting image with greater variation in the galaxy population can change `n_use` to # something larger (but it should be <=100, the number of galaxies in this small example # catalog). With 4000 galaxies in a 4k x 4k image with the WFIRST pixel scale, the effective # galaxy number density is 74/arcmin^2. This is not the number density that is expected for a # sample that is so bright (I<23.5) but it makes the image more visually interesting. One could # think of it as what you'd get if you added up several images at once, making the images for a # sample that is much deeper have the same S/N as that for an I<23.5 sample in a single image. n_use = 10 n_tot = 4000 # Default is to use all filters. Specify e.g. 'YJH' to only do Y106, J129, and H158. use_filters = 'H158' # quick and dirty command line parsing. for var in argv: if var.startswith('data='): datapath = var[5:] if var.startswith('out='): outpath = var[4:] if var.startswith('nuse='): n_use = int(var[5:]) if var.startswith('ntot='): n_tot = int(var[5:]) if var.startswith('filters='): use_filters = var[8:].upper() # Make output directory if not already present. if not os.path.isdir(outpath): os.mkdir(outpath) # In non-script code, use getLogger(__name__) at module scope instead. logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo13") # Initialize (pseudo-)random number generator. random_seed = 123456 rng = galsim.BaseDeviate(random_seed) # Generate a Poisson noise model. poisson_noise = galsim.PoissonNoise(rng) logger.info('Poisson noise model created.') # Read in the WFIRST filters, setting an AB zeropoint appropriate for this telescope given its # diameter and (since we didn't use any keyword arguments to modify this) using the typical # exposure time for WFIRST images. By default, this routine truncates the parts of the # bandpasses that are near 0 at the edges, and thins them by the default amount. filters = wfirst.getBandpasses(AB_zeropoint=True) logger.debug('Read in WFIRST imaging filters.') # Here we carry out the initial steps that are necessary to get a fully chromatic PSF. We use # the getPSF() routine in the WFIRST module, which knows all about the telescope parameters # (diameter, bandpasses, obscuration, etc.). Note that we arbitrarily choose a single SCA # (Sensor Chip Assembly) rather than all of them, for faster calculations, and use a simple # representation of the struts for faster calculations. To do a more exact calculation of the # chromaticity and pupil plane configuration, remove the `approximate_struts` and the `n_waves` # keyword from the call to getPSF(): PA = [20, 50] for i in range(2): use_SCA = 7 # This could be any number from 1...18 logger.info('Doing expensive pre-computation of PSF.') t1 = time.time() logger.setLevel(logging.DEBUG) # We choose a particular (RA, dec) location on the sky for our observation. ra_targ = 90. * galsim.degrees dec_targ = -10. * galsim.degrees targ_pos = galsim.CelestialCoord(ra=ra_targ, dec=dec_targ) # Get the WCS for an observation at this position. We are not supplying a date, so the routine # will assume it's the vernal equinox. We are also not supplying a position angle for the # observatory, which means that it will just find the optimal one (the one that has the solar # panels pointed most directly towards the Sun given this targ_pos and date). The output of # this routine is a dict of WCS objects, one for each SCA. We then take the WCS for the SCA # that we are using. wcs_dict = wfirst.getWCS(world_pos=targ_pos, PA=PA[i] * galsim.radians, SCAs=use_SCA) wcs = wcs_dict[use_SCA] # We need to find the center position for this SCA. We'll tell it to give us a CelestialCoord # corresponding to (X, Y) = (wfirst.n_pix/2, wfirst.n_pix/2). SCA_cent_pos = wcs.toWorld( galsim.PositionD(wfirst.n_pix / 2, wfirst.n_pix / 2)) # Need to make a separate PSF for each filter. We are, however, ignoring the # position-dependence of the PSF within each SCA, just using the PSF at the center of the SCA # (default kwargs). PSFs = {} for filter_name, filter_ in filters.items(): logger.info('PSF pre-computation for SCA %d, filter %s.' % (use_SCA, filter_name)) PSFs[filter_name] = galsim.roman.getPSF(use_SCA, filter_name, n_waves=10, wcs=wcs, pupil_bin=4) t2 = time.time() logger.info('Done PSF precomputation in %.1f seconds!' % (t2 - t1)) # Define the size of the postage stamp that we use for each individual galaxy within the larger # image, and for the PSF images. stamp_size = 256 # We randomly distribute points in (X, Y) on the CCD. # If we had a real galaxy catalog with positions in terms of RA, dec we could use wcs.toImage() # to find where those objects should be in terms of (X, Y). pos_rng = galsim.UniformDeviate(random_seed) # Calculate the sky level for each filter, and draw the PSF and the galaxies through the # filters. for filter_name, filter_ in filters.items(): if use_filters is not None and filter_name[0] not in use_filters: logger.info('Skipping filter {0}.'.format(filter_name)) continue logger.info('Beginning work for {0}.'.format(filter_name)) # Drawing PSF. Note that the PSF object intrinsically has a flat SED, so if we convolve it # with a galaxy, it will properly take on the SED of the galaxy. For the sake of this demo, # we will simply convolve with a 'star' that has a flat SED and unit flux in this band, so # that the PSF image will be normalized to unit flux. This does mean that the PSF image # being drawn here is not quite the right PSF for the galaxy. Indeed, the PSF for the # galaxy effectively varies within it, since it differs for the bulge and the disk. To make # a real image, one would have to choose SEDs for stars and convolve with a star that has a # reasonable SED, but we just draw with a flat SED for this demo. out_filename = os.path.join(outpath, 'demo13_PSF_' + str(i) + '.fits') # Generate a point source. point = galsim.DeltaFunction(flux=1.) # Use a flat SED here, but could use something else. A stellar SED for instance. # Or a typical galaxy SED. Depending on your purpose for drawing the PSF. star_sed = galsim.SED(lambda x: 1, 'nm', 'flambda').withFlux( 1., filter_) # Give it unit flux in this filter. star = galsim.Convolve(point * star_sed, PSFs[filter_name]) img_psf = galsim.ImageF(64, 64) star.drawImage(bandpass=filter_, image=img_psf, scale=wfirst.pixel_scale) img_psf.write(out_filename) logger.debug( 'Created PSF with flat SED for {0}-band'.format(filter_name)) # Set up the full image that will contain all the individual galaxy images, with information # about WCS: final_image = galsim.ImageF(wfirst.n_pix, wfirst.n_pix, wcs=wcs)