def test_airy_radii(): """Test Airy half light radius and FWHM correctly set and match image. """ import math # Test constructor using lam_over_diam: (only option for Airy) test_gal = galsim.Airy(lam_over_diam= 1./0.8, flux=1.) # test half-light-radius getter got_hlr = test_gal.half_light_radius hlr_sum = radial_integrate(test_gal, 0., got_hlr) print('hlr_sum = ',hlr_sum) np.testing.assert_almost_equal( hlr_sum, 0.5, decimal=4, err_msg="Error in Airy half-light radius") # test FWHM getter center = test_gal.xValue(galsim.PositionD(0,0)) got_fwhm = test_gal.fwhm ratio = test_gal.xValue(galsim.PositionD(0.5 * got_fwhm,0)) / center print('fwhm ratio = ',ratio) np.testing.assert_almost_equal( ratio, 0.5, decimal=4, err_msg="Error in fwhm for Airy.") # Check that the getters don't work after modifying the original. test_gal_shear = test_gal.shear(g1=0.3, g2=0.1) assert_raises(AttributeError, getattr, test_gal_shear, "fwhm") assert_raises(AttributeError, getattr, test_gal_shear, "half_light_radius") assert_raises(AttributeError, getattr, test_gal_shear, "lam_over_diam") # hlr and fwhm not implemented for obscuration != 0 airy2 = galsim.Airy(lam_over_diam= 1./0.8, flux=1., obscuration=0.2) with assert_raises(galsim.GalSimNotImplementedError): airy2.half_light_radius with assert_raises(galsim.GalSimNotImplementedError): airy2.fwhm
def test_airy_shoot(): """Test Airy with photon shooting. Particularly the flux of the final image. """ # Airy patterns have *very* extended wings, so make this really small and the image really # big to make sure we capture all the photons. rng = galsim.BaseDeviate(1234) obj = galsim.Airy(lam_over_diam=0.01, flux=1.e4) im = galsim.Image(1000, 1000, scale=1) im.setCenter(0, 0) added_flux, photons = obj.drawPhot(im, poisson_flux=False, rng=rng.duplicate()) print('obj.flux = ', obj.flux) print('added_flux = ', added_flux) print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max()) print('image flux = ', im.array.sum()) assert np.isclose(added_flux, obj.flux) assert np.isclose(im.array.sum(), obj.flux) photons2 = obj.makePhot(poisson_flux=False, rng=rng) assert photons2 == photons, "Airy makePhot not equivalent to drawPhot" obj = galsim.Airy(lam_over_diam=0.01, flux=1.e4, obscuration=0.4) added_flux, photons = obj.drawPhot(im, poisson_flux=False, rng=rng.duplicate()) print('obj.flux = ', obj.flux) print('added_flux = ', added_flux) print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max()) print('image flux = ', im.array.sum()) assert np.isclose(added_flux, obj.flux) assert np.isclose(im.array.sum(), obj.flux) photons2 = obj.makePhot(poisson_flux=False, rng=rng) assert photons2 == photons, "Airy makePhot not equivalent to drawPhot"
def make_psf(rng, iprof=None, scale=None, g1=None, g2=None, flux=10000): np_rng = np.random.default_rng(rng.raw()) # Pick from one of several plausible PSF profiles. psf_profs = [ galsim.Gaussian(fwhm=1.), galsim.Moffat(beta=1.5, fwhm=1.), galsim.Moffat(beta=4.5, fwhm=1.), galsim.Kolmogorov(fwhm=1.), galsim.Airy(lam=700, diam=4), galsim.Airy(lam=1200, diam=6.5, obscuration=0.6), galsim.OpticalPSF(lam=700, diam=4, obscuration=0.6, defocus=0.2, coma1=0.2, coma2=-0.2, astig1=-0.1, astig2=0.1), galsim.OpticalPSF(lam=900, diam=2.1, obscuration=0.2, aberrations=[ 0, 0, 0, 0, -0.1, 0.2, -0.15, -0.1, 0.15, 0.1, 0.15, -0.2 ]), galsim.OpticalPSF(lam=1200, diam=6.5, obscuration=0.3, aberrations=[ 0, 0, 0, 0, 0.2, 0.1, 0.15, -0.1, -0.15, -0.2, 0.1, 0.15 ]), ] # The last 5 need an atmospheric part, or else they don't much resemble the kinds of # PSF profiles we actually care about. psf_profs[-5:] = [ galsim.Convolve(galsim.Kolmogorov(fwhm=0.6), p) for p in psf_profs[-5:] ] if iprof is None: psf = np_rng.choice(psf_profs) else: psf = psf_profs[iprof] # Choose a random size and shape within reasonable ranges. if g1 is None: g1 = np_rng.uniform(-0.03, 0.03) if g2 is None: g2 = np_rng.uniform(-0.03, 0.03) if scale is None: # Note: Don't go too small, since hsm fails more often for size close to pixel_scale. scale = np.exp(np_rng.uniform(-0.3, 1.0)) psf = psf.dilate(scale).shear(g1=g1, g2=g2).withFlux(flux) return psf
def test_limiting_cases(): """SecondKick has some two interesting limiting cases. A) When kcrit = 0, SecondKick = Convolve(Airy, VonKarman). B) When kcrit = inf, SecondKick = Airy Test these. """ lam = 500.0 r0 = 0.2 diam = 8.36 obscuration = 0.61 # First kcrit=0 sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=0.0) limiting_case = galsim.Convolve( galsim.VonKarman(lam, r0, L0=1.e8), galsim.Airy(lam=lam, diam=diam, obscuration=obscuration) ) print(sk.stepk, sk.maxk) print(limiting_case.stepk, limiting_case.maxk) for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]: print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real) np.testing.assert_allclose( sk.kValue(0, k).real, limiting_case.kValue(0, k).real, rtol=1e-3, atol=1e-4) # Normally, one wouldn't use SecondKick.xValue, since it does a real-space convolution, # so it's slow. But we do allow it, so test it here. import time t0 = time.time() xv_2k = sk.xValue(0,0) print("xValue(0,0) = ",xv_2k) t1 = time.time() # The VonKarman * Airy xValue is much slower still, so don't do that. # Instead compare it to the 'sb' image. xv_image = limiting_case.drawImage(nx=1,ny=1,method='sb',scale=0.1)(1,1) print('from image ',xv_image) t2 = time.time() print('t = ',t1-t0, t2-t1) np.testing.assert_almost_equal(xv_2k, xv_image, decimal=3) # kcrit=inf sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=np.inf) limiting_case = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration) for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]: print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real) np.testing.assert_allclose( sk.kValue(0, k).real, limiting_case.kValue(0, k).real, rtol=1e-3, atol=1e-4)
def test_airy_flux_scaling(): """Test flux scaling for Airy. """ # decimal point to go to for parameter value comparisons param_decimal = 12 test_loD = 1.9 test_obscuration = 0.32 test_flux = 17.9 # init with lam_over_diam and flux only (should be ok given last tests) obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj *= 2. np.testing.assert_almost_equal( obj.flux, test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __imul__.") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj /= 2. np.testing.assert_almost_equal( obj.flux, test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __idiv__.") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = obj * 2. # First test that original obj is unharmed... np.testing.assert_almost_equal( obj.flux, test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __rmul__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.flux, test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __rmul__ (result).") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = 2. * obj # First test that original obj is unharmed... np.testing.assert_almost_equal( obj.flux, test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __mul__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.flux, test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __mul__ (result).") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = obj / 2. # First test that original obj is unharmed... np.testing.assert_almost_equal( obj.flux, test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __div__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.flux, test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __div__ (result).")
def test_airy_flux_scaling(): """Test flux scaling for Airy. """ import time t1 = time.time() # init with lam_over_r0 and flux only (should be ok given last tests) obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj *= 2. np.testing.assert_almost_equal( obj.getFlux(), test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __imul__.") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj /= 2. np.testing.assert_almost_equal( obj.getFlux(), test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __idiv__.") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = obj * 2. # First test that original obj is unharmed... (also tests that .copy() is working) np.testing.assert_almost_equal( obj.getFlux(), test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __rmul__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.getFlux(), test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __rmul__ (result).") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = 2. * obj # First test that original obj is unharmed... (also tests that .copy() is working) np.testing.assert_almost_equal( obj.getFlux(), test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __mul__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.getFlux(), test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __mul__ (result).") obj = galsim.Airy(lam_over_diam=test_loD, flux=test_flux, obscuration=test_obscuration) obj2 = obj / 2. # First test that original obj is unharmed... (also tests that .copy() is working) np.testing.assert_almost_equal( obj.getFlux(), test_flux, decimal=param_decimal, err_msg="Flux param inconsistent after __div__ (original).") # Then test new obj2 flux np.testing.assert_almost_equal( obj2.getFlux(), test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __div__ (result).") t2 = time.time() print 'time for %s = %.2f'%(funcname(),t2-t1)
def test_OpticalPSF_vs_Airy_with_obs(): """Compare the array view on an unaberrated OpticalPSF with obscuration to that of an Airy. """ import time t1 = time.time() lod = 7.5 # lambda/D value: don't choose unity in case symmetry hides something obses = (0.1, 0.3, 0.5) # central obscuration radius ratios nlook = 100 # size of array region at the centre of each image to compare image = galsim.ImageF(nlook, nlook) for obs in obses: airy_test = galsim.Airy(lam_over_diam=lod, obscuration=obs, flux=1.) optics_test = galsim.OpticalPSF(lam_over_diam=lod, pad_factor=1, obscuration=obs, suppress_warning=True) airy_array = airy_test.draw(scale=1., image=image).array optics_array = optics_test.draw(scale=1., image=image).array np.testing.assert_array_almost_equal( optics_array, airy_array, decimal_dft, err_msg= "Unaberrated Optical with obscuration not quite equal to Airy") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_OpticalPSF_vs_Airy_with_obs(): """Compare the array view on an unaberrated OpticalPSF with obscuration to that of an Airy. """ lod = 7.5 # lambda/D value: don't choose unity in case symmetry hides something obses = (0.1, 0.3, 0.5) # central obscuration radius ratios nlook = 100 # size of array region at the centre of each image to compare image = galsim.ImageF(nlook, nlook) for obs in obses: airy_test = galsim.Airy(lam_over_diam=lod, obscuration=obs, flux=1.) optics_test = galsim.OpticalPSF(lam_over_diam=lod, pad_factor=1, obscuration=obs, suppress_warning=True) airy_array = airy_test.drawImage(scale=1., image=image, method='no_pixel').array optics_array = optics_test.drawImage(scale=1., image=image, method='no_pixel').array np.testing.assert_array_almost_equal( optics_array, airy_array, decimal_dft, err_msg= "Unaberrated Optical with obscuration not quite equal to Airy") do_pickle( optics_test, lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel')) do_pickle(optics_test)
def test_cosmos_fluxnorm(): """Check for flux normalization properties of COSMOSCatalog class.""" # Check that if we make a RealGalaxy catalog, and a COSMOSCatalog, and draw the real object, the # fluxes should match very well. These correspond to 1s exposures. test_ind = 54 rand_seed = 12345 cat = galsim.COSMOSCatalog( file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath, exclusion_level='none') rgc = galsim.RealGalaxyCatalog( file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath) final_psf = galsim.Airy(diam=1.2, lam=800.) # PSF twice as big as HST in F814W. gal1 = cat.makeGalaxy(test_ind, gal_type='real', rng=galsim.BaseDeviate(rand_seed)) gal2 = galsim.RealGalaxy(rgc, index=test_ind, rng=galsim.BaseDeviate(rand_seed)) gal1 = galsim.Convolve(gal1, final_psf) gal2 = galsim.Convolve(gal2, final_psf) im1 = gal1.drawImage(scale=0.05) im2 = gal2.drawImage(scale=0.05) # Then check that if we draw a parametric representation that is achromatic, that the flux # matches reasonably well (won't be exact because model isn't perfect). gal1_param = cat.makeGalaxy(test_ind, gal_type='parametric', chromatic=False) gal1_param_final = galsim.Convolve(gal1_param, final_psf) im1_param = gal1_param_final.drawImage(scale=0.05) # Then check the same for a chromatic parametric representation that is drawn into the same # band. bp_file = os.path.join(galsim.meta_data.share_dir, 'bandpasses', 'ACS_wfc_F814W.dat') bandpass = galsim.Bandpass(bp_file, wave_type='nm').withZeropoint(25.94) #34.19) gal1_chrom = cat.makeGalaxy(test_ind, gal_type='parametric', chromatic=True) gal1_chrom = galsim.Convolve(gal1_chrom, final_psf) im1_chrom = gal1_chrom.drawImage(bandpass, scale=0.05) ref_val = [im1.array.sum(), im1.array.sum(), im1.array.sum()] test_val = [im2.array.sum(), im1_param.array.sum(), im1_chrom.array.sum()] np.testing.assert_allclose( ref_val, test_val, rtol=0.1, err_msg='Flux normalization problem in COSMOS galaxies') # Finally, check that the original COSMOS info is stored properly after transformations, for # both Sersic galaxies (like galaxy 0 in the catalog) and the one that is gal1_param above. gal0_param = cat.makeGalaxy(0, gal_type='parametric', chromatic=False) assert hasattr(gal0_param.shear(g1=0.05).original, 'index'), \ 'Sersic galaxy does not retain index information after transformation' assert hasattr(gal1_param.shear(g1=0.05).original, 'index'), \ 'Bulge+disk galaxy does not retain index information after transformation'
def test_OpticalPSF_vs_Airy(): """Compare the array view on an unaberrated OpticalPSF to that of an Airy. """ lods = ( 4.e-7, 9., 16.4 ) # lambda/D values: don't choose unity in case symmetry hides something nlook = 100 image = galsim.ImageF(nlook, nlook) for lod in lods: airy_test = galsim.Airy(lam_over_diam=lod, obscuration=0., flux=1.) #pad same as an Airy, natch! optics_test = galsim.OpticalPSF(lam_over_diam=lod, pad_factor=1, suppress_warning=True) airy_array = airy_test.drawImage(scale=.25 * lod, image=image, method='no_pixel').array optics_array = optics_test.drawImage(scale=.25 * lod, image=image, method='no_pixel').array np.testing.assert_array_almost_equal( optics_array, airy_array, decimal_dft, err_msg="Unaberrated Optical not quite equal to Airy")
def test_integer_shift_photon(): """Test if applyShift works correctly for integer shifts using drawShoot method. """ import time t1 = time.time() n_photons_low = 10 seed = 10 gal = galsim.Gaussian(sigma=test_sigma) pix = galsim.Pixel(1.) psf = galsim.Airy(lam_over_diam=test_hlr) # shift galaxy only final=galsim.Convolve([gal, psf, pix]) img_center = galsim.ImageD(n_pix_x,n_pix_y) test_deviate = galsim.BaseDeviate(seed) final.drawShoot(img_center,scale=1,rng=test_deviate,n_photons=n_photons_low) gal.applyShift(dx=int_shift_x,dy=int_shift_y) final=galsim.Convolve([gal, psf, pix]) img_shift = galsim.ImageD(n_pix_x,n_pix_y) test_deviate = galsim.BaseDeviate(seed) final.drawShoot(img_shift,scale=1,rng=test_deviate,n_photons=n_photons_low) sub_center = img_center.array[ (n_pix_y - delta_sub) / 2 : (n_pix_y + delta_sub) / 2, (n_pix_x - delta_sub) / 2 : (n_pix_x + delta_sub) / 2] sub_shift = img_shift.array[ (n_pix_y - delta_sub) / 2 + int_shift_y : (n_pix_y + delta_sub) / 2 + int_shift_y, (n_pix_x - delta_sub) / 2 + int_shift_x : (n_pix_x + delta_sub) / 2 + int_shift_x] np.testing.assert_array_almost_equal( sub_center, sub_shift, decimal=image_decimal_precise, err_msg="Integer shift failed for FFT rendered Gaussian GSObject with shifted Galaxy only") # shift PSF only gal = galsim.Gaussian(sigma=test_sigma) psf.applyShift(dx=int_shift_x,dy=int_shift_y) final=galsim.Convolve([gal, psf, pix]) img_shift = galsim.ImageD(n_pix_x,n_pix_y) test_deviate = galsim.BaseDeviate(seed) final.drawShoot(img_shift,scale=1,rng=test_deviate,n_photons=n_photons_low) sub_center = img_center.array[ (n_pix_y - delta_sub) / 2 : (n_pix_y + delta_sub) / 2, (n_pix_x - delta_sub) / 2 : (n_pix_x + delta_sub) / 2] sub_shift = img_shift.array[ (n_pix_y - delta_sub) / 2 + int_shift_y : (n_pix_y + delta_sub) / 2 + int_shift_y, (n_pix_x - delta_sub) / 2 + int_shift_x : (n_pix_x + delta_sub) / 2 + int_shift_x] np.testing.assert_array_almost_equal( sub_center, sub_shift, decimal=image_decimal_precise, err_msg="Integer shift failed for FFT rendered Gaussian GSObject with only PSF shifted ") t2 = time.time() print 'time for %s = %.2f'%(funcname(),t2-t1)
def test_integer_shift_fft(): """Test if shift works correctly for integer shifts using drawImage method. """ gal = galsim.Gaussian(sigma=test_sigma) psf = galsim.Airy(lam_over_diam=test_hlr) # shift galaxy only final = galsim.Convolve([gal, psf]) img_center = galsim.ImageD(n_pix_x, n_pix_y) final.drawImage(img_center, scale=1) gal = gal.shift(dx=int_shift_x, dy=int_shift_y) final = galsim.Convolve([gal, psf]) img_shift = galsim.ImageD(n_pix_x, n_pix_y) final.drawImage(img_shift, scale=1) sub_center = img_center.array[(n_pix_y - delta_sub) // 2:(n_pix_y + delta_sub) // 2, (n_pix_x - delta_sub) // 2:(n_pix_x + delta_sub) // 2] sub_shift = img_shift.array[(n_pix_y - delta_sub) // 2 + int_shift_y:(n_pix_y + delta_sub) // 2 + int_shift_y, (n_pix_x - delta_sub) // 2 + int_shift_x:(n_pix_x + delta_sub) // 2 + int_shift_x] np.testing.assert_array_almost_equal( sub_center, sub_shift, decimal=image_decimal_precise, err_msg= "Integer shift failed for FFT rendered Gaussian GSObject with shifted Galaxy only" ) # shift PSF only gal = galsim.Gaussian(sigma=test_sigma) psf = psf.shift(dx=int_shift_x, dy=int_shift_y) final = galsim.Convolve([gal, psf]) img_shift = galsim.ImageD(n_pix_x, n_pix_y) final.drawImage(img_shift, scale=1) sub_center = img_center.array[(n_pix_y - delta_sub) // 2:(n_pix_y + delta_sub) // 2, (n_pix_x - delta_sub) // 2:(n_pix_x + delta_sub) // 2] sub_shift = img_shift.array[(n_pix_y - delta_sub) // 2 + int_shift_y:(n_pix_y + delta_sub) // 2 + int_shift_y, (n_pix_x - delta_sub) // 2 + int_shift_x:(n_pix_x + delta_sub) // 2 + int_shift_x] np.testing.assert_array_almost_equal( sub_center, sub_shift, decimal=image_decimal_precise, err_msg= "Integer shift failed for FFT rendered Gaussian GSObject with only PSF shifted " )
def get_optical_psf(self, wavelength): #Convert dimensionless lam/D to arcsec units. lam_over_diam_arcsec = ((wavelength / self.diameter) * u.rad).to( u.arcsec) # Airy requires floats as inputs, not numpy scalars. return galsim.Airy(lam_over_diam=float(lam_over_diam_arcsec.value), obscuration=float( np.sqrt(self.obscuration_area_fraction)))
def make_a_star(ud, wcs, psf, affine): # Choose a random RA, Dec around the sky_center. dec = center_dec + (ud() - 0.5) * image_ysize_arcsec * galsim.arcsec ra = center_ra + ( ud() - 0.5) * image_xsize_arcsec / numpy.cos(dec) * galsim.arcsec world_pos = galsim.CelestialCoord(ra, dec) # We will need the image position as well, so use the wcs to get that image_pos = wcs.toImage(world_pos) # We also need this in the tangent plane, which we call "world coordinates" here, # This is still an x/y corrdinate uv_pos = affine.toWorld(image_pos) # Draw star flux at random; based on distribution of star fluxes in real images # Generate PSF at location of star, convolve simple Airy with the PSF to make a star flux_dist = galsim.DistDeviate(ud, function=lambda x: x**-1, x_min=799.2114, x_max=890493.9) """ flux_dist = galsim.DistDeviate(ud, function = lambda x:x**-1.5, x_min = 3000, x_max = 30000) """ star_flux = flux_dist() shining_star = galsim.Airy(lam=lam, obscuration=0.380, diam=tel_diam, scale_unit=galsim.arcsec, flux=star_flux) this_psf = psf.getPSF(image_pos) star = galsim.Convolve([shining_star, optics, this_psf]) #star=galsim.Convolve([shining_star,this_psf]) # Account for the fractional part of the position # cf. demo9.py for an explanation of this nominal position stuff. x_nominal = image_pos.x + 0.5 y_nominal = image_pos.y + 0.5 ix_nominal = int(math.floor(x_nominal + 0.5)) iy_nominal = int(math.floor(y_nominal + 0.5)) dx = x_nominal - ix_nominal dy = y_nominal - iy_nominal offset = galsim.PositionD(dx, dy) #star_stamp = star.drawImage(wcs=wcs.local(image_pos), offset=offset, method='no_pixel') star_stamp = star.drawImage(wcs=wcs.local(image_pos), offset=offset) #,method='no_pixel') # Recenter the stamp at the desired position: star_stamp.setCenter(ix_nominal, iy_nominal) star_truth = truth() star_truth.ra = ra.deg star_truth.dec = dec.deg star_truth.x = ix_nominal star_truth.y = iy_nominal return star_stamp, star_truth
def make_a_star(ud, this_im_wcs, psf, affine): # Choose a random RA, Dec around the sky_center. center_dec = this_im_wcs.center.dec center_ra = this_im_wcs.center.ra dec = center_dec + (ud() - 0.5) * image_ysize_arcsec * galsim.arcsec ra = center_ra + ( ud() - 0.5) * image_xsize_arcsec / numpy.cos(dec) * galsim.arcsec world_pos = galsim.CelestialCoord(ra, dec) # We will need the image position as well, so use the wcs to get that image_pos = this_im_wcs.posToImage(world_pos) # We also need this in the tangent plane, which we call "world coordinates" here, # since the PowerSpectrum class is really defined on that plane, not in (ra,dec). # This is still an x/y corrdinate uv_pos = affine.toWorld(image_pos) # Draw star flux at random; based on distribution of star fluxes in real images # Generate PSF at location of star, convolve simple Airy with the PSF to make a star flux_dist = galsim.DistDeviate( ud, function= '/Users/jemcclea/Research/GalSim/examples/output/empirical_psfs/v2/stars_flux300_prob.txt' ) #,interpolant='floor') star_flux = (flux_dist()) * (exp_time / 300.) shining_star = galsim.Airy(lam=lam, obscuration=0.3840, diam=tel_diam, scale_unit=galsim.arcsec, flux=star_flux) this_psf = psf.getPSF(image_pos) star = galsim.Convolve([shining_star, this_psf]) # Account for the fractional part of the position # cf. demo9.py for an explanation of this nominal position stuff. x_nominal = image_pos.x + 0.5 y_nominal = image_pos.y + 0.5 ix_nominal = int(math.floor(x_nominal + 0.5)) iy_nominal = int(math.floor(y_nominal + 0.5)) dx = x_nominal - ix_nominal dy = y_nominal - iy_nominal offset = galsim.PositionD(dx, dy) star_stamp = star.drawImage(wcs=this_im_wcs.local(image_pos), offset=offset, method='no_pixel') # Recenter the stamp at the desired position: star_stamp.setCenter(ix_nominal, iy_nominal) star_truth = truth() star_truth.ra = ra.deg star_truth.dec = dec.deg star_truth.x = ix_nominal star_truth.y = iy_nominal return star_stamp, star_truth
def test_cosmos_fluxnorm(): """Check for flux normalization properties of COSMOSCatalog class.""" import time t1 = time.time() # Check that if we make a RealGalaxy catalog, and a COSMOSCatalog, and draw the real object, the # fluxes should match very well. These correspond to 1s exposures. test_ind = 54 rand_seed = 12345 cat = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_example.fits', dir=datapath, exclude_fail=False, exclude_bad=False) rgc = galsim.RealGalaxyCatalog( file_name='real_galaxy_catalog_example.fits', dir=datapath) final_psf = galsim.Airy(diam=1.2, lam=800.) # PSF twice as big as HST in F814W. gal1 = cat.makeGalaxy(test_ind, gal_type='real', rng=galsim.BaseDeviate(rand_seed)) gal2 = galsim.RealGalaxy(rgc, index=test_ind, rng=galsim.BaseDeviate(rand_seed)) gal1 = galsim.Convolve(gal1, final_psf) gal2 = galsim.Convolve(gal2, final_psf) im1 = gal1.drawImage(scale=0.05) im2 = gal2.drawImage(scale=0.05) # Then check that if we draw a parametric representation that is achromatic, that the flux # matches reasonably well (won't be exact because model isn't perfect). gal1_param = cat.makeGalaxy(test_ind, gal_type='parametric', chromatic=False) gal1_param = galsim.Convolve(gal1_param, final_psf) im1_param = gal1_param.drawImage(scale=0.05) # Then check the same for a chromatic parametric representation that is drawn into the same # band. bp_file = os.path.join(galsim.meta_data.share_dir, 'wfc_F814W.dat.gz') bandpass = galsim.Bandpass(bp_file, wave_type='ang').thin().withZeropoint( 25.94) #34.19) gal1_chrom = cat.makeGalaxy(test_ind, gal_type='parametric', chromatic=True) gal1_chrom = galsim.Convolve(gal1_chrom, final_psf) im1_chrom = gal1_chrom.drawImage(bandpass, scale=0.05) ref_val = [im1.array.sum(), im1.array.sum(), im1.array.sum()] test_val = [im2.array.sum(), im1_param.array.sum(), im1_chrom.array.sum()] np.testing.assert_allclose( ref_val, test_val, rtol=0.1, err_msg='Flux normalization problem in COSMOS galaxies') t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_airy(): """Test various ways to build a Airy """ config = { 'gal1' : { 'type' : 'Airy' , 'lam_over_diam' : 2 }, 'gal2' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'obscuration' : 0.3, 'flux' : 100 }, 'gal3' : { 'type' : 'Airy' , 'lam_over_diam' : 1.3, 'obscuration' : 0, 'flux' : 1.e6, 'ellip' : { 'type' : 'QBeta' , 'q' : 0.6, 'beta' : 0.39 * galsim.radians } }, 'gal4' : { 'type' : 'Airy' , 'lam_over_diam' : 1, 'flux' : 50, 'dilate' : 3, 'ellip' : galsim.Shear(e1=0.3), 'rotate' : 12 * galsim.degrees, 'magnify' : 1.03, 'shear' : galsim.Shear(g1=0.03, g2=-0.05), 'shift' : { 'type' : 'XY', 'x' : 0.7, 'y' : -1.2 } }, 'gal5' : { 'type' : 'Airy' , 'lam_over_diam' : 45, 'gsparams' : { 'xvalue_accuracy' : 1.e-2 } }, 'gal6' : { 'type' : 'Airy' , 'lam' : 400., 'diam' : 4.0, 'scale_unit' : 'arcmin' } } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] gal1b = galsim.Airy(lam_over_diam = 2) gsobject_compare(gal1a, gal1b) gal2a = galsim.config.BuildGSObject(config, 'gal2')[0] gal2b = galsim.Airy(lam_over_diam = 0.4, obscuration = 0.3, flux = 100) gsobject_compare(gal2a, gal2b) gal3a = galsim.config.BuildGSObject(config, 'gal3')[0] gal3b = galsim.Airy(lam_over_diam = 1.3, flux = 1.e6) gal3b = gal3b.shear(q = 0.6, beta = 0.39 * galsim.radians) gsobject_compare(gal3a, gal3b) gal4a = galsim.config.BuildGSObject(config, 'gal4')[0] gal4b = galsim.Airy(lam_over_diam = 1, flux = 50) gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees).magnify(1.03) gal4b = gal4b.shear(g1 = 0.03, g2 = -0.05).shift(dx = 0.7, dy = -1.2) gsobject_compare(gal4a, gal4b) # The approximation from xvalue_accuracy here happens at the core, so you need a very # large size to notice. (Which tells me this isn't that useful an approximation, but # so be it.) gal5a = galsim.config.BuildGSObject(config, 'gal5')[0] gsparams = galsim.GSParams(xvalue_accuracy=1.e-2) gal5b = galsim.Airy(lam_over_diam=45, gsparams=gsparams) gsobject_compare(gal5a, gal5b) gal6a = galsim.config.BuildGSObject(config, 'gal6')[0] gal6b = galsim.Airy(lam=400., diam=4., scale_unit=galsim.arcmin) gsobject_compare(gal6a, gal6b) try: # Make sure they don't match when using the default GSParams gal5c = galsim.Airy(lam_over_diam=45) np.testing.assert_raises(AssertionError,gsobject_compare, gal5a, gal5c) except ImportError: print('The assert_raises tests require nose')
def test_ne(): """Test base.py GSObjects for not-equals.""" # Define some universal gsps gsp = galsim.GSParams(maxk_threshold=1.1e-3, folding_threshold=5.1e-3) # Airy. Params include lam_over_diam, lam, diam, obscuration, flux, and gsparams. # The following should all test unequal: gals = [galsim.Airy(lam_over_diam=1.0), galsim.Airy(lam_over_diam=1.1), galsim.Airy(lam=1.0, diam=1.2), galsim.Airy(lam=1.0, diam=1.2, scale_unit=galsim.arcmin), galsim.Airy(lam=1.0, diam=1.2, scale_unit='degrees'), galsim.Airy(lam=1.0, diam=1.0, obscuration=0.1), galsim.Airy(lam_over_diam=1.0, flux=1.1), galsim.Airy(lam_over_diam=1.0, gsparams=gsp)] all_obj_diff(gals)
def _stepK(self, **kwargs): """Return an appropriate stepk for this phase screen. @param lam Wavelength in nanometers. @param diam Aperture diameter in meters. @param obscuration Fractional linear aperture obscuration. [default: 0.0] @param gsparams An optional GSParams argument. See the docstring for GSParams for details. [default: None] @returns stepk in inverse arcsec. """ lam = kwargs['lam'] diam = kwargs['diam'] obscuration = kwargs.get('obscuration', 0.0) gsparams = kwargs.get('gsparams', None) # Use an Airy for get appropriate stepk. obj = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration, gsparams=gsparams) return obj.stepk
def __init__(self, rng, wavelength, gsparams=None): u = galsim.UniformDeviate(rng) # Fudge factor below comes from an attempt to force the PSF ellipticity distribution to # match more closely the targets in the SRD (not be too round). (See the discussion at # https://github.com/LSSTDESC/DC2-production/issues/259). Since the values in the # mock_deviations function currently rely on a small set of simulations (7), this was deemed # reasonable. deviationsFudgeFactor = 3.0 self.deviations = deviationsFudgeFactor * mock_deviations( seed=int(u() * 2**31)) self.oz = OpticalZernikes(self.deviations) self.dynamic = False self.reversible = True # Compute stepk once and store obj = galsim.Airy(lam=wavelength, diam=8.36, obscuration=0.61, gsparams=gsparams) self.stepk = obj.stepk
def get_psf(Args): atmospheric_psf_fwhm = Args.zenith_psf_fwhm * Args.airmass**0.6 if Args.atmospheric_psf_beta > 0: atmospheric_psf_model = galsim.Moffat(beta=Args.atmospheric_psf_beta, fwhm=atmospheric_psf_fwhm) else: atmospheric_psf_model = galsim.Kolmogorov(fwhm=atmospheric_psf_fwhm) lambda_over_diameter = 3600 * math.degrees( 1e-10 * Args.central_wavelength / Args.mirror_diameter) area_ratio = Args.effective_area / (math.pi * (0.5 * Args.mirror_diameter)**2) obscuration_fraction = math.sqrt(1 - area_ratio) optical_psf_model = galsim.Airy(lam_over_diam=lambda_over_diameter, obscuration=obscuration_fraction) psf_model = galsim.Convolve(atmospheric_psf_model, optical_psf_model) psf_size_pixels = 2 * int( math.ceil(10 * atmospheric_psf_fwhm / Args.pixel_scale)) psf_image = galsim.Image(psf_size_pixels, psf_size_pixels, scale=Args.pixel_scale) psf_model.drawImage(image=psf_image) return psf_image.array
def test_OpticalPSF_vs_Airy(): """Compare the array view on an unaberrated OpticalPSF to that of an Airy. """ import time t1 = time.time() lods = ( 4.e-7, 9., 16.4 ) # lambda/D values: don't choose unity in case symmetry hides something nlook = 100 image = galsim.ImageF(nlook, nlook) for lod in lods: airy_test = galsim.Airy(lam_over_diam=lod, obscuration=0., flux=1.) optics_test = galsim.OpticalPSF( lam_over_diam=lod, pad_factor=1) #pad same as an Airy, natch! airy_array = airy_test.draw(dx=.25 * lod, image=image).array optics_array = optics_test.draw(dx=.25 * lod, image=image).array np.testing.assert_array_almost_equal( optics_array, airy_array, decimal_dft, err_msg="Unaberrated Optical not quite equal to Airy") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_phase_gradient_shoot(): """Test that photon-shooting PSFs match Fourier optics PSFs when using the same phase screens, and also match the expected size from an analytic VonKarman-convolved-with-Airy PSF. """ # Make the atmosphere seed = 12345 r0_500 = 0.15 # m L0 = 20.0 # m nlayers = 6 screen_size = 102.4 # m # Ideally, we'd use as small a screen scale as possible here. The runtime for generating # phase screens scales like `screen_scale`^-2 though, which is pretty steep, so we use a larger- # than-desireable scale for the __name__ != '__main__' branch. This is known to lead to a bias # in PSF size, which we attempt to account for below when actually comparing FFT PSF moments to # photon-shooting PSF moments. Note that we don't need to apply such a correction when # comparing the photon-shooting PSF to the analytic VonKarman PSF since these both avoid the # screen_scale problem to begin with. (Even though we do generate screens for the # photon-shooting PSF, because we truncate the power spectrum above kcrit, we don't require as # high of resolution). if __name__ == '__main__': screen_scale = 0.025 # m else: screen_scale = 0.1 # m max_speed = 20 # m/s rng = galsim.BaseDeviate(seed) u = galsim.UniformDeviate(rng) # Use atmospheric weights from 1998 Gemini site selection process as something reasonably # realistic. (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable( Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') alts = np.max(Ellerbroek_alts)*np.arange(nlayers)/(nlayers-1) weights = Ellerbroek_interp(alts) weights /= sum(weights) spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500s = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(nlayers): spd.append(u()*max_speed) dirn.append(u()*360*galsim.degrees) r0_500s.append(r0_500*weights[i]**(-3./5)) rng2 = rng.duplicate() atm = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts, rng=rng, screen_size=screen_size, screen_scale=screen_scale) # Make a second atmosphere to use for geometric photon-shooting atm2 = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts, rng=rng2, screen_size=screen_size, screen_scale=screen_scale) # These should be equal at the moment, before we've actually instantiated any screens by drawing # with them. assert atm == atm2 lam = 500.0 diam = 4.0 pad_factor = 0.5 oversampling = 0.5 aper = galsim.Aperture(diam=diam, lam=lam, screen_list=atm, pad_factor=pad_factor, oversampling=oversampling) xs = np.empty((10,), dtype=float) ys = np.empty((10,), dtype=float) u.generate(xs) u.generate(ys) thetas = [(x*galsim.degrees, y*galsim.degrees) for x, y in zip(xs, ys)] if __name__ == '__main__': exptime = 15.0 time_step = 0.05 centroid_tolerance = 0.06 size_tolerance = 0.06 # absolute size_bias = 0.02 # as a fraction shape_tolerance = 0.01 else: exptime = 1.0 time_step = 0.1 centroid_tolerance = 0.3 size_tolerance = 0.3 size_bias = 0.15 shape_tolerance = 0.04 psfs = [atm.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper) for th in thetas] psfs2 = [atm2.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper, time_step=time_step) for th in thetas] shoot_moments = [] fft_moments = [] vk = galsim.VonKarman(lam=lam, r0=r0_500*(lam/500)**1.2, L0=L0) airy = galsim.Airy(lam=lam, diam=diam) obj = galsim.Convolve(vk, airy) vkImg = obj.drawImage(nx=48, ny=48, scale=0.05) vkMom = galsim.hsm.FindAdaptiveMom(vkImg) for psf, psf2 in zip(psfs, psfs2): im_shoot = psf.drawImage(nx=48, ny=48, scale=0.05, method='phot', n_photons=100000, rng=rng) im_fft = psf2.drawImage(nx=48, ny=48, scale=0.05) # at this point, the atms should be different. assert atm != atm2 shoot_moment = galsim.hsm.FindAdaptiveMom(im_shoot) fft_moment = galsim.hsm.FindAdaptiveMom(im_fft) print() print() print() print(shoot_moment.observed_shape.g1) print(fft_moment.observed_shape.g1) # import matplotlib.pyplot as plt # fig, axes = plt.subplots(ncols=2) # axes[0].imshow(im_shoot.array) # axes[1].imshow(im_fft.array) # plt.show() np.testing.assert_allclose( shoot_moment.moments_centroid.x, fft_moment.moments_centroid.x, rtol=0, atol=centroid_tolerance, err_msg='Phase gradient centroid x not close to fft centroid') np.testing.assert_allclose( shoot_moment.moments_centroid.y, fft_moment.moments_centroid.y, rtol=0, atol=centroid_tolerance, err_msg='Phase gradient centroid y not close to fft centroid') np.testing.assert_allclose( shoot_moment.moments_sigma, fft_moment.moments_sigma*(1+size_bias), rtol=0, atol=size_tolerance, err_msg='Phase gradient sigma not close to fft sigma') np.testing.assert_allclose( shoot_moment.moments_sigma, vkMom.moments_sigma, rtol=0.1, atol=0, err_msg='Phase gradient sigma not close to infinite exposure analytic sigma' ) np.testing.assert_allclose( shoot_moment.observed_shape.g1, fft_moment.observed_shape.g1, rtol=0, atol=shape_tolerance, err_msg='Phase gradient shape g1 not close to fft shape') np.testing.assert_allclose( shoot_moment.observed_shape.g2, fft_moment.observed_shape.g2, rtol=0, atol=shape_tolerance, err_msg='Phase gradient shape g2 not close to fft shape') shoot_moments.append(shoot_moment) fft_moments.append(fft_moment) # I cheated. Here's code to evaluate how small I could potentially set the tolerances above. # I think they're all fine, but this is admittedly a tad bit backwards. best_size_bias = np.mean([s1.moments_sigma/s2.moments_sigma for s1, s2 in zip(shoot_moments, fft_moments)]) print("best_size_bias = ", best_size_bias) print("xcentroid") print(max(np.abs([s1.moments_centroid.x - s2.moments_centroid.x for s1, s2 in zip(shoot_moments, fft_moments)]))) print("ycentroid") print(max(np.abs([s1.moments_centroid.y - s2.moments_centroid.y for s1, s2 in zip(shoot_moments, fft_moments)]))) print("size") print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(1+size_bias) for s1, s2 in zip(shoot_moments, fft_moments)]))) print("bestsize") print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(best_size_bias) for s1, s2 in zip(shoot_moments, fft_moments)]))) print("g1") print(max(np.abs([s1.observed_shape.g1 - s2.observed_shape.g1 for s1, s2 in zip(shoot_moments, fft_moments)]))) print("g2") print(max(np.abs([s1.observed_shape.g2 - s2.observed_shape.g2 for s1, s2 in zip(shoot_moments, fft_moments)]))) # import matplotlib.pyplot as plt # fig, ax = plt.subplots(nrows=1, ncols=1) # ax.scatter( # [s.observed_shape.g1 for s in shoot_moments], # [s.observed_shape.g1 for s in fft_moments] # ) # xlim = ax.get_xlim() # ylim = ax.get_ylim() # lim = (min(xlim[0], ylim[0]), max(xlim[1], ylim[1])) # ax.set_xlim(lim) # ax.set_ylim(lim) # ax.plot([-100, 100], [-100, 100]) # plt.show() # Verify that shoot with rng=None runs psf.shoot(100, rng=None) # Check that second_kick=False and second_kick=GSObject also run, and that we can shoot # photons with these settings. for second_kick in [False, galsim.Gaussian(fwhm=1)]: psf = atm.makePSF(lam=500.0, exptime=10, aper=aper, second_kick=second_kick) assert psf.second_kick == second_kick img = psf.drawImage(nx=64, ny=64, scale=0.1, method='phot', n_photons=100) # Verify that we can phase_gradient_shoot with 0 or 1 photons. psf.shoot(0) psf.shoot(1)
def __init__(self, no_analysis=False, **args): if set(args.keys()) != set(Survey._parameter_names): raise RuntimeError('Missing or extra arguments provided to Survey constructor.') self.args = args self.__dict__.update(args) # Build our atmospheric PSF model. atmospheric_psf_fwhm = self.zenith_psf_fwhm*self.airmass**0.6 if self.atmospheric_psf_beta > 0: atmospheric_psf_model = galsim.Moffat( beta = self.atmospheric_psf_beta, fwhm = atmospheric_psf_fwhm) else: atmospheric_psf_model = galsim.Kolmogorov(fwhm = atmospheric_psf_fwhm) # Shear the atmospheric PSF, if necessary. Note that GalSim uses g1,g2 for the # |g| = (a-b)/(a+b) ellipticity spinor and e1,e2 for |e| = (a^2-b^2)/(a^2+b^2). if self.atmospheric_psf_e1 != 0 or self.atmospheric_psf_e2 != 0: atmospheric_psf_model = atmospheric_psf_model.shear( g1 = self.atmospheric_psf_e1, g2 = self.atmospheric_psf_e2) # Combine with our optical PSF model, if any. if self.mirror_diameter > 0: lambda_over_diameter = 3600*math.degrees( 1e-10*Survey._central_wavelength[self.filter_band]/self.mirror_diameter) area_ratio = self.effective_area/(math.pi*(0.5*self.mirror_diameter)**2) if area_ratio <= 0 or area_ratio > 1: raise RuntimeError('Incompatible effective-area and mirror-diameter values.') self.obscuration_fraction = math.sqrt(1 - area_ratio) optical_psf_model = galsim.Airy(lam_over_diam = lambda_over_diameter, obscuration = self.obscuration_fraction) self.psf_model = galsim.Convolve(atmospheric_psf_model,optical_psf_model) else: self.psf_model = atmospheric_psf_model self.obscuration_fraction = 0. # Draw a centered PSF image covering 10x the atmospheric PSF FWHM. psf_size_pixels = 2*int(math.ceil(10*atmospheric_psf_fwhm/self.pixel_scale)) self.psf_image = galsim.Image(psf_size_pixels,psf_size_pixels,scale = self.pixel_scale) self.psf_model.drawImage(image = self.psf_image) if not no_analysis: # Draw a (temporary) high-resolution (10x) image covering the same area. zoom = 10 hires_psf_image = galsim.Image(zoom*psf_size_pixels,zoom*psf_size_pixels,scale = self.pixel_scale/zoom) self.psf_model.drawImage(image = hires_psf_image) # Calculate the unweighted second moments in arcsecs**2 of the hi-res PSF image. hires_sum = np.sum(hires_psf_image.array) hires_grid = (self.pixel_scale/zoom)*(np.arange(zoom*psf_size_pixels) - 0.5*zoom*psf_size_pixels + 0.5) hires_x,hires_y = np.meshgrid(hires_grid,hires_grid) psf_x = np.sum(hires_psf_image.array*hires_x)/hires_sum psf_y = np.sum(hires_psf_image.array*hires_x)/hires_sum hires_x -= psf_x hires_y -= psf_y psf_xx = np.sum(hires_psf_image.array*hires_x**2)/hires_sum psf_xy = np.sum(hires_psf_image.array*hires_x*hires_y)/hires_sum psf_yy = np.sum(hires_psf_image.array*hires_y**2)/hires_sum self.psf_second_moments = np.array(((psf_xx,psf_xy),(psf_xy,psf_yy))) # Calculate the corresponding PSF sizes |Q|**0.25 and (0.5*trQ)**0.5 self.psf_sigma_m = np.power(np.linalg.det(self.psf_second_moments),0.25) self.psf_sigma_p = np.sqrt(0.5*np.trace(self.psf_second_moments)) # Also calculate the PSF size as |Q|**0.25 using adaptive weighted second moments # of the non-hires PSF image. try: hsm_results = galsim.hsm.FindAdaptiveMom(self.psf_image) self.psf_size_hsm = hsm_results.moments_sigma*self.pixel_scale except RuntimeError as e: raise RuntimeError('Unable to calculate adaptive moments of PSF image.') # Calculate the mean sky background level in detected electrons per pixel. self.mean_sky_level = self.get_flux(self.sky_brightness)*self.pixel_scale**2 # Create an empty image using (0,0) to index the lower-left corner pixel. self.image_bounds = galsim.BoundsI(0,self.image_width-1,0,self.image_height-1) self.image = galsim.Image(bounds = self.image_bounds,scale=self.pixel_scale, dtype = np.float32)
sersic_n = 6. print 'Creating Sersic profile with n=', sersic_n, ' and hlr=', test_hlr s = galsim.Sersic(sersic_n, half_light_radius=test_hlr) print 'Checking radial integration of profile....' hlr_sum = radial_integrate(s, 0., test_hlr, 1.e-4) print 'Sum of profile to half-light-radius: ', hlr_sum # make Sersic convolved with some ground-based PSF (Kolmogorov x optical PSF) print "Testing sersic ground-based sim..." ground_psf_fwhm = 0.7 ground_pix_scale = 0.2 space_psf_fwhm = 0.1 imsize = 512 n_photons = 1000000 psf = galsim.Kolmogorov(fwhm=ground_psf_fwhm) opt_psf = galsim.Airy(space_psf_fwhm) pix = galsim.Pixel(ground_pix_scale) epsf = galsim.Convolve(psf, opt_psf, pix) obj_epsf = galsim.Convolve(s, psf, opt_psf, pix) obj_psf = galsim.Convolve(s, psf, opt_psf) # compare photon-shot vs. Fourier draw image, directly and with moments im_draw = galsim.ImageF(imsize, imsize) im_shoot = galsim.ImageF(imsize, imsize) im_epsf = galsim.ImageF(imsize, imsize) im_draw = obj_epsf.draw(image=im_draw, dx=ground_pix_scale, wmult=4.) im_shoot, _ = obj_psf.drawShoot(image=im_shoot, dx=ground_pix_scale, n_photons=n_photons) im_epsf = epsf.draw(image=im_epsf, dx=ground_pix_scale) res_draw = im_draw.FindAdaptiveMom() res_shoot = im_shoot.FindAdaptiveMom()
def test_flip(): """Test several ways to flip a profile """ # The Shapelet profile has the advantage of being fast and not circularly symmetric, so # it is a good test of the actual code for doing the flips (in SBTransform). # But since the bug Rachel reported in #645 was actually in SBInterpolatedImage # (one calculation implicitly assumed dx > 0), it seems worthwhile to run through all the # classes to make sure we hit everything with negative steps for dx and dy. prof_list = [ galsim.Shapelet(sigma=0.17, order=2, bvec=[1.7, 0.01,0.03, 0.29, 0.33, -0.18]), ] if __name__ == "__main__": image_dir = './real_comparison_images' catalog_file = 'test_catalog.fits' rgc = galsim.RealGalaxyCatalog(catalog_file, dir=image_dir) # Some of these are slow, so only do the Shapelet test as part of the normal unit tests. prof_list += [ galsim.Airy(lam_over_diam=0.17, flux=1.7), galsim.Airy(lam_over_diam=0.17, obscuration=0.2, flux=1.7), # Box gets rendered with real-space convolution. The default accuracy isn't quite # enough to get the flip to match at 6 decimal places. galsim.Box(0.17, 0.23, flux=1.7, gsparams=galsim.GSParams(realspace_relerr=1.e-6)), # Without being convolved by anything with a reasonable k cutoff, this needs # a very large fft. galsim.DeVaucouleurs(half_light_radius=0.17, flux=1.7), # I don't really understand why this needs a lower maxk_threshold to work, but # without it, the k-space tests fail. galsim.Exponential(scale_radius=0.17, flux=1.7, gsparams=galsim.GSParams(maxk_threshold=1.e-4)), galsim.Gaussian(sigma=0.17, flux=1.7), galsim.Kolmogorov(fwhm=0.17, flux=1.7), galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7), galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7, trunc=0.82), galsim.OpticalPSF(lam_over_diam=0.17, obscuration=0.2, nstruts=6, coma1=0.2, coma2=0.5, defocus=-0.1, flux=1.7), # Like with Box, we need to increase the real-space convolution accuracy. # This time lowering both relerr and abserr. galsim.Pixel(0.23, flux=1.7, gsparams=galsim.GSParams(realspace_relerr=1.e-6, realspace_abserr=1.e-8)), # Note: RealGalaxy should not be rendered directly because of the deconvolution. # Here we convolve it by a Gaussian that is slightly larger than the original PSF. galsim.Convolve([ galsim.RealGalaxy(rgc, index=0, flux=1.7), # "Real" RealGalaxy galsim.Gaussian(sigma=0.08) ]), galsim.Convolve([ galsim.RealGalaxy(rgc, index=1, flux=1.7), # "Fake" RealGalaxy galsim.Gaussian(sigma=0.08) ]), # (cf. test_real.py) galsim.Spergel(nu=-0.19, half_light_radius=0.17, flux=1.7), galsim.Spergel(nu=0., half_light_radius=0.17, flux=1.7), galsim.Spergel(nu=0.8, half_light_radius=0.17, flux=1.7), galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7), galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7, trunc=0.82), # The shifts here caught a bug in how SBTransform handled the recentering. # Two of the shifts (0.125 and 0.375) lead back to 0.0 happening on an integer # index, which now works correctly. galsim.Sum([ galsim.Gaussian(sigma=0.17, flux=1.7).shift(-0.2,0.125), galsim.Exponential(scale_radius=0.23, flux=3.1).shift(0.375,0.23)]), galsim.TopHat(0.23, flux=1.7), # Box and Pixel use real-space convolution. Convolve with a Gaussian to get fft. galsim.Convolve([ galsim.Box(0.17, 0.23, flux=1.7).shift(-0.2,0.1), galsim.Gaussian(sigma=0.09) ]), galsim.Convolve([ galsim.TopHat(0.17, flux=1.7).shift(-0.275,0.125), galsim.Gaussian(sigma=0.09) ]), # Test something really crazy with several layers worth of transformations galsim.Convolve([ galsim.Sum([ galsim.Gaussian(sigma=0.17, flux=1.7).shear(g1=0.1,g2=0.2).shift(2,3), galsim.Kolmogorov(fwhm=0.33, flux=3.9).transform(0.31,0.19,-0.23,0.33) * 88., galsim.Box(0.11, 0.44, flux=4).rotate(33 * galsim.degrees) / 1.9 ]).shift(-0.3,1), galsim.AutoConvolve(galsim.TopHat(0.5).shear(g1=0.3,g2=0)).rotate(3*galsim.degrees), (galsim.AutoCorrelate(galsim.Box(0.2, 0.3)) * 11).shift(3,2).shift(2,-3) * 0.31 ]).shift(0,0).transform(0,-1,-1,0).shift(-1,1) ] s = galsim.Shear(g1=0.11, g2=-0.21) s1 = galsim.Shear(g1=0.11, g2=0.21) # Appropriate for the flips around x and y axes s2 = galsim.Shear(g1=-0.11, g2=-0.21) # Appropriate for the flip around x=y # Also use shears with just a g1 to get dx != dy, but dxy, dyx = 0. q = galsim.Shear(g1=0.11, g2=0.) q1 = galsim.Shear(g1=0.11, g2=0.) # Appropriate for the flips around x and y axes q2 = galsim.Shear(g1=-0.11, g2=0.) # Appropriate for the flip around x=y decimal=6 # Oddly, these aren't as precise as I would have expected. # Even when we only go to this many digits of accuracy, the Exponential needed # a lower than default value for maxk_threshold. im = galsim.ImageD(16,16, scale=0.05) for prof in prof_list: print('prof = ',prof) # Not all profiles are expected to have a max_sb value close to the maximum pixel value, # so mark the ones where we don't want to require this to be true. close_maxsb = True name = str(prof) if ('DeVauc' in name or 'Sersic' in name or 'Spergel' in name or 'Optical' in name or 'shift' in name): close_maxsb = False # Make sure we hit all 4 fill functions. # image_x uses fillXValue with izero, jzero # image_x1 uses fillXValue with izero, jzero, and unequal dx,dy # image_x2 uses fillXValue with dxy, dyx # image_k uses fillKValue with izero, jzero # image_k1 uses fillKValue with izero, jzero, and unequal dx,dy # image_k2 uses fillKValue with dxy, dyx image_x = prof.drawImage(image=im.copy(), method='no_pixel') image_x1 = prof.shear(q).drawImage(image=im.copy(), method='no_pixel') image_x2 = prof.shear(s).drawImage(image=im.copy(), method='no_pixel') image_k = prof.drawImage(image=im.copy()) image_k1 = prof.shear(q).drawImage(image=im.copy()) image_k2 = prof.shear(s).drawImage(image=im.copy()) if close_maxsb: np.testing.assert_allclose( image_x.array.max(), prof.max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image_x1.array.max(), prof.shear(q).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image_x2.array.max(), prof.shear(s).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") # Flip around y axis (i.e. x -> -x) flip1 = prof.transform(-1, 0, 0, 1) image2_x = flip1.drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x.array, image2_x.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed x test") image2_x1 = flip1.shear(q1).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x1.array, image2_x1.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed x1 test") image2_x2 = flip1.shear(s1).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x2.array, image2_x2.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed x2 test") image2_k = flip1.drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k.array, image2_k.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed k test") image2_k1 = flip1.shear(q1).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k1.array, image2_k1.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed k1 test") image2_k2 = flip1.shear(s1).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k2.array, image2_k2.array[:,::-1], decimal=decimal, err_msg="Flipping image around y-axis failed k2 test") if close_maxsb: np.testing.assert_allclose( image2_x.array.max(), flip1.max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x1.array.max(), flip1.shear(q).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x2.array.max(), flip1.shear(s).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") # Flip around x axis (i.e. y -> -y) flip2 = prof.transform(1, 0, 0, -1) image2_x = flip2.drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x.array, image2_x.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed x test") image2_x1 = flip2.shear(q1).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x1.array, image2_x1.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed x1 test") image2_x2 = flip2.shear(s1).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x2.array, image2_x2.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed x2 test") image2_k = flip2.drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k.array, image2_k.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed k test") image2_k1 = flip2.shear(q1).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k1.array, image2_k1.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed k1 test") image2_k2 = flip2.shear(s1).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k2.array, image2_k2.array[::-1,:], decimal=decimal, err_msg="Flipping image around x-axis failed k2 test") if close_maxsb: np.testing.assert_allclose( image2_x.array.max(), flip2.max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x1.array.max(), flip2.shear(q).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x2.array.max(), flip2.shear(s).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") # Flip around x=y (i.e. y -> x, x -> y) flip3 = prof.transform(0, 1, 1, 0) image2_x = flip3.drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x.array, np.transpose(image2_x.array), decimal=decimal, err_msg="Flipping image around x=y failed x test") image2_x1 = flip3.shear(q2).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x1.array, np.transpose(image2_x1.array), decimal=decimal, err_msg="Flipping image around x=y failed x1 test") image2_x2 = flip3.shear(s2).drawImage(image=im.copy(), method='no_pixel') np.testing.assert_array_almost_equal( image_x2.array, np.transpose(image2_x2.array), decimal=decimal, err_msg="Flipping image around x=y failed x2 test") image2_k = flip3.drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k.array, np.transpose(image2_k.array), decimal=decimal, err_msg="Flipping image around x=y failed k test") image2_k1 = flip3.shear(q2).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k1.array, np.transpose(image2_k1.array), decimal=decimal, err_msg="Flipping image around x=y failed k1 test") image2_k2 = flip3.shear(s2).drawImage(image=im.copy()) np.testing.assert_array_almost_equal( image_k2.array, np.transpose(image2_k2.array), decimal=decimal, err_msg="Flipping image around x=y failed k2 test") if close_maxsb: np.testing.assert_allclose( image2_x.array.max(), flip3.max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x1.array.max(), flip3.shear(q).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") np.testing.assert_allclose( image2_x2.array.max(), flip3.shear(s).max_sb*im.scale**2, rtol=0.2, err_msg="max_sb did not match maximum pixel value") do_pickle(prof, lambda x: x.drawImage(image=im.copy(), method='no_pixel')) do_pickle(flip1, lambda x: x.drawImage(image=im.copy(), method='no_pixel')) do_pickle(flip2, lambda x: x.drawImage(image=im.copy(), method='no_pixel')) do_pickle(flip3, lambda x: x.drawImage(image=im.copy(), method='no_pixel')) do_pickle(prof) do_pickle(flip1) do_pickle(flip2) do_pickle(flip3)
def main(argv): """ Make a fits image cube where each frame has two images of the same galaxy drawn with regular FFT convolution and with photon shooting. We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random) fluxes, sizes, and shapes. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo7") # To turn off logging: #logger.propagate = False # Define some parameters we'll use below. # Make output directory if not already present. if not os.path.isdir('output'): os.mkdir('output') file_name = os.path.join('output', 'cube_phot.fits.gz') random_seed = 553728 sky_level = 1.e4 # ADU / arcsec^2 pixel_scale = 0.28 # arcsec nx = 64 ny = 64 gal_flux_min = 1.e4 # Range for galaxy flux gal_flux_max = 1.e5 gal_hlr_min = 0.3 # arcsec gal_hlr_max = 1.3 # arcsec gal_e_min = 0. # Range for ellipticity gal_e_max = 0.8 psf_fwhm = 0.65 # arcsec # This script is set up as a comparison between using FFTs for doing the convolutions and # shooting photons. The two methods have trade-offs in speed and accuracy which vary # with the kind of profile being drawn and the S/N of the object, among other factors. # In addition, for each method, there are a number of parameters GalSim uses that control # aspects of the calculation that further affect the speed and accuracy. # # We encapsulate these parameters with an object called GSParams. The default values # are intended to be accurate enough for normal precision shear tests, without sacrificing # too much speed. # # Any PSF or galaxy object can be given a gsparams argument on construction that can # have different values to make the calculation more or less accurate (typically trading # off for speed or memory). # # In this script, we adjust some of the values slightly, just to show you how it works. # You could play around with these values and see what effect they have on the drawn images. # Usually, it requires a pretty drastic change in these parameters for you to be able to # notice the difference by eye. But subtle effects that may impact the shapes of galaxies # can happen well before then. # Type help(galsim.GSParams) for the complete list of parameters and more detailed # documentation, including the default values for each parameter. gsparams = galsim.GSParams( alias_threshold= 1.e-2, # maximum fractional flux that may be aliased around edge of FFT maxk_threshold= 2.e-3, # k-values less than this may be excluded off edge of FFT xvalue_accuracy= 1.e-4, # approximations in real space aim to be this accurate kvalue_accuracy= 1.e-4, # approximations in fourier space aim to be this accurate shoot_accuracy= 1.e-4, # approximations in photon shooting aim to be this accurate minimum_fft_size=64) # minimum size of ffts logger.info('Starting demo script 7') # Make the pixel: pix = galsim.Pixel(xw=pixel_scale) # Make the PSF profiles: psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams) psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams) psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams) psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm, flux=0.2, gsparams=gsparams) psf3 = psf3_inner + psf3_outer atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams) optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm, obscuration=0.4, defocus=0.1, astig1=0.3, astig2=-0.2, coma1=0.2, coma2=0.1, spher=-0.3, gsparams=gsparams) psf4 = galsim.Convolve([atmos, optics ]) # Convolve inherits the gsparams from the first # item in the list. (Or you can supply a gsparams # argument explicitly if you want to override this.) atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams) optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams) psf5 = galsim.Convolve([atmos, optics]) psfs = [psf1, psf2, psf3, psf4, psf5] psf_names = [ "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF", "Kolmogorov * Airy" ] psf_times = [0, 0, 0, 0, 0] psf_fft_times = [0, 0, 0, 0, 0] psf_phot_times = [0, 0, 0, 0, 0] # Make the galaxy profiles: gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams) gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams) gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams) gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams) bulge = galsim.Sersic(half_light_radius=0.7, n=3.2, gsparams=gsparams) disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams) gal5 = 0.4 * bulge + 0.6 * disk # Net half-light radius is only approximate for this one. gals = [gal1, gal2, gal3, gal4, gal5] gal_names = [ "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic", "Bulge + Disk" ] gal_times = [0, 0, 0, 0, 0] gal_fft_times = [0, 0, 0, 0, 0] gal_phot_times = [0, 0, 0, 0, 0] # Other times to keep track of: setup_times = 0 fft_times = 0 phot_times = 0 noise_times = 0 # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape. all_images = [] k = 0 for ipsf in range(len(psfs)): psf = psfs[ipsf] psf_name = psf_names[ipsf] for igal in range(len(gals)): gal = gals[igal] gal_name = gal_names[igal] for i in range(4): logger.debug('Start work on image %d', i) t1 = time.time() # Initialize the random number generator we will be using. rng = galsim.UniformDeviate(random_seed + k) # Get a new copy, we'll want to keep the original unmodified. gal1 = gal.copy() # Generate random variates: flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min gal1.setFlux(flux) hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min gal1.applyDilation(hlr) beta_ellip = rng() * 2 * math.pi * galsim.radians ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min gal_shape = galsim.Shear(e=ellip, beta=beta_ellip) gal1.applyShear(gal_shape) # Build the final object by convolving the galaxy, PSF and pixel response. final = galsim.Convolve([psf, pix, gal1]) # For photon shooting, need a version without the pixel (see below). final_nopix = galsim.Convolve([psf, gal1]) # Create the large, double width output image image = galsim.ImageF(2 * nx + 2, ny) # Rather than provide a dx= argument to the draw commands, we can also # set the pixel scale in the image itself with setScale. image.setScale(pixel_scale) # Assign the following two "ImageViews", fft_image and phot_image. # Using the syntax below, these are views into the larger image. # Changes/additions to the sub-images referenced by the views are automatically # reflected in the original image. fft_image = image[galsim.BoundsI(1, nx, 1, ny)] phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)] logger.debug( ' Read in training sample galaxy and PSF from file') t2 = time.time() # Draw the profile final.draw(fft_image) logger.debug( ' Drew fft image. Total drawn flux = %f. .flux = %f', fft_image.array.sum(), final.getFlux()) t3 = time.time() # Add Poisson noise sky_level_pixel = sky_level * pixel_scale**2 fft_image.addNoise( galsim.PoissonNoise(rng, sky_level=sky_level_pixel)) t4 = time.time() # The next two lines are just to get the output from this demo script # to match the output from the parsing of demo7.yaml. rng = galsim.UniformDeviate(random_seed + k) rng() rng() rng() rng() # Repeat for photon shooting image. # Photon shooting automatically convolves by the pixel, so we've made sure not # to include it in the profile! final_nopix.drawShoot(phot_image, max_extra_noise=sky_level_pixel / 100, rng=rng) t5 = time.time() # For photon shooting, galaxy already has Poisson noise, so we want to make # sure not to add that noise again! Thus, we just add sky noise, which # is Poisson with the mean = sky_level_pixel pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel) # DeviateNoise just adds the action of the given deviate to every pixel. phot_image.addNoise(galsim.DeviateNoise(pd)) # For PoissonDeviate, the mean is not zero, so for a background-subtracted # image, we need to subtract the mean back off when we are done. phot_image -= sky_level_pixel logger.debug( ' Added Poisson noise. Image fluxes are now %f and %f', fft_image.array.sum(), phot_image.array.sum()) t6 = time.time() # Store that into the list of all images all_images += [image] k = k + 1 logger.info( '%d: %s * %s, flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)', k, gal_name, psf_name, flux, hlr, gal_shape.getE1(), gal_shape.getE2()) logger.debug(' Times: %f, %f, %f, %f, %f', t2 - t1, t3 - t2, t4 - t3, t5 - t4, t6 - t5) psf_times[ipsf] += t6 - t1 psf_fft_times[ipsf] += t3 - t2 psf_phot_times[ipsf] += t5 - t4 gal_times[igal] += t6 - t1 gal_fft_times[igal] += t3 - t2 gal_phot_times[igal] += t5 - t4 setup_times += t2 - t1 fft_times += t3 - t2 phot_times += t5 - t4 noise_times += t4 - t3 + t6 - t5 logger.info('Done making images of galaxies') logger.info('') logger.info('Some timing statistics:') logger.info(' Total time for setup steps = %f', setup_times) logger.info(' Total time for regular fft drawing = %f', fft_times) logger.info(' Total time for photon shooting = %f', phot_times) logger.info(' Total time for adding noise = %f', noise_times) logger.info('') logger.info('Breakdown by PSF type:') for ipsf in range(len(psfs)): logger.info(' %s: Total time = %f (fft: %f, phot: %f)', psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf], psf_phot_times[ipsf]) logger.info('') logger.info('Breakdown by Galaxy type:') for igal in range(len(gals)): logger.info(' %s: Total time = %f (fft: %f, phot: %f)', gal_names[igal], gal_times[igal], gal_fft_times[igal], gal_phot_times[igal]) logger.info('') # Now write the image to disk. # With any write command, you can optionally compress the file using several compression # schemes: # 'gzip' uses gzip on the full output file. # 'bzip2' uses bzip2 on the full output file. # 'rice' uses rice compression on the image, leaving the fits headers readable. # 'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable. # 'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it # doesn't work for writeCube. # 'plio' uses plio on the image, but it is only valid for positive integer data. # Furthermore, the first three have standard filename extensions associated with them, # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz', # the corresponding compression will be selected automatically. # In other words, the `compression='gzip'` specification is actually optional here: galsim.fits.writeCube(all_images, file_name, compression='gzip') logger.info('Wrote fft image to fits data cube %r', file_name)
def run_tests(random_seed, outfile, config=None, gsparams=None, wmult=None, logger=None, fail_value=-666.): """Run a full set of tests, writing pickled tuple output to outfile. """ import sys import cPickle import numpy as np import galsim import galaxy_sample # Load up the comparison_utilities module from the parent directory sys.path.append('..') import comparison_utilities if config is None: use_config = False if gsparams is None: import warnings warnings.warn("No gsparams provided to run_tests?") if wmult is None: raise ValueError("wmult must be set if config=None.") else: use_config = True if gsparams is not None: import warnings warnings.warn( "gsparams is provided as a kwarg but the config['image']['gsparams'] will take " + "precedence.") if wmult is not None: import warnings warnings.warn( "wmult is provided as a kwarg but the config['image']['wmult'] will take " + "precedence.") # Get galaxy sample n_cosmos, hlr_cosmos, gabs_cosmos = galaxy_sample.get() # Only take the first NOBS objects n_cosmos = n_cosmos[0:NOBS] hlr_cosmos = hlr_cosmos[0:NOBS] gabs_cosmos = gabs_cosmos[0:NOBS] ntest = len(SERSIC_N_TEST) # Setup a UniformDeviate ud = galsim.UniformDeviate(random_seed) # Open the output file and write a header: fout = open(outfile, 'wb') fout.write( '# g1obs_draw g2obs_draw sigma_draw delta_g1obs delta_g2obs delta_sigma ' + 'err_g1obs err_g2obs err_sigma\n') # Start looping through the sample objects and collect the results for i, hlr, gabs in zip(range(NOBS), hlr_cosmos, gabs_cosmos): print "Testing galaxy #"+str(i+1)+"/"+str(NOBS)+\ " with (hlr, |g|) = "+str(hlr)+", "+str(gabs) random_theta = 2. * np.pi * ud() g1 = gabs * np.cos(2. * random_theta) g2 = gabs * np.sin(2. * random_theta) for j, sersic_n in zip(range(ntest), SERSIC_N_TEST): print "Exploring Sersic n = " + str(sersic_n) if use_config: # Increment the random seed so that each test gets a unique one config['image'][ 'random_seed'] = random_seed + i * NOBS * ntest + j * ntest + 1 config['gal'] = { "type": "Sersic", "n": sersic_n, "half_light_radius": hlr, "ellip": { "type": "G1G2", "g1": g1, "g2": g2 } } config['psf'] = { "type": "Airy", "lam_over_diam": PSF_LAM_OVER_DIAM } try: results = comparison_utilities.compare_dft_vs_photon_config( config, abs_tol_ellip=TOL_ELLIP, abs_tol_size=TOL_SIZE, logger=logger) test_ran = True except RuntimeError as err: test_ran = False pass # Uncomment lines below to ouput a check image #import copy #checkimage = galsim.config.BuildImage(copy.deepcopy(config))[0] #im = first element #checkimage.write('junk_'+str(i + 1)+'_'+str(j + 1)+'.fits') else: test_gsparams = galsim.GSParams(maximum_fft_size=MAX_FFT_SIZE) galaxy = galsim.Sersic(sersic_n, half_light_radius=hlr, gsparams=test_gsparams) galaxy.applyShear(g1=g1, g2=g2) psf = galsim.Airy(lam_over_diam=PSF_LAM_OVER_DIAM, gsparams=test_gsparams) try: results = comparison_utilities.compare_dft_vs_photon_object( galaxy, psf_object=psf, rng=ud, pixel_scale=PIXEL_SCALE, size=IMAGE_SIZE, abs_tol_ellip=TOL_ELLIP, abs_tol_size=TOL_SIZE, n_photons_per_trial=NPHOTONS, wmult=wmult) test_ran = True except RuntimeError, err: test_ran = False pass if not test_ran: import warnings warnings.warn('RuntimeError encountered for galaxy ' + str(i + 1) + '/' + str(NOBS) + ' with ' + 'Sersic n = ' + str(sersic_n) + ': ' + str(err)) fout.write('%e %e %e %e %e %e %e %e %e %e %e %e %e\n' % (fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value, fail_value)) fout.flush() else: fout.write('%e %e %e %e %e %e %e %e %e %e %e %e %e\n' % (results.g1obs_draw, results.g2obs_draw, results.sigma_draw, results.delta_g1obs, results.delta_g2obs, results.delta_sigma, results.err_g1obs, results.err_g2obs, results.err_sigma, sersic_n, hlr, g1, g2)) fout.flush()
t1 = time.time() gd = galsim.GaussianDeviate(rseed) dx_cosmos = 0.03 # Non-unity, non-default value to be used below cn = galsim.getCOSMOSNoise( gd, '../../../examples/data/acs_I_unrot_sci_20_cf.fits', dx_cosmos=dx_cosmos) cn.setVariance(1000.) # Again chosen to be non-unity # Define a PSF with which to convolve the noise field, one WITHOUT 2-fold rotational symmetry # (see test_autocorrelate in test_SBProfile.py for more info as to why this is relevant) # Make a relatively realistic mockup of a GREAT3 target image lam_over_diam_cosmos = (814.e-9 / 2.4) * (180. / np.pi) * 3600. # ~lamda/D in arcsec lam_over_diam_ground = lam_over_diam_cosmos * 2.4 / 4. # Generic 4m at same lambda psf_cosmos = galsim.Convolve([ galsim.Airy(lam_over_diam=lam_over_diam_cosmos, obscuration=0.4), galsim.Pixel(0.05) ]) psf_ground = galsim.Convolve([ galsim.Kolmogorov(fwhm=0.8), galsim.Pixel(0.18), galsim.OpticalPSF(lam_over_diam=lam_over_diam_ground, coma2=0.4, defocus=-0.6) ]) psf_shera = galsim.Convolve([ psf_ground, (galsim.Deconvolve(psf_cosmos)).createSheared(g1=0.03, g2=-0.01) ]) # Then define the convolved cosmos correlated noise model conv_cn = cn.copy()
def main(argv): """ Make a fits image cube where each frame has two images of the same galaxy drawn with regular FFT convolution and with photon shooting. We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random) fluxes, sizes, and shapes. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo7") # To turn off logging: #logger.propagate = False # To turn on the debugging messages: #logger.setLevel(logging.DEBUG) # Define some parameters we'll use below. # Make output directory if not already present. if not os.path.isdir('output'): os.mkdir('output') file_name = os.path.join('output', 'cube_phot.fits.gz') random_seed = 553728 sky_level = 1.e4 # ADU / arcsec^2 pixel_scale = 0.28 # arcsec nx = 64 ny = 64 gal_flux_min = 1.e4 # Range for galaxy flux gal_flux_max = 1.e5 gal_hlr_min = 0.3 # arcsec gal_hlr_max = 1.3 # arcsec gal_e_min = 0. # Range for ellipticity gal_e_max = 0.8 psf_fwhm = 0.65 # arcsec # This script is set up as a comparison between using FFTs for doing the convolutions and # shooting photons. The two methods have trade-offs in speed and accuracy which vary # with the kind of profile being drawn and the S/N of the object, among other factors. # In addition, for each method, there are a number of parameters GalSim uses that control # aspects of the calculation that further affect the speed and accuracy. # # We encapsulate these parameters with an object called GSParams. The default values # are intended to be accurate enough for normal precision shear tests, without sacrificing # too much speed. # # Any PSF or galaxy object can be given a gsparams argument on construction that can # have different values to make the calculation more or less accurate (typically trading # off for speed or memory). # # In this script, we adjust some of the values slightly, just to show you how it works. # You could play around with these values and see what effect they have on the drawn images. # Usually, it requires a pretty drastic change in these parameters for you to be able to # notice the difference by eye. But subtle effects that may impact the shapes of galaxies # can happen well before then. # Type help(galsim.GSParams) for the complete list of parameters and more detailed # documentation, including the default values for each parameter. gsparams = galsim.GSParams( folding_threshold= 1.e-2, # maximum fractional flux that may be folded around edge of FFT maxk_threshold= 2.e-3, # k-values less than this may be excluded off edge of FFT xvalue_accuracy= 1.e-4, # approximations in real space aim to be this accurate kvalue_accuracy= 1.e-4, # approximations in fourier space aim to be this accurate shoot_accuracy= 1.e-4, # approximations in photon shooting aim to be this accurate minimum_fft_size=64) # minimum size of ffts logger.info('Starting demo script 7') # Make the PSF profiles: psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams) psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams) psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams) psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm, flux=0.2, gsparams=gsparams) psf3 = psf3_inner + psf3_outer atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams) # The OpticalPSF and set of Zernike values chosen below correspond to a reasonably well aligned, # smallish ~0.3m / 12 inch diameter telescope with a central obscuration of ~0.12m or 5 inches # diameter, being used in optical wavebands. # In the Noll convention, the value of the Zernike coefficient also gives the RMS optical path # difference across a circular pupil. An RMS difference of ~0.5 or larger indicates that parts # of the wavefront are in fully destructive interference, and so we might expect aberrations to # become strong when Zernike aberrations summed in quadrature approach 0.5 wave. # The aberrations chosen in this case correspond to operating close to a 0.25 wave RMS optical # path difference. Unlike in demo3, we specify the aberrations by making a list that we pass # in using the 'aberrations' kwarg. The order of aberrations starting from index 4 is defocus, # astig1, astig2, coma1, coma2, trefoil1, trefoil2, spher as in the Noll convention. # We ignore the first 4 values so that the index number corresponds to the Zernike index # in the Noll convention. This will be particularly convenient once we start allowing # coefficients beyond spherical (index 11). c.f. The Wikipedia page about the Noll indices: # # http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials aberrations = [0.0] * 12 # Set the initial size. aberrations[4] = 0.06 # Noll index 4 = Defocus aberrations[5:7] = [0.12, -0.08] # Noll index 5,6 = Astigmatism aberrations[7:9] = [0.07, 0.04] # Noll index 7,8 = Coma aberrations[11] = -0.13 # Noll index 11 = Spherical # You could also define these all at once if that is more convenient: #aberrations = [0.0, 0.0, 0.0, 0.0, 0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13] optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm, obscuration=0.4, aberrations=aberrations, gsparams=gsparams) psf4 = galsim.Convolve([atmos, optics ]) # Convolve inherits the gsparams from the first # item in the list. (Or you can supply a gsparams # argument explicitly if you want to override this.) atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams) optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams) psf5 = galsim.Convolve([atmos, optics]) psfs = [psf1, psf2, psf3, psf4, psf5] psf_names = [ "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF", "Kolmogorov * Airy" ] psf_times = [0, 0, 0, 0, 0] psf_fft_times = [0, 0, 0, 0, 0] psf_phot_times = [0, 0, 0, 0, 0] # Make the galaxy profiles: gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams) gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams) gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams) gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams) # A Sersic profile may be truncated if desired. # The units for this are expected to be arcsec (or specifically -- whatever units # you are using for all the size values as defined by the pixel_scale). bulge = galsim.Sersic(half_light_radius=0.7, n=3.2, trunc=8.5, gsparams=gsparams) disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams) gal5 = 0.4 * bulge + 0.6 * disk # Net half-light radius is only approximate for this one. gals = [gal1, gal2, gal3, gal4, gal5] gal_names = [ "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic", "Bulge + Disk" ] gal_times = [0, 0, 0, 0, 0] gal_fft_times = [0, 0, 0, 0, 0] gal_phot_times = [0, 0, 0, 0, 0] # Other times to keep track of: setup_times = 0 fft_times = 0 phot_times = 0 noise_times = 0 # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape. all_images = [] k = 0 for ipsf in range(len(psfs)): psf = psfs[ipsf] psf_name = psf_names[ipsf] logger.info('psf %d: %s', ipsf + 1, psf) # Note that this implicitly calls str(psf). We've made an effort to give all GalSim # objects an informative but relatively succinct str representation. Some details may # be missing, but it should look essentially like how you would create the object. logger.debug('repr = %r', psf) # The repr() version are a bit more pedantic in form and should be completely informative, # to the point where two objects that are not identical should never have equal repr # strings. As such the repr strings may in some cases be somewhat unwieldy. For instance, # since we set non-default gsparams in these, the repr includes that information, but # it is omitted from the str for brevity. for igal in range(len(gals)): gal = gals[igal] gal_name = gal_names[igal] logger.info(' galaxy %d: %s', igal + 1, gal) logger.debug(' repr = %r', gal) for i in range(4): logger.debug(' Start work on image %d', i) t1 = time.time() # Initialize the random number generator we will be using. rng = galsim.UniformDeviate(random_seed + k) # Generate random variates: flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min # Use a new variable name, since we'll want to keep the original unmodified. this_gal = gal.withFlux(flux) hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min this_gal = this_gal.dilate(hlr) beta_ellip = rng() * 2 * math.pi * galsim.radians ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min gal_shape = galsim.Shear(e=ellip, beta=beta_ellip) this_gal = this_gal.shear(gal_shape) # Build the final object by convolving the galaxy and PSF. final = galsim.Convolve([this_gal, psf]) # Create the large, double width output image # Rather than provide a scale= argument to the drawImage commands, we can also # set the pixel scale in the image constructor. # Note: You can also change it after the construction with im.scale=pixel_scale image = galsim.ImageF(2 * nx + 2, ny, scale=pixel_scale) # Assign the following two Image "views", fft_image and phot_image. # Using the syntax below, these are views into the larger image. # Changes/additions to the sub-images referenced by the views are automatically # reflected in the original image. fft_image = image[galsim.BoundsI(1, nx, 1, ny)] phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)] logger.debug( ' Read in training sample galaxy and PSF from file') t2 = time.time() # Draw the profile # This default rendering method (method='auto') usually defaults to FFT, since # that is normally the most efficient method. However, we can also set method # to 'fft' explicitly to force it to always use FFTs for the convolution # by the pixel response. (In this case, it doesn't have any effect, since # the 'auto' method would have always chosen 'fft' anyway, so this is just # for illustrative purposes.) final.drawImage(fft_image, method='fft') logger.debug( ' Drew fft image. Total drawn flux = %f. .flux = %f', fft_image.array.sum(), final.getFlux()) t3 = time.time() # Add Poisson noise sky_level_pixel = sky_level * pixel_scale**2 fft_image.addNoise( galsim.PoissonNoise(rng, sky_level=sky_level_pixel)) t4 = time.time() # The next two lines are just to get the output from this demo script # to match the output from the parsing of demo7.yaml. rng = galsim.UniformDeviate(random_seed + k) rng() rng() rng() rng() # Repeat for photon shooting image. # The max_extra_noise parameter indicates how much extra noise per pixel we are # willing to tolerate. The sky noise will be adding a variance of sky_level_pixel, # so we allow up to 1% of that extra. final.drawImage(phot_image, method='phot', max_extra_noise=sky_level_pixel / 100, rng=rng) t5 = time.time() # For photon shooting, galaxy already has Poisson noise, so we want to make # sure not to add that noise again! Thus, we just add sky noise, which # is Poisson with the mean = sky_level_pixel pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel) # DeviateNoise just adds the action of the given deviate to every pixel. phot_image.addNoise(galsim.DeviateNoise(pd)) # For PoissonDeviate, the mean is not zero, so for a background-subtracted # image, we need to subtract the mean back off when we are done. phot_image -= sky_level_pixel logger.debug( ' Added Poisson noise. Image fluxes are now %f and %f', fft_image.array.sum(), phot_image.array.sum()) t6 = time.time() # Store that into the list of all images all_images += [image] k = k + 1 logger.info( ' %d: flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)', k, flux, hlr, gal_shape.getE1(), gal_shape.getE2()) logger.debug(' Times: %f, %f, %f, %f, %f', t2 - t1, t3 - t2, t4 - t3, t5 - t4, t6 - t5) psf_times[ipsf] += t6 - t1 psf_fft_times[ipsf] += t3 - t2 psf_phot_times[ipsf] += t5 - t4 gal_times[igal] += t6 - t1 gal_fft_times[igal] += t3 - t2 gal_phot_times[igal] += t5 - t4 setup_times += t2 - t1 fft_times += t3 - t2 phot_times += t5 - t4 noise_times += t4 - t3 + t6 - t5 logger.info('Done making images of galaxies') logger.info('') logger.info('Some timing statistics:') logger.info(' Total time for setup steps = %f', setup_times) logger.info(' Total time for regular fft drawing = %f', fft_times) logger.info(' Total time for photon shooting = %f', phot_times) logger.info(' Total time for adding noise = %f', noise_times) logger.info('') logger.info('Breakdown by PSF type:') for ipsf in range(len(psfs)): logger.info(' %s: Total time = %f (fft: %f, phot: %f)', psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf], psf_phot_times[ipsf]) logger.info('') logger.info('Breakdown by Galaxy type:') for igal in range(len(gals)): logger.info(' %s: Total time = %f (fft: %f, phot: %f)', gal_names[igal], gal_times[igal], gal_fft_times[igal], gal_phot_times[igal]) logger.info('') # Now write the image to disk. # With any write command, you can optionally compress the file using several compression # schemes: # 'gzip' uses gzip on the full output file. # 'bzip2' uses bzip2 on the full output file. # 'rice' uses rice compression on the image, leaving the fits headers readable. # 'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable. # 'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it # doesn't work for writeCube. # 'plio' uses plio on the image, but it is only valid for positive integer data. # Furthermore, the first three have standard filename extensions associated with them, # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz', # the corresponding compression will be selected automatically. # In other words, the `compression='gzip'` specification is actually optional here: galsim.fits.writeCube(all_images, file_name, compression='gzip') logger.info('Wrote fft image to fits data cube %r', file_name)