def test_exponential_flux_scaling(): """Test flux scaling for Exponential. """ # decimal point to go to for parameter value comparisons param_decimal = 12 test_flux = 17.9 test_scale = 1.8 # init with scale and flux only (should be ok given last tests) obj = galsim.Exponential(scale_radius=test_scale, flux=test_flux) obj *= 2. np.testing.assert_almost_equal( obj.flux, test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __imul__.") obj = galsim.Exponential(scale_radius=test_scale, flux=test_flux) obj /= 2. np.testing.assert_almost_equal( obj.flux, test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __idiv__.") obj = galsim.Exponential(scale_radius=test_scale, flux=test_flux) 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.Exponential(scale_radius=test_scale, flux=test_flux) 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.Exponential(scale_radius=test_scale, flux=test_flux) 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_fwhm(): """Test the calculateFWHM method. """ # Compare the calculation for a simple Gaussian. g1 = galsim.Gaussian(sigma=5, flux=1.7) print('g1 native fwhm = ', g1.fwhm) print('g1.calculateFWHM = ', g1.calculateFWHM()) # These should be exactly equal. np.testing.assert_equal( g1.fwhm, g1.calculateFWHM(), err_msg="Gaussian.calculateFWHM() returned wrong value.") # Check for a convolution of two Gaussians. Should be equivalent, but now will need to # do the calculation. g2 = galsim.Convolve(galsim.Gaussian(sigma=3, flux=1.3), galsim.Gaussian(sigma=4, flux=23)) test_fwhm = g2.calculateFWHM() print('g2.calculateFWHM = ', test_fwhm) print('ratio - 1 = ', test_fwhm / g1.fwhm - 1) np.testing.assert_almost_equal( test_fwhm / g1.fwhm, 1.0, decimal=3, err_msg="Gaussian.calculateFWHM() is not accurate.") # The default scale already accurate to around 3 dp. Using scale = 0.1 is accurate to 8 dp. test_fwhm = g2.calculateFWHM(scale=0.1) print('g2.calculateFWHM(scale=0.1) = ', test_fwhm) print('ratio - 1 = ', test_fwhm / g1.fwhm - 1) np.testing.assert_almost_equal( test_fwhm / g1.fwhm, 1.0, decimal=8, err_msg="Gaussian.calculateFWHM(scale=0.1) is not accurate.") # Finally, we don't expect this to be accurate, but make sure the code can handle having # only the central pixel higher than half-maximum. test_fwhm = g2.calculateFWHM(scale=20) print('g2.calculateFWHM(scale=20) = ', test_fwhm) print('ratio - 1 = ', test_fwhm / g1.fwhm - 1) np.testing.assert_almost_equal( test_fwhm / g1.fwhm / 10, 0.1, decimal=1, err_msg="Gaussian.calculateFWHM(scale=20) is not accurate.") # Next, use an Exponential profile e1 = galsim.Exponential(scale_radius=5, flux=1.7) # The true fwhm for this is analytic, but not an attribute. e1_fwhm = 2. * np.log(2.0) * e1.scale_radius print('true e1 fwhm = ', e1_fwhm) # Test with the default scale and size. test_fwhm = e1.calculateFWHM() print('e1.calculateFWHM = ', test_fwhm) print('ratio - 1 = ', test_fwhm / e1_fwhm - 1) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=3, err_msg="Exponential.calculateFWHM() is not accurate.") # The default scale already accurate to around 3 dp. Using scale = 0.1 is accurate to 7 dp. # We can also decrease the size, which should still be accurate, but maybe a little faster. # Go a bit more that fwhm in units of the pixels. size = int(1.2 * e1_fwhm / 0.1) test_fwhm = e1.calculateFWHM(scale=0.1, size=size) print('e1.calculateFWHM(scale=0.1) = ', test_fwhm) print('ratio - 1 = ', test_fwhm / e1_fwhm - 1) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=7, err_msg="Exponential.calculateFWHM(scale=0.1) is not accurate.") # Check that it works if the centroid is not at the origin e3 = e1.shift(2, 3) test_fwhm = e3.calculateFWHM(scale=0.1) print('e3.calculateFWHM(scale=0.1) = ', test_fwhm) print('ratio - 1 = ', test_fwhm / e1_fwhm - 1) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=6, err_msg="shifted Exponential FWHM is not accurate.") # Can set a centroid manually. This should be equivalent to the default. print('e3.centroid = ', e3.centroid()) test_fwhm = e3.calculateFWHM(scale=0.1, centroid=e3.centroid()) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=6, err_msg="shifted FWHM with explicit centroid is not accurate.") # Check the image version. im = e1.drawImage(scale=0.1, method='sb') test_fwhm = im.calculateFWHM(Imax=e1.xValue(0, 0)) print('im.calculateFWHM() = ', test_fwhm) print('ratio - 1 = ', test_fwhm / e1_fwhm - 1) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=6, err_msg="image.calculateFWHM is not accurate.") # Check that a non-square image works correctly. Also, not centered anywhere in particular. bounds = galsim.BoundsI(-1234, -1234 + size * 2, 8234, 8234 + size) offset = galsim.PositionD(29, 1) im = e1.drawImage(scale=0.1, bounds=bounds, offset=offset, method='sb') test_fwhm = im.calculateFWHM(Imax=e1.xValue(0, 0), center=im.trueCenter() + offset) print('im.calculateFWHM() = ', test_fwhm) print('ratio - 1 = ', test_fwhm / e1_fwhm - 1) np.testing.assert_almost_equal( test_fwhm / e1_fwhm, 1.0, decimal=6, err_msg="non-square image.calculateFWHM is not accurate.")
def test_sigma(): """Test the calculateMomentRadius method. """ import time t1 = time.time() # Compare the calculation for a simple Gaussian. g1 = galsim.Gaussian(sigma=5, flux=1.7) print 'g1 native sigma = ',g1.sigma print 'g1.calculateMomentRadius = ',g1.calculateMomentRadius() # These should be exactly equal. np.testing.assert_equal( g1.sigma, g1.calculateMomentRadius(), err_msg="Gaussian.calculateMomentRadius() returned wrong value.") np.testing.assert_equal( g1.sigma, g1.calculateMomentRadius(rtype='trace'), err_msg="Gaussian.calculateMomentRadius(trace) returned wrong value.") np.testing.assert_equal( g1.sigma, g1.calculateMomentRadius(rtype='det'), err_msg="Gaussian.calculateMomentRadius(det) returned wrong value.") np.testing.assert_equal( (g1.sigma, g1.sigma), g1.calculateMomentRadius(rtype='both'), err_msg="Gaussian.calculateMomentRadius(both) returned wrong value.") # Check for a convolution of two Gaussians. Should be equivalent, but now will need to # do the calculation. g2 = galsim.Convolve(galsim.Gaussian(sigma=3, flux=1.3), galsim.Gaussian(sigma=4, flux=23)) test_sigma = g2.calculateMomentRadius() print 'g2.calculateMomentRadius = ',test_sigma print 'ratio - 1 = ',test_sigma/g1.sigma-1 np.testing.assert_almost_equal( test_sigma/g1.sigma, 1.0, decimal=1, err_msg="Gaussian.calculateMomentRadius() is not accurate.") # The default scale and size is only accurate to around 1 dp. Using scale = 0.1 is accurate # to 4 dp. test_sigma = g2.calculateMomentRadius(scale=0.1) print 'g2.calculateMomentRadius(scale=0.1) = ',test_sigma print 'ratio - 1 = ',test_sigma/g1.sigma-1 np.testing.assert_almost_equal( test_sigma/g1.sigma, 1.0, decimal=4, err_msg="Gaussian.calculateMomentRadius(scale=0.1) is not accurate.") # In this case, the different calculations are eqivalent: np.testing.assert_almost_equal( test_sigma, g2.calculateMomentRadius(scale=0.1, rtype='trace'), err_msg="Gaussian.calculateMomentRadius(trace) is not accurate.") np.testing.assert_almost_equal( test_sigma, g2.calculateMomentRadius(scale=0.1, rtype='det'), err_msg="Gaussian.calculateMomentRadius(trace) is not accurate.") np.testing.assert_almost_equal( (test_sigma, test_sigma), g2.calculateMomentRadius(scale=0.1, rtype='both'), err_msg="Gaussian.calculateMomentRadius(trace) is not accurate.") # However, when we shear it, the default (det) measure stays equal to the original sigma, but # the trace measure increases by a factor of (1-e^2)^0.25 g3 = g2.shear(e1=0.4, e2=0.3) esq = 0.4**2 + 0.3**2 sheared_sigma = g3.calculateMomentRadius(scale=0.1) print 'g3.calculateMomentRadius(scale=0.1) = ',sheared_sigma print 'ratio - 1 = ',sheared_sigma/g1.sigma-1 sheared_sigma2 = g3.calculateMomentRadius(scale=0.1, rtype='trace') print 'g3.calculateMomentRadius(scale=0.1,trace) = ',sheared_sigma2 print 'ratio = ',sheared_sigma2 / g1.sigma print '(1-e^2)^-0.25 = ',(1-esq)**-0.25 print 'ratio - 1 = ',sheared_sigma2/(g1.sigma*(1.-esq)**-0.25)-1 np.testing.assert_almost_equal( sheared_sigma/g1.sigma, 1.0, decimal=4, err_msg="sheared Gaussian.calculateMomentRadius(scale=0.1) is not accurate.") np.testing.assert_almost_equal( sheared_sigma2/(g1.sigma*(1.-esq)**-0.25), 1.0, decimal=4, err_msg="sheared Gaussian.calculateMomentRadius(scale=0.1,trace) is not accurate.") # Next, use an Exponential profile e1 = galsim.Exponential(scale_radius=5, flux=1.7) # The true "sigma" for this is analytic, but not an attribute. e1_sigma = np.sqrt(3.0) * e1.scale_radius print 'true e1 sigma = sqrt(3) * e1.scale_radius = ',e1_sigma # Test with the default scale and size. test_sigma = e1.calculateMomentRadius() print 'e1.calculateMomentRadius = ',test_sigma print 'ratio - 1 = ',test_sigma/e1_sigma-1 np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=1, err_msg="Exponential.calculateMomentRadius() is not accurate.") # The default scale and size is only accurate to around 1 dp. This time we have to both # decrease the scale and also increase the size to get 4 dp of precision. test_sigma = e1.calculateMomentRadius(scale=0.1, size=2000) print 'e1.calculateMomentRadius(scale=0.1) = ',test_sigma print 'ratio - 1 = ',test_sigma/e1_sigma-1 np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="Exponential.calculateMomentRadius(scale=0.1) is not accurate.") # Check that it works if the centroid is not at the origin e3 = e1.shift(2,3) test_sigma = e3.calculateMomentRadius(scale=0.1, size=2000) print 'e1.calculateMomentRadius(scale=0.1) = ',test_sigma print 'ratio - 1 = ',test_sigma/e1_sigma-1 np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="shifted Exponential MomentRadius is not accurate.") # Can set a centroid manually. This should be equivalent to the default. print 'e3.centroid = ',e3.centroid() test_sigma = e3.calculateMomentRadius(scale=0.1, size=2000, centroid=e3.centroid()) np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="shifted MomentRadius with explicit centroid is not accurate.") # Check the image version. size = 2000 im = e1.drawImage(scale=0.1, nx=size, ny=size) test_sigma = im.calculateMomentRadius(flux=e1.flux) print 'im.calculateMomentRadius() = ',test_sigma print 'ratio - 1 = ',test_sigma/e1_sigma-1 np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="image.calculateMomentRadius is not accurate.") # Check that a non-square image works correctly. Also, not centered anywhere in particular. bounds = galsim.BoundsI(-1234, -1234+size*2, 8234, 8234+size) offset = galsim.PositionD(29,1) im = e1.drawImage(scale=0.1, bounds=bounds, offset=offset) test_hlr = im.calculateMomentRadius(flux=e1.flux, center=im.trueCenter()+offset) print 'im.calculateMomentRadius() = ',test_sigma print 'ratio - 1 = ',test_sigma/e1_sigma-1 np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="non-square image.calculateMomentRadius is not accurate.") t2 = time.time() print 'time for %s = %.2f'%(funcname(),t2-t1)
def galSimFakeSersic(flux, gal, psfImage=None, scaleRad=False, returnObj=True, expAll=False, devAll=False, plotFake=False, trunc=0, drawMethod="auto", addPoisson=False, scale=1.0, transform=None, addShear=False): """ Make a fake single Sersic galaxy using the galSim.Sersic function Inputs: total flux of the galaxy, and a record array that stores the necessary parameters [reffPix, nSersic, axisRatio, posAng] Output: a 2-D image array of the galaxy model OR a GalSim object of the model Options: psfImage: PSF image for convolution trunc: Flux of Sersic models will truncate at trunc * reffPix radius; trunc=0 means no truncation drawMethod: The method for drawImage: ['auto', 'fft', 'real_space'] addPoisson: Add Poisson noise plotFake: Generate a PNG figure of the model expAll: Input model will be seen as nSersic=1 devAll: Input model will be seen as nSersic=4 returnObj: If TRUE, will return the GSObj, instead of the image array """ # Convert the numpy.float32 into normal float format nSersic = float(gal["sersic_n"]) reff = float(gal["reff"]) axisRatio = float(gal["b_a"]) posAng = float(gal["theta"]) # Truncate the flux at trunc x reff if trunc > 0: trunc = trunc * reff # Make sure Sersic index is not too large if nSersic > 6.0: raise ValueError("Sersic index is too large! Should be <= 6.0") # Check the axisRatio value if axisRatio <= 0.24: raise ValueError("Axis Ratio is too small! Should be >= 0.24") # Make the Sersic model based on flux, re, and Sersic index if nSersic == 1.0 or expAll: if scaleRad: serObj = galsim.Exponential(scale_radius=reff) else: serObj = galsim.Exponential(half_light_radius=reff) if expAll: print " * This model is treated as a n=1 Exponential disk : %d" % ( gal["ID"]) elif nSersic == 4.0 or devAll: serObj = galsim.DeVaucouleurs(half_light_radius=reff, trunc=trunc) if devAll: print " * This model is treated as a n=4 De Vaucouleurs model: %d" % ( gal["ID"]) elif nSersic <= 0.9: serObj = galsim.Sersic(nSersic, half_light_radius=reff) else: serObj = galsim.Sersic(nSersic, half_light_radius=reff, trunc=trunc) # If necessary, apply the Axis Ratio (q=b/a) using the Shear method if axisRatio < 1.0: serObj = serObj.shear(q=axisRatio, beta=0.0 * galsim.degrees) # If necessary, apply the Position Angle (theta) using the Rotate method #if posAng != 0.0 or posAng != 180.0: serObj = serObj.rotate((90.0 - posAng) * galsim.degrees) # If necessary, apply addtion shear (e.g. for weak lensing test) if addShear: try: g1 = float(gal['g1']) g2 = float(gal['g2']) serObj = serObj.shear(g1=g1, g2=g2) except ValueError: # TODO: Should check other options warnings.warn( "Can not find g1 or g2 in the input! No shear has been added!") #do the transformation from sky to pixel coordinates, if given if transform is not None: serObj = serObj.transform(*tuple(transform.ravel())) # Convolve the Sersic model using the provided PSF image if psfImage is not None: # Convert the PSF Image Array into a GalSim Object # Norm=True by default psfObj = arrayToGSObj(psfImage, norm=True) serFinal = galsim.Convolve([serObj, psfObj]) else: serFinal = serObj # Pass the flux to the object serFinal = serFinal.withFlux(float(flux)) # Make a PNG figure of the fake galaxy to check if everything is Ok # TODO: For test, should be removed later if plotFake: plotFakeGalaxy(serFinal, galID=gal['ID']) # Now, by default, the function will just return the GSObj if returnObj: return serFinal else: return galSimDrawImage(serFinal, method=drawMethod, scale=scale, addPoisson=addPoisson)
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 + 1) # 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 + 1) 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)
def _run_ml(*, n_sims, rng, dudx, dudy, dvdx, dvdy): """Run metacal on an image composed of stamps w/ constant noise. Parameters ---------- n_sims : int The number of objects to run. rng : np.random.RandomState An RNG to use. dudx : float The du/dx Jacobian component. dudy : float The du/dy Jacobian component. dydx : float The dv/dx Jacobian component. dvdy : float The dv/dy Jacobian component. Returns ------- result : dict A dictionary with each of the metacal catalogs. """ method = 'no_pixel' stamp_size = 33 psf_stamp_size = 33 cen = (stamp_size - 1) / 2 psf_cen = (psf_stamp_size - 1) / 2 s2n = 1e16 flux = 1e6 galsim_jac = galsim.JacobianWCS(dudx=dudx, dudy=dudy, dvdx=dvdx, dvdy=dvdy) gal = galsim.Exponential(half_light_radius=0.5).withFlux(flux) psf = galsim.Gaussian(fwhm=0.9).withFlux(1) obj = galsim.Convolve(gal, psf) obj_im = obj.drawImage(nx=111, ny=111).array noise = np.sqrt(np.sum(obj_im**2)) / s2n data = [] for ind in tqdm.trange(n_sims): ################################ # make the obs # psf psf_im = psf.drawImage(nx=psf_stamp_size, ny=psf_stamp_size, wcs=galsim_jac, method=method).array psf_noise = np.sqrt(np.sum(psf_im**2)) / 10000 wgt = np.ones_like(psf_im) / psf_noise**2 psf_im += (rng.normal(size=psf_im.shape) * psf_noise) psf_jac = ngmix.Jacobian(x=psf_cen, y=psf_cen, dudx=dudx, dudy=dudy, dvdx=dvdx, dvdy=dvdy) psf_obs = ngmix.Observation(image=psf_im, weight=wgt, jacobian=psf_jac) # now render object scale = psf_jac.scale shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2) _obj = obj.shift(dx=shift[0], dy=shift[1]) xy = galsim_jac.toImage(galsim.PositionD(shift)) im = _obj.drawImage(nx=stamp_size, ny=stamp_size, wcs=galsim_jac, method=method).array jac = ngmix.Jacobian(x=cen + xy.x, y=cen + xy.y, dudx=dudx, dudy=dudy, dvdx=dvdx, dvdy=dvdy) wgt = np.ones_like(im) / noise**2 nse = rng.normal(size=im.shape) * noise im += (rng.normal(size=im.shape) * noise) obs = ngmix.Observation(image=im, weight=wgt, noise=nse, bmask=np.zeros_like(im, dtype=np.int32), ormask=np.zeros_like(im, dtype=np.int32), jacobian=jac, psf=psf_obs) # build the mbobs mbobs = ngmix.MultiBandObsList() obslist = ngmix.ObsList() obslist.append(obs) mbobs.append(obslist) mbobs.meta['id'] = ind + 1 # these settings do not matter that much I think mbobs[0].meta['Tsky'] = 1 mbobs[0].meta['magzp_ref'] = 26.5 mbobs[0][0].meta['orig_col'] = ind + 1 mbobs[0][0].meta['orig_row'] = ind + 1 ################################ # run the fitters try: res = _run_ml_fitter(mbobs, rng) except Exception as e: print('err:', e, type(e)) res = None if res is not None: data.append(res) if len(data) > 0: res = data else: res = None return res
def test_operations_simple(): """Simple test of operations on InterpolatedImage: shear, magnification, rotation, shifting.""" import time t1 = time.time() # Make some nontrivial image that can be described in terms of sums and convolutions of # GSObjects. We want this to be somewhat hard to describe, but should be at least # critically-sampled, so put in an Airy PSF. gal_flux = 1000. pix_scale = 0.03 # arcsec bulge_frac = 0.3 bulge_hlr = 0.3 # arcsec bulge_e = 0.15 bulge_pos_angle = 30. * galsim.degrees disk_hlr = 0.6 # arcsec disk_e = 0.5 disk_pos_angle = 60. * galsim.degrees lam = 800 # nm NB: don't use lambda - that's a reserved word. tel_diam = 2.4 # meters lam_over_diam = lam * 1.e-9 / tel_diam # radians lam_over_diam *= 206265 # arcsec im_size = 512 bulge = galsim.Sersic(4, half_light_radius=bulge_hlr) bulge.applyShear(e=bulge_e, beta=bulge_pos_angle) disk = galsim.Exponential(half_light_radius=disk_hlr) disk.applyShear(e=disk_e, beta=disk_pos_angle) gal = bulge_frac * bulge + (1. - bulge_frac) * disk gal.setFlux(gal_flux) psf = galsim.Airy(lam_over_diam) pix = galsim.Pixel(pix_scale) obj = galsim.Convolve(gal, psf, pix) im = obj.draw(dx=pix_scale) # Turn it into an InterpolatedImage with default param settings int_im = galsim.InterpolatedImage(im) # Shear it, and compare with expectations from GSObjects directly test_g1 = -0.07 test_g2 = 0.1 test_decimal = 2 # in % difference, i.e. 2 means 1% agreement comp_region = 30 # compare the central region of this linear size test_int_im = int_im.createSheared(g1=test_g1, g2=test_g2) ref_obj = obj.createSheared(g1=test_g1, g2=test_g2) # make large images im = galsim.ImageD(im_size, im_size) ref_im = galsim.ImageD(im_size, im_size) test_int_im.draw(image=im, dx=pix_scale) ref_obj.draw(image=ref_im, dx=pix_scale) # define subregion for comparison new_bounds = galsim.BoundsI(1, comp_region, 1, comp_region) new_bounds.shift((im_size - comp_region) / 2, (im_size - comp_region) / 2) im_sub = im.subImage(new_bounds) ref_im_sub = ref_im.subImage(new_bounds) diff_im = im_sub - ref_im_sub rel = diff_im / im_sub zeros_arr = np.zeros((comp_region, comp_region)) # require relative difference to be smaller than some amount np.testing.assert_array_almost_equal( rel.array, zeros_arr, test_decimal, err_msg='Sheared InterpolatedImage disagrees with reference') # Magnify it, and compare with expectations from GSObjects directly test_mag = 1.08 test_decimal = 2 # in % difference, i.e. 2 means 1% agreement comp_region = 30 # compare the central region of this linear size test_int_im = int_im.createMagnified(test_mag) ref_obj = obj.createMagnified(test_mag) # make large images im = galsim.ImageD(im_size, im_size) ref_im = galsim.ImageD(im_size, im_size) test_int_im.draw(image=im, dx=pix_scale) ref_obj.draw(image=ref_im, dx=pix_scale) # define subregion for comparison new_bounds = galsim.BoundsI(1, comp_region, 1, comp_region) new_bounds.shift((im_size - comp_region) / 2, (im_size - comp_region) / 2) im_sub = im.subImage(new_bounds) ref_im_sub = ref_im.subImage(new_bounds) diff_im = im_sub - ref_im_sub rel = diff_im / im_sub zeros_arr = np.zeros((comp_region, comp_region)) # require relative difference to be smaller than some amount np.testing.assert_array_almost_equal( rel.array, zeros_arr, test_decimal, err_msg='Magnified InterpolatedImage disagrees with reference') # Lens it (shear and magnify), and compare with expectations from GSObjects directly test_g1 = -0.03 test_g2 = -0.04 test_mag = 0.74 test_decimal = 2 # in % difference, i.e. 2 means 1% agreement comp_region = 30 # compare the central region of this linear size test_int_im = int_im.createLensed(test_g1, test_g2, test_mag) ref_obj = obj.createLensed(test_g1, test_g2, test_mag) # make large images im = galsim.ImageD(im_size, im_size) ref_im = galsim.ImageD(im_size, im_size) test_int_im.draw(image=im, dx=pix_scale) ref_obj.draw(image=ref_im, dx=pix_scale) # define subregion for comparison new_bounds = galsim.BoundsI(1, comp_region, 1, comp_region) new_bounds.shift((im_size - comp_region) / 2, (im_size - comp_region) / 2) im_sub = im.subImage(new_bounds) ref_im_sub = ref_im.subImage(new_bounds) diff_im = im_sub - ref_im_sub rel = diff_im / im_sub zeros_arr = np.zeros((comp_region, comp_region)) # require relative difference to be smaller than some amount np.testing.assert_array_almost_equal( rel.array, zeros_arr, test_decimal, err_msg='Lensed InterpolatedImage disagrees with reference') # Rotate it, and compare with expectations from GSObjects directly test_rot_angle = 32. * galsim.degrees test_decimal = 2 # in % difference, i.e. 2 means 1% agreement comp_region = 30 # compare the central region of this linear size test_int_im = int_im.createRotated(test_rot_angle) ref_obj = obj.createRotated(test_rot_angle) # make large images im = galsim.ImageD(im_size, im_size) ref_im = galsim.ImageD(im_size, im_size) test_int_im.draw(image=im, dx=pix_scale) ref_obj.draw(image=ref_im, dx=pix_scale) # define subregion for comparison new_bounds = galsim.BoundsI(1, comp_region, 1, comp_region) new_bounds.shift((im_size - comp_region) / 2, (im_size - comp_region) / 2) im_sub = im.subImage(new_bounds) ref_im_sub = ref_im.subImage(new_bounds) diff_im = im_sub - ref_im_sub rel = diff_im / im_sub zeros_arr = np.zeros((comp_region, comp_region)) # require relative difference to be smaller than some amount np.testing.assert_array_almost_equal( rel.array, zeros_arr, test_decimal, err_msg='Rotated InterpolatedImage disagrees with reference') # Shift it, and compare with expectations from GSObjects directly x_shift = -0.31 y_shift = 0.87 test_decimal = 2 # in % difference, i.e. 2 means 1% agreement comp_region = 30 # compare the central region of this linear size test_int_im = int_im.createShifted(x_shift, y_shift) ref_obj = obj.createShifted(x_shift, y_shift) # make large images im = galsim.ImageD(im_size, im_size) ref_im = galsim.ImageD(im_size, im_size) test_int_im.draw(image=im, dx=pix_scale) ref_obj.draw(image=ref_im, dx=pix_scale) # define subregion for comparison new_bounds = galsim.BoundsI(1, comp_region, 1, comp_region) new_bounds.shift((im_size - comp_region) / 2, (im_size - comp_region) / 2) im_sub = im.subImage(new_bounds) ref_im_sub = ref_im.subImage(new_bounds) diff_im = im_sub - ref_im_sub rel = diff_im / im_sub zeros_arr = np.zeros((comp_region, comp_region)) # require relative difference to be smaller than some amount np.testing.assert_array_almost_equal( rel.array, zeros_arr, test_decimal, err_msg='Shifted InterpolatedImage disagrees with reference') t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_drawK_Exponential_Moffat(): """Test the drawK function using known symmetries of the Exponential Hankel transform (which is a Moffat with beta=1.5). See http://mathworld.wolfram.com/HankelTransform.html. """ import time t1 = time.time() test_flux = 4.1 # Choose a non-unity flux test_scale_radius = 13. # ...likewise for scale_radius test_imsize = 45 # Dimensions of comparison image, doesn't need to be large # Define an Exponential GSObject gal = galsim.Exponential(scale_radius=test_scale_radius, flux=test_flux) # Then define a related object which is in fact the opposite number in the Hankel transform pair # For the Exponential we need a Moffat, with scale_radius=1/scale_radius. The total flux under # this Moffat with unit amplitude at r=0 is is pi * scale_radius**(-2) / (beta - 1) # = 2. * pi * scale_radius**(-2) in this case, so it works analagously to the Gaussian above. gal_hankel = galsim.Moffat(beta=1.5, scale_radius=1. / test_scale_radius, flux=test_flux * 2. * np.pi / test_scale_radius**2) # Do a basic flux test: the total flux of the gal should equal gal_Hankel(k=(0, 0)) np.testing.assert_almost_equal( gal.getFlux(), gal_hankel.xValue(galsim.PositionD(0., 0.)), decimal=12, err_msg= "Test object flux does not equal k=(0, 0) mode of its Hankel transform conjugate." ) image_test = galsim.ImageD(test_imsize, test_imsize) rekimage_test = galsim.ImageD(test_imsize, test_imsize) imkimage_test = galsim.ImageD(test_imsize, test_imsize) # Then compare these two objects at a couple of different scale (reasonably matched for size) for scale_test in (0.15 / test_scale_radius, 0.6 / test_scale_radius): gal.drawK(re=rekimage_test, im=imkimage_test, scale=scale_test) gal_hankel.draw(image_test, scale=scale_test, use_true_center=False, normalization="sb") np.testing.assert_array_almost_equal( rekimage_test.array, image_test.array, decimal=12, err_msg= "Test object drawK() and draw() from Hankel conjugate do not match for grid " + "spacing scale = " + str(scale_test)) np.testing.assert_array_almost_equal( imkimage_test.array, np.zeros_like(imkimage_test.array), decimal=12, err_msg= "Non-zero imaginary part for drawK from test object that is purely centred on " + "the origin.") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_offset(): """Test the offset parameter to the draw and drawShoot function. """ import time t1 = time.time() scale = 0.23 # Use some more exact GSParams. We'll be comparing FFT images to real-space convolved values, # so we don't want to suffer from our overall accuracy being only about 10^-3. # Update: It turns out the only one I needed to reduce to obtain the accuracy I wanted # below is maxk_threshold. Perhaps this is a sign that we ought to lower it in general? params = galsim.GSParams(maxk_threshold=1.e-4) # We use a simple Exponential for our object: gal = galsim.Exponential(flux=test_flux, scale_radius=0.5, gsparams=params) pix = galsim.Pixel(scale, gsparams=params) obj = galsim.Convolve([gal, pix], gsparams=params) # The shapes of the images we will build # Make sure all combinations of odd/even are represented. shape_list = [(256, 256), (256, 243), (249, 260), (255, 241), (270, 260)] # Some reasonable (x,y) values at which to test the xValues (near the center) xy_list = [(128, 128), (123, 131), (126, 124)] # The offsets to test offset_list = [(1, -3), (0.3, -0.1), (-2.3, -1.2)] # Make the images somewhat large so the moments are measured accurately. for nx, ny in shape_list: #print '\n\n\nnx,ny = ',nx,ny # First check that the image agrees with our calculation of the center cenx = (nx + 1.) / 2. ceny = (ny + 1.) / 2. #print 'cen = ',cenx,ceny im = galsim.ImageD(nx, ny, scale=scale) true_center = im.bounds.trueCenter() np.testing.assert_almost_equal( cenx, true_center.x, 6, "im.bounds.trueCenter().x is wrong for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( ceny, true_center.y, 6, "im.bounds.trueCenter().y is wrong for (nx,ny) = %d,%d" % (nx, ny)) # Check that the default draw command puts the centroid in the center of the image. obj.draw(im, normalization='sb') moments = getmoments(im) #print 'moments = ',moments np.testing.assert_almost_equal( moments[0], cenx, 5, "obj.draw(im) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( moments[1], ceny, 5, "obj.draw(im) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) # Test that a few pixel values match xValue. # Note: we don't expect the FFT drawn image to match the xValues precisely, since the # latter use real-space convolution, so they should just match to our overall accuracy # requirement, which is something like 1.e-3 or so. But an image of just the galaxy # should use real-space drawing, so should be pretty much exact. im2 = galsim.ImageD(nx, ny, scale=scale) gal.draw(im2, normalization='sb') for x, y in xy_list: #print 'x,y = ',x,y #print 'im(x,y) = ',im(x,y) u = (x - cenx) * scale v = (y - ceny) * scale #print 'xval(x-cenx,y-ceny) = ',obj.xValue(galsim.PositionD(u,v)) np.testing.assert_almost_equal( im(x, y), obj.xValue(galsim.PositionD(u, v)), 2, "im(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) np.testing.assert_almost_equal( im2(x, y), gal.xValue(galsim.PositionD(u, v)), 6, "im2(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) # Check that offset moves the centroid by the right amount. for offx, offy in offset_list: # For integer offsets, we expect the centroids to come out pretty much exact. # (Only edge effects of the image should produce any error, and those are very small.) # However, for non-integer effects, we don't actually expect the centroids to be # right, even with perfect image rendering. To see why, imagine using a delta function # for the galaxy. The centroid changes discretely, not continuously as the offset # varies. The effect isn't as severe of course for our Exponential, but the effect # is still there in part. Hence, only use 2 decimal places for non-integer offsets. if offx == int(offx) and offy == int(offy): decimal = 4 else: decimal = 2 #print 'offx,offy = ',offx,offy offset = galsim.PositionD(offx, offy) obj.draw(im, normalization='sb', offset=offset) moments = getmoments(im) #print 'moments = ',moments np.testing.assert_almost_equal( moments[0], cenx + offx, decimal, "obj.draw(im,offset) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( moments[1], ceny + offy, decimal, "obj.draw(im,offset) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) # Test that a few pixel values match xValue gal.draw(im2, normalization='sb', offset=offset) for x, y in xy_list: #print 'x,y = ',x,y #print 'im(x,y) = ',im(x,y) u = (x - cenx - offx) * scale v = (y - ceny - offy) * scale #print 'xval(x-cenx-offx,y-ceny-offy) = ',obj.xValue(galsim.PositionD(u,v)) np.testing.assert_almost_equal( im(x, y), obj.xValue(galsim.PositionD(u, v)), 2, "im(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) np.testing.assert_almost_equal( im2(x, y), gal.xValue(galsim.PositionD(u, v)), 6, "im2(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) # Check that applyShift also moves the centroid by the right amount. shifted_obj = obj.createShifted(offset * scale) shifted_obj.draw(im, normalization='sb') moments = getmoments(im) #print 'moments = ',moments np.testing.assert_almost_equal( moments[0], cenx + offx, decimal, "shifted_obj.draw(im) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( moments[1], ceny + offy, decimal, "shifted_obj.draw(im) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) # Test that a few pixel values match xValue shifted_gal = gal.createShifted(offset * scale) shifted_gal.draw(im2, normalization='sb') for x, y in xy_list: #print 'x,y = ',x,y #print 'im(x,y) = ',im(x,y) u = (x - cenx) * scale v = (y - ceny) * scale #print 'shifted xval(x-cenx,y-ceny) = ',shifted_obj.xValue(galsim.PositionD(u,v)) np.testing.assert_almost_equal( im(x, y), shifted_obj.xValue(galsim.PositionD(u, v)), 2, "im(%d,%d) does not match shifted xValue(%f,%f)" % (x, y, x - cenx, y - ceny)) np.testing.assert_almost_equal( im2(x, y), shifted_gal.xValue(galsim.PositionD(u, v)), 6, "im2(%d,%d) does not match shifted xValue(%f,%f)" % (x, y, x - cenx, y - ceny)) u = (x - cenx - offx) * scale v = (y - ceny - offy) * scale #print 'xval(x-cenx-offx,y-ceny-offy) = ',obj.xValue(galsim.PositionD(u,v)) np.testing.assert_almost_equal( im(x, y), obj.xValue(galsim.PositionD(u, v)), 2, "im(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) np.testing.assert_almost_equal( im2(x, y), gal.xValue(galsim.PositionD(u, v)), 6, "im2(%d,%d) does not match xValue(%f,%f)" % (x, y, u, v)) # Chcek the image's definition of the nominal center nom_cenx = (nx + 2) / 2 nom_ceny = (ny + 2) / 2 nominal_center = im.bounds.center() np.testing.assert_almost_equal( nom_cenx, nominal_center.x, 6, "im.bounds.center().x is wrong for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( nom_ceny, nominal_center.y, 6, "im.bounds.center().y is wrong for (nx,ny) = %d,%d" % (nx, ny)) # Check that use_true_center = false is consistent with an offset by 0 or 0.5 pixels. obj.draw(im, normalization='sb', use_true_center=False) moments = getmoments(im) #print 'moments = ',moments np.testing.assert_almost_equal( moments[0], nom_cenx, 4, "obj.draw(im, use_true_center=False) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) np.testing.assert_almost_equal( moments[1], nom_ceny, 4, "obj.draw(im, use_true_center=False) not centered correctly for (nx,ny) = %d,%d" % (nx, ny)) cen_offset = galsim.PositionD(nom_cenx - cenx, nom_ceny - ceny) #print 'cen_offset = ',cen_offset obj.draw(im2, normalization='sb', offset=cen_offset) np.testing.assert_array_almost_equal( im.array, im2.array, 6, "obj.draw(im, offset=%f,%f) different from use_true_center=False") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_draw_methods(): """Test the the different method options do the right thing. """ import time t1 = time.time() # We use a simple Exponential for our object: obj = galsim.Exponential(flux=test_flux, scale_radius=1.09) test_scale = 0.28 pix = galsim.Pixel(scale=test_scale) obj_pix = galsim.Convolve(obj, pix) N = 64 im1 = galsim.ImageD(N, N, scale=test_scale) # auto and fft should be equivalent to drawing obj_pix with no_pixel im1 = obj.drawImage(image=im1) im2 = obj_pix.drawImage(image=im1.copy(), method='no_pixel') print 'im1 flux diff = ', abs(im1.array.sum() - test_flux) np.testing.assert_almost_equal( im1.array.sum(), test_flux, 2, "obj.drawImage() produced image with wrong flux") print 'im2 flux diff = ', abs(im2.array.sum() - test_flux) np.testing.assert_almost_equal( im2.array.sum(), test_flux, 2, "obj_pix.drawImage(no_pixel) produced image with wrong flux") print 'im1, im2 max diff = ', abs(im1.array - im2.array).max() np.testing.assert_array_almost_equal( im1.array, im2.array, 6, "obj.drawImage() differs from obj_pix.drawImage(no_pixel)") im3 = obj.drawImage(image=im1.copy(), method='fft') print 'im1, im3 max diff = ', abs(im1.array - im3.array).max() np.testing.assert_array_almost_equal( im1.array, im3.array, 6, "obj.drawImage(fft) differs from obj.drawImage") # real_space should be similar, but not precisely equal. im4 = obj.drawImage(image=im1.copy(), method='real_space') print 'im1, im4 max diff = ', abs(im1.array - im4.array).max() np.testing.assert_array_almost_equal( im1.array, im4.array, 4, "obj.drawImage(real_space) differs from obj.drawImage") # sb should match xValue for pixel centers. And be scale**2 factor different from no_pixel. im5 = obj.drawImage(image=im1.copy(), method='sb', use_true_center=False) im5.setCenter(0, 0) print 'im5(0,0) = ', im5(0, 0) print 'obj.xValue(0,0) = ', obj.xValue(0., 0.) np.testing.assert_almost_equal( im5(0, 0), obj.xValue(0., 0.), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) np.testing.assert_almost_equal( im5(3, 2), obj.xValue(3 * test_scale, 2 * test_scale), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) im5 = obj.drawImage(image=im5, method='sb') print 'im5(0,0) = ', im5(0, 0) print 'obj.xValue(dx/2,dx/2) = ', obj.xValue(test_scale / 2., test_scale / 2.) np.testing.assert_almost_equal( im5(0, 0), obj.xValue(0.5 * test_scale, 0.5 * test_scale), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) np.testing.assert_almost_equal( im5(3, 2), obj.xValue(3.5 * test_scale, 2.5 * test_scale), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) im6 = obj.drawImage(image=im1.copy(), method='no_pixel') print 'im6, im5*scale**2 max diff = ', abs(im6.array - im5.array * test_scale**2).max() np.testing.assert_array_almost_equal( im5.array * test_scale**2, im6.array, 6, "obj.drawImage(sb) * scale**2 differs from obj.drawImage(no_pixel)") # Drawing a truncated object, auto should be identical to real_space obj = galsim.Sersic(flux=test_flux, n=3.7, half_light_radius=2, trunc=4) obj_pix = galsim.Convolve(obj, pix) # auto and real_space should be equivalent to drawing obj_pix with no_pixel im1 = obj.drawImage(image=im1) im2 = obj_pix.drawImage(image=im1.copy(), method='no_pixel') print 'im1 flux diff = ', abs(im1.array.sum() - test_flux) np.testing.assert_almost_equal( im1.array.sum(), test_flux, 2, "obj.drawImage() produced image with wrong flux") print 'im2 flux diff = ', abs(im2.array.sum() - test_flux) np.testing.assert_almost_equal( im2.array.sum(), test_flux, 2, "obj_pix.drawImage(no_pixel) produced image with wrong flux") print 'im1, im2 max diff = ', abs(im1.array - im2.array).max() np.testing.assert_array_almost_equal( im1.array, im2.array, 6, "obj.drawImage() differs from obj_pix.drawImage(no_pixel)") im4 = obj.drawImage(image=im1.copy(), method='real_space') print 'im1, im4 max diff = ', abs(im1.array - im4.array).max() np.testing.assert_array_almost_equal( im1.array, im4.array, 6, "obj.drawImage(real_space) differs from obj.drawImage") # fft should be similar, but not precisely equal. import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") # This emits a warning about convolving two things with hard edges. im3 = obj.drawImage(image=im1.copy(), method='fft') print 'im1, im3 max diff = ', abs(im1.array - im3.array).max() np.testing.assert_array_almost_equal( im1.array, im3.array, 3, # Should be close, but not exact. "obj.drawImage(fft) differs from obj.drawImage") # sb should match xValue for pixel centers. And be scale**2 factor different from no_pixel. im5 = obj.drawImage(image=im1.copy(), method='sb') im5.setCenter(0, 0) print 'im5(0,0) = ', im5(0, 0) print 'obj.xValue(dx/2,dx/2) = ', obj.xValue(test_scale / 2., test_scale / 2.) np.testing.assert_almost_equal( im5(0, 0), obj.xValue(0.5 * test_scale, 0.5 * test_scale), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) np.testing.assert_almost_equal( im5(3, 2), obj.xValue(3.5 * test_scale, 2.5 * test_scale), 6, "obj.drawImage(sb) values do not match surface brightness given by xValue" ) im6 = obj.drawImage(image=im1.copy(), method='no_pixel') print 'im6, im5*scale**2 max diff = ', abs(im6.array - im5.array * test_scale**2).max() np.testing.assert_array_almost_equal( im5.array * test_scale**2, im6.array, 6, "obj.drawImage(sb) * scale**2 differs from obj.drawImage(no_pixel)") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_draw(): """Test the various optional parameters to the draw function. In particular test the parameters image, dx, and wmult in various combinations. """ import time t1 = time.time() # We use a simple Exponential for our object: obj = galsim.Exponential(flux=test_flux, scale_radius=2) # First test draw() with no kwargs. It should: # - create a new image # - return the new image # - set the scale to obj.nyquistDx() # - set the size large enough to contain 99.5% of the flux im1 = obj.draw() dx_nyq = obj.nyquistDx() np.testing.assert_almost_equal( im1.scale, dx_nyq, 9, "obj.draw() produced image with wrong scale") #print 'im1.bounds = ',im1.bounds assert im1.bounds == galsim.BoundsI( 1, 56, 1, 56), ("obj.draw() produced image with wrong bounds") np.testing.assert_almost_equal(CalculateScale(im1), 2, 1, "Measured wrong scale after obj.draw()") # The flux is only really expected to come out right if the object has been # convoled with a pixel: obj2 = galsim.Convolve([obj, galsim.Pixel(im1.scale)]) im2 = obj2.draw() dx_nyq = obj2.nyquistDx() np.testing.assert_almost_equal( im2.scale, dx_nyq, 9, "obj2.draw() produced image with wrong scale") np.testing.assert_almost_equal( im2.array.astype(float).sum(), test_flux, 2, "obj2.draw() produced image with wrong flux") assert im2.bounds == galsim.BoundsI( 1, 56, 1, 56), ("obj2.draw() produced image with wrong bounds") np.testing.assert_almost_equal(CalculateScale(im2), 2, 1, "Measured wrong scale after obj2.draw()") # Test if we provide an image argument. It should: # - write to the existing image # - also return that image # - set the scale to obj2.nyquistDx() # - zero out any existing data im3 = galsim.ImageD(56, 56) im4 = obj2.draw(im3) np.testing.assert_almost_equal( im3.scale, dx_nyq, 9, "obj2.draw(im3) produced image with wrong scale") np.testing.assert_almost_equal( im3.array.sum(), test_flux, 2, "obj2.draw(im3) produced image with wrong flux") np.testing.assert_almost_equal( im3.array.sum(), im2.array.astype(float).sum(), 6, "obj2.draw(im3) produced image with different flux than im2") np.testing.assert_almost_equal( CalculateScale(im3), 2, 1, "Measured wrong scale after obj2.draw(im3)") np.testing.assert_array_equal(im3.array, im4.array, "im4 = obj2.draw(im3) produced im4 != im3") im3.fill(9.8) np.testing.assert_array_equal( im3.array, im4.array, "im4 = obj2.draw(im3) produced im4 is not im3") im4 = obj2.draw(im3) np.testing.assert_almost_equal( im3.array.sum(), im2.array.astype(float).sum(), 6, "obj2.draw(im3) doesn't zero out existing data") # Test if we provide an image with undefined bounds. It should: # - resize the provided image # - also return that image # - set the scale to obj2.nyquistDx() im5 = galsim.ImageD() obj2.draw(im5) np.testing.assert_almost_equal( im5.scale, dx_nyq, 9, "obj2.draw(im5) produced image with wrong scale") np.testing.assert_almost_equal( im5.array.sum(), test_flux, 2, "obj2.draw(im5) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im5), 2, 1, "Measured wrong scale after obj2.draw(im5)") np.testing.assert_almost_equal( im5.array.sum(), im2.array.astype(float).sum(), 6, "obj2.draw(im5) produced image with different flux than im2") assert im5.bounds == galsim.BoundsI( 1, 56, 1, 56), ("obj2.draw(im5) produced image with wrong bounds") # Test if we provide wmult. It should: # - create a new image that is wmult times larger in each direction. # - return the new image # - set the scale to obj2.nyquistDx() im6 = obj2.draw(wmult=4.) np.testing.assert_almost_equal( im6.scale, dx_nyq, 9, "obj2.draw(wmult) produced image with wrong scale") # Can assert accuracy to 4 decimal places now, since we're capturing much more # of the flux on the image. np.testing.assert_almost_equal( im6.array.astype(float).sum(), test_flux, 4, "obj2.draw(wmult) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im6), 2, 2, "Measured wrong scale after obj2.draw(wmult)") #print 'im6.bounds = ',im6.bounds assert im6.bounds == galsim.BoundsI( 1, 220, 1, 220), ("obj2.draw(wmult) produced image with wrong bounds") # Test if we provide an image argument and wmult. It should: # - write to the existing image # - also return that image # - set the scale to obj2.nyquistDx() # - zero out any existing data # - the calculation of the convolution should be slightly more accurate than for im3 im3.setZero() im5.setZero() obj2.draw(im3, wmult=4.) obj2.draw(im5) np.testing.assert_almost_equal( im3.scale, dx_nyq, 9, "obj2.draw(im3) produced image with wrong scale") np.testing.assert_almost_equal( im3.array.sum(), test_flux, 2, "obj2.draw(im3,wmult) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im3), 2, 1, "Measured wrong scale after obj2.draw(im3,wmult)") assert ((im3.array - im5.array)**2).sum() > 0, ( "obj2.draw(im3,wmult) produced the same image as without wmult") # Test if we provide a dx to use. It should: # - create a new image using that dx for the scale # - return the new image # - set the size large enough to contain 99.5% of the flux im7 = obj2.draw(scale=0.51) np.testing.assert_almost_equal( im7.scale, 0.51, 9, "obj2.draw(dx) produced image with wrong scale") np.testing.assert_almost_equal( im7.array.astype(float).sum(), test_flux, 2, "obj2.draw(dx) produced image with wrong flux") np.testing.assert_almost_equal(CalculateScale(im7), 2, 1, "Measured wrong scale after obj2.draw(dx)") #print 'im7.bounds = ',im7.bounds assert im7.bounds == galsim.BoundsI( 1, 68, 1, 68), ("obj2.draw(dx) produced image with wrong bounds") # Test with dx and wmult. It should: # - create a new image using that dx for the scale # - set the size a factor of wmult times larger in each direction. # - return the new image im8 = obj2.draw(scale=0.51, wmult=4.) np.testing.assert_almost_equal( im8.scale, 0.51, 9, "obj2.draw(dx,wmult) produced image with wrong scale") np.testing.assert_almost_equal( im8.array.astype(float).sum(), test_flux, 4, "obj2.draw(dx,wmult) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im8), 2, 2, "Measured wrong scale after obj2.draw(dx,wmult)") #print 'im8.bounds = ',im8.bounds assert im8.bounds == galsim.BoundsI( 1, 270, 1, 270), ("obj2.draw(dx,wmult) produced image with wrong bounds") # Test if we provide an image with a defined scale. It should: # - write to the existing image # - use the image's scale im9 = galsim.ImageD(200, 200, scale=0.51) obj2.draw(im9) np.testing.assert_almost_equal( im9.scale, 0.51, 9, "obj2.draw(im9) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9)") # Test if we provide an image with a defined scale <= 0. It should: # - write to the existing image # - set the scale to obj2.nyquistDx() im9.scale = -0.51 im9.setZero() obj2.draw(im9) np.testing.assert_almost_equal( im9.scale, dx_nyq, 9, "obj2.draw(im9) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9)") im9.scale = 0 im9.setZero() obj2.draw(im9) np.testing.assert_almost_equal( im9.scale, dx_nyq, 9, "obj2.draw(im9) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9)") # Test if we provide an image and dx. It should: # - write to the existing image # - use the provided dx # - write the new dx value to the image's scale im9.scale = 0.73 im9.setZero() obj2.draw(im9, scale=0.51) np.testing.assert_almost_equal( im9.scale, 0.51, 9, "obj2.draw(im9,dx) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9,dx) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9,dx)") # Test if we provide an image and dx <= 0. It should: # - write to the existing image # - set the scale to obj2.nyquistDx() im9.scale = 0.73 im9.setZero() obj2.draw(im9, scale=-0.51) np.testing.assert_almost_equal( im9.scale, dx_nyq, 9, "obj2.draw(im9,dx<0) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9,dx<0) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9,dx<0)") im9.scale = 0.73 im9.setZero() obj2.draw(im9, scale=0) np.testing.assert_almost_equal( im9.scale, dx_nyq, 9, "obj2.draw(im9,scale=0) produced image with wrong scale") np.testing.assert_almost_equal( im9.array.sum(), test_flux, 4, "obj2.draw(im9,scale=0) produced image with wrong flux") np.testing.assert_almost_equal( CalculateScale(im9), 2, 2, "Measured wrong scale after obj2.draw(im9,scale=0)") # Test if we provide nx, ny, and scale. It should: # - create a new image with the right size # - set the scale im10 = obj2.draw(nx=200, ny=100, scale=dx_nyq) np.testing.assert_almost_equal( im10.array.shape, (100, 200), 9, "obj2.draw(nx=200, ny=100) produced image with wrong size") np.testing.assert_almost_equal( im10.scale, dx_nyq, 9, "obj2.draw(nx=200, ny=100) produced image with wrong scale") np.testing.assert_almost_equal( im10.array.sum(), test_flux, 4, "obj2.draw(nx=200, ny=100) produced image with wrong flux") try: # Test if we provide nx, ny, and no scale. It should: # - raise a ValueError im10 = galsim.ImageF() kwargs = {'nx': 200, 'ny': 100} np.testing.assert_raises(ValueError, obj2.draw, kwargs) # Test if we provide nx, ny, scale, and an existing image. It should: # - raise a ValueError im10 = galsim.ImageF() kwargs = {'nx': 200, 'ny': 100, 'scale': dx_nyq, 'image': im10} np.testing.assert_raises(ValueError, obj2.draw, kwargs) except ImportError: print 'The assert_raises tests require nose' # Test if we provide bounds and scale. It should: # - create a new image with the right size # - set the scale bounds = galsim.BoundsI(1, 200, 1, 100) im10 = obj2.draw(bounds=bounds, scale=dx_nyq) np.testing.assert_almost_equal( im10.array.shape, (100, 200), 9, "obj2.draw(bounds=galsim.Bounds(1,200,1,100)) produced image with wrong size" ) np.testing.assert_almost_equal( im10.scale, dx_nyq, 9, "obj2.draw(bounds=galsim.Bounds(1,200,1,100)) produced image with wrong scale" ) np.testing.assert_almost_equal( im10.array.sum(), test_flux, 4, "obj2.draw(bounds=galsim.Bounds(1,200,1,100)) produced image with wrong flux" ) try: # Test if we provide bounds and no scale. It should: # - raise a ValueError bounds = galsim.BoundsI(1, 200, 1, 100) kwargs = {'bounds': bounds} np.testing.assert_raises(ValueError, obj2.draw, kwargs) # Test if we provide bounds, scale, and an existing image. It should: # - raise a ValueError bounds = galsim.BoundsI(1, 200, 1, 100) kwargs = {'bounds': bounds, 'scale': dx_nyq, 'image': im10} np.testing.assert_raises(ValueError, obj2.draw, kwargs) except ImportError: print 'The assert_raises tests require nose' t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def main(argv): """ A little bit more sophisticated, but still pretty basic: - Use a sheared, exponential profile for the galaxy. - Convolve it by a circular Moffat PSF. - Add Poisson noise to the image. """ # 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("demo2") gal_flux = 1.e5 # counts gal_r0 = 0.7 # arcsec radius g1 = 0.8 # shear g2 = 0.1 # shear psf_beta = 5 # psf_re = 1.0 # arcsec # Fixed parameters nx = 33 ny = 33 pixel_scale = 0.2 # arcsec / pixel sky_level = 0 # counts / arcsec^2 # This time use a particular seed, so the image is deterministic. # This is the same seed that is used in demo2.yaml, which means the images produced # by the two methods will be precisely identical. random_seed = 1534225 logger.info('Starting demo script 2 using:') logger.info( ' - sheared (%.2f,%.2f) exponential galaxy (flux = %.1e, scale radius = %.2f),', g1, g2, gal_flux, gal_r0) logger.info(' - circular Moffat PSF (beta = %.1f, re = %.2f),', psf_beta, psf_re) logger.info(' - pixel scale = %.2f,', pixel_scale) logger.info(' - Poisson noise (sky level = %.1e).', sky_level) # Initialize the (pseudo-)random number generator that we will be using below. # For a technical reason that will be explained later (demo9.py), we add 1 to the # given random seed here. rng = galsim.BaseDeviate(random_seed + 1) # Define the galaxy profile. gal = galsim.Exponential(flux=gal_flux, scale_radius=gal_r0) # Shear the galaxy by some value. # There are quite a few ways you can use to specify a shape. # q, beta Axis ratio and position angle: q = b/a, 0 < q < 1 # e, beta Ellipticity and position angle: |e| = (1-q^2)/(1+q^2) # g, beta ("Reduced") Shear and position angle: |g| = (1-q)/(1+q) # eta, beta Conformal shear and position angle: eta = ln(1/q) # e1,e2 Ellipticity components: e1 = e cos(2 beta), e2 = e sin(2 beta) # g1,g2 ("Reduced") shear components: g1 = g cos(2 beta), g2 = g sin(2 beta) # eta1,eta2 Conformal shear components: eta1 = eta cos(2 beta), eta2 = eta sin(2 beta) gal = gal.shear(g1=g1, g2=g2) logger.debug('Made galaxy profile') # Define the PSF profile. psf = galsim.Moffat(beta=psf_beta, flux=1., half_light_radius=psf_re) logger.debug('Made PSF profile') # Final profile is the convolution of these. final = galsim.Convolve([gal, psf]) logger.debug('Convolved components into final profile') # Draw the image with a particular pixel scale. image = final.drawImage(scale=pixel_scale) # The "effective PSF" is the PSF as drawn on an image, which includes the convolution # by the pixel response. We label it epsf here. image_epsf = psf.drawImage(scale=pixel_scale) logger.debug('Made image of the profile') # To get Poisson noise on the image, we will use a class called PoissonNoise. # However, we want the noise to correspond to what you would get with a significant # flux from tke sky. This is done by telling PoissonNoise to add noise from a # sky level in addition to the counts currently in the image. # # One wrinkle here is that the PoissonNoise class needs the sky level in each pixel, # while we have a sky_level in counts per arcsec^2. So we need to convert: sky_level_pixel = sky_level * pixel_scale**2 noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel) image.addNoise(noise) logger.debug('Added Poisson noise') # Write the image to a file. if not os.path.isdir('output'): os.mkdir('output') file_name = os.path.join('output', 'demo2.fits') file_name_epsf = os.path.join('output', 'demo2_epsf.fits') image.write(file_name) image_epsf.write(file_name_epsf) logger.info('Wrote image to %r', file_name) logger.info('Wrote effective PSF image to %r', file_name_epsf) results = galsim.hsm.EstimateShear(image, image_epsf) logger.info('HSM reports that the image has observed shape and size:') logger.info(' e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)', results.observed_shape.e1, results.observed_shape.e2, results.moments_sigma) logger.info( 'When carrying out Regaussianization PSF correction, HSM reports distortions' ) logger.info(' e1, e2 = %.3f, %.3f', results.corrected_e1, results.corrected_e2) logger.info( 'Expected values in the limit that noise and non-Gaussianity are negligible:' ) exp_shear = galsim.Shear(g1=g1, g2=g2) logger.info(' g1, g2 = %.3f, %.3f', exp_shear.e1, exp_shear.e2) ## NR added plt.imshow(image.array) plt.show() return image
def main(argv): # Where to find and output data path, filename = os.path.split(__file__) datapath = os.path.abspath(os.path.join(path, "data/")) outpath = os.path.abspath(os.path.join(path, "output/")) # In non-script code, use getLogger(__name__) at module scope instead. logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo12") # initialize (pseudo-)random number generator random_seed = 1234567 rng = galsim.BaseDeviate(random_seed) # read in SEDs SED_names = ['CWW_E_ext', 'CWW_Sbc_ext', 'CWW_Scd_ext', 'CWW_Im_ext'] SEDs = {} for SED_name in SED_names: SED_filename = os.path.join(datapath, '{0}.sed'.format(SED_name)) # Here we create some galsim.SED objects to hold star or galaxy spectra. The most # convenient way to create realistic spectra is to read them in from a two-column ASCII # file, where the first column is wavelength and the second column is flux. Wavelengths in # the example SED files are in Angstroms, flux in flambda. The default wavelength type for # galsim.SED is nanometers, however, so we need to override by specifying # `wave_type = 'Ang'`. SED = galsim.SED(SED_filename, wave_type='Ang') # The normalization of SEDs affects how many photons are eventually drawn into an image. # One way to control this normalization is to specify the flux density in photons per nm # at a particular wavelength. For example, here we normalize such that the photon density # is 1 photon per nm at 500 nm. SEDs[SED_name] = SED.withFluxDensity(target_flux_density=1.0, wavelength=500) logger.debug('Successfully read in SEDs') # read in the LSST filters filter_names = 'ugrizy' filters = {} for filter_name in filter_names: filter_filename = os.path.join(datapath, 'LSST_{0}.dat'.format(filter_name)) # Here we create some galsim.Bandpass objects to represent the filters we're observing # through. These include the entire imaging system throughput including the atmosphere, # reflective and refractive optics, filters, and the CCD quantum efficiency. These are # also conveniently read in from two-column ASCII files where the first column is # wavelength and the second column is dimensionless flux. The example filter files have # units of nanometers and dimensionless throughput, which is exactly what galsim.Bandpass # expects, so we just specify the filename. filters[filter_name] = galsim.Bandpass(filter_filename) # For speed, we can thin out the wavelength sampling of the filter a bit. # In the following line, `rel_err` specifies the relative error when integrating over just # the filter (however, this is not necessarily the relative error when integrating over the # filter times an SED) filters[filter_name] = filters[filter_name].thin(rel_err=1e-4) logger.debug('Read in filters') pixel_scale = 0.2 # arcseconds #----------------------------------------------------------------------------------------------- # Part A: chromatic de Vaucouleurs galaxy # Here we create a chromatic version of a de Vaucouleurs profile using the Chromatic class. # This class lets one create chromatic versions of any galsim GSObject class. The first # argument is the GSObject instance to be chromaticized, and the second argument is the # profile's SED. logger.info('') logger.info('Starting part A: chromatic De Vaucouleurs galaxy') redshift = 0.8 mono_gal = galsim.DeVaucouleurs(half_light_radius=0.5) SED = SEDs['CWW_E_ext'].atRedshift(redshift) gal = galsim.Chromatic(mono_gal, SED) # You can still shear, shift, and dilate the resulting chromatic object. gal = gal.shear(g1=0.5, g2=0.3).dilate(1.05).shift((0.0, 0.1)) logger.debug('Created Chromatic') # convolve with PSF to make final profile PSF = galsim.Moffat(fwhm=0.6, beta=2.5) final = galsim.Convolve([gal, PSF]) logger.debug('Created final profile') # draw profile through LSST filters gaussian_noise = galsim.GaussianNoise(rng, sigma=0.1) for filter_name, filter_ in filters.iteritems(): img = galsim.ImageF(64, 64, scale=pixel_scale) final.drawImage(filter_, image=img) img.addNoise(gaussian_noise) logger.debug('Created {0}-band image'.format(filter_name)) out_filename = os.path.join(outpath, 'demo12a_{0}.fits'.format(filter_name)) galsim.fits.write(img, out_filename) logger.debug('Wrote {0}-band image to disk'.format(filter_name)) logger.info('Added flux for {0}-band image: {1}'.format( filter_name, img.added_flux)) logger.info( 'You can display the output in ds9 with a command line that looks something like:' ) logger.info( 'ds9 output/demo12a_*.fits -match scale -zoom 2 -match frame image &') #----------------------------------------------------------------------------------------------- # Part B: chromatic bulge+disk galaxy logger.info('') logger.info('Starting part B: chromatic bulge+disk galaxy') redshift = 0.8 # make a bulge ... mono_bulge = galsim.DeVaucouleurs(half_light_radius=0.5) bulge_SED = SEDs['CWW_E_ext'].atRedshift(redshift) # The `*` operator can be used as a shortcut for creating a chromatic version of a GSObject: bulge = mono_bulge * bulge_SED bulge = bulge.shear(g1=0.12, g2=0.07) logger.debug('Created bulge component') # ... and a disk ... mono_disk = galsim.Exponential(half_light_radius=2.0) disk_SED = SEDs['CWW_Im_ext'].atRedshift(redshift) disk = mono_disk * disk_SED disk = disk.shear(g1=0.4, g2=0.2) logger.debug('Created disk component') # ... and then combine them. bdgal = 1.1 * ( 0.8 * bulge + 4 * disk ) # you can add and multiply ChromaticObjects just like GSObjects bdfinal = galsim.Convolve([bdgal, PSF]) # Note that at this stage, our galaxy is chromatic but our PSF is still achromatic. Part C) # below will dive into chromatic PSFs. logger.debug('Created bulge+disk galaxy final profile') # draw profile through LSST filters gaussian_noise = galsim.GaussianNoise(rng, sigma=0.02) for filter_name, filter_ in filters.iteritems(): img = galsim.ImageF(64, 64, scale=pixel_scale) bdfinal.drawImage(filter_, image=img) img.addNoise(gaussian_noise) logger.debug('Created {0}-band image'.format(filter_name)) out_filename = os.path.join(outpath, 'demo12b_{0}.fits'.format(filter_name)) galsim.fits.write(img, out_filename) logger.debug('Wrote {0}-band image to disk'.format(filter_name)) logger.info('Added flux for {0}-band image: {1}'.format( filter_name, img.added_flux)) logger.info( 'You can display the output in ds9 with a command line that looks something like:' ) logger.info( 'ds9 -rgb -blue -scale limits -0.2 0.8 output/demo12b_r.fits -green -scale limits' + ' -0.25 1.0 output/demo12b_i.fits -red -scale limits -0.25 1.0 output/demo12b_z.fits' + ' -zoom 2 &') #----------------------------------------------------------------------------------------------- # Part C: chromatic PSF logger.info('') logger.info('Starting part C: chromatic PSF') redshift = 0.0 mono_gal = galsim.Exponential(half_light_radius=0.5) SED = SEDs['CWW_Im_ext'].atRedshift(redshift) # Here's another way to set the normalization of the SED. If we want 50 counts to be drawn # when observing an object with this SED through the LSST g-band filter, for instance, then we # can do: SED = SED.withFlux(50.0, filters['g']) # The flux drawn through other bands, which sample different parts of the SED and have different # throughputs, will, of course, be different. gal = mono_gal * SED gal = gal.shear(g1=0.5, g2=0.3) logger.debug('Created `Chromatic` galaxy') # For a ground-based PSF, two chromatic effects are introduced by the atmosphere: # (i) differential chromatic refraction (DCR), and (ii) wavelength-dependent seeing. # # DCR shifts the position of the PSF as a function of wavelength. Blue light is shifted # toward the zenith slightly more than red light. # # Kolmogorov turbulence in the atmosphere leads to a seeing size (e.g., FWHM) that scales with # wavelength to the (-0.2) power. # # The ChromaticAtmosphere function will attach both of these effects to a fiducial PSF at # some fiducial wavelength. # First we define a monochromatic PSF to be the fiducial PSF. PSF_500 = galsim.Moffat(beta=2.5, fwhm=0.5) # Then we use ChromaticAtmosphere to manipulate this fiducial PSF as a function of wavelength. # ChromaticAtmosphere also needs to know the wavelength of the fiducial PSF, and the location # and orientation of the object with respect to the zenith. This final piece of information # can be specified in several ways (see the ChromaticAtmosphere docstring for all of them). # Here are a couple ways: let's pretend our object is located near M101 on the sky, we observe # it 1 hour before it transits and we're observing from Mauna Kea. ra = galsim.HMS_Angle("14:03:13") # hours : minutes : seconds dec = galsim.DMS_Angle("54:20:57") # degrees : minutes : seconds m101 = galsim.CelestialCoord(ra, dec) latitude = 19.8207 * galsim.degrees # latitude of Mauna Kea HA = -1.0 * galsim.hours # Hour angle = one hour before transit # Then we can compute the zenith angle and parallactic angle (which is is the position angle # of the zenith measured from North through East) of this object: za, pa = galsim.dcr.zenith_parallactic_angles(m101, HA=HA, latitude=latitude) # And then finally, create the chromatic PSF PSF = galsim.ChromaticAtmosphere(PSF_500, 500.0, zenith_angle=za, parallactic_angle=pa) # We could have also just passed `m101`, `latitude` and `HA` to ChromaticAtmosphere directly: PSF = galsim.ChromaticAtmosphere(PSF_500, 500.0, obj_coord=m101, latitude=latitude, HA=HA) # and proceed like normal. # convolve with galaxy to create final profile final = galsim.Convolve([gal, PSF]) logger.debug('Created chromatic PSF finale profile') # Draw profile through LSST filters gaussian_noise = galsim.GaussianNoise(rng, sigma=0.03) for filter_name, filter_ in filters.iteritems(): img = galsim.ImageF(64, 64, scale=pixel_scale) final.drawImage(filter_, image=img) img.addNoise(gaussian_noise) logger.debug('Created {0}-band image'.format(filter_name)) out_filename = os.path.join(outpath, 'demo12c_{0}.fits'.format(filter_name)) galsim.fits.write(img, out_filename) logger.debug('Wrote {0}-band image to disk'.format(filter_name)) logger.info('Added flux for {0}-band image: {1}'.format( filter_name, img.added_flux)) logger.info( 'You can display the output in ds9 with a command line that looks something like:' ) logger.info( 'ds9 output/demo12c_*.fits -match scale -zoom 2 -match frame image -blink &' )
def test_exponential(): """Test the generation of a specific exp profile against a known result. """ re = 1.0 # Note the factor below should really be 1.6783469900166605, but the value of 1.67839 is # retained here as it was used by SBParse to generate the original known result (this changed # in commit b77eb05ab42ecd31bc8ca03f1c0ae4ee0bc0a78b. # The value of this test for regression purposes is not harmed by retaining the old scaling, it # just means that the half light radius chosen for the test is not really 1, but 0.999974... r0 = re / 1.67839 savedImg = galsim.fits.read(os.path.join(imgdir, "exp_1.fits")) dx = 0.2 myImg = galsim.ImageF(savedImg.bounds, scale=dx) myImg.setCenter(0, 0) expon = galsim.Exponential(flux=1., scale_radius=r0) expon.drawImage(myImg, scale=dx, method="sb", use_true_center=False) np.testing.assert_array_almost_equal( myImg.array, savedImg.array, 5, err_msg="Using GSObject Exponential disagrees with expected result") # Check with default_params expon = galsim.Exponential(flux=1., scale_radius=r0, gsparams=default_params) expon.drawImage(myImg, scale=dx, method="sb", use_true_center=False) np.testing.assert_array_almost_equal( myImg.array, savedImg.array, 5, err_msg= "Using GSObject Exponential with default_params disagrees with expected result" ) expon = galsim.Exponential(flux=1., scale_radius=r0, gsparams=galsim.GSParams()) expon.drawImage(myImg, scale=dx, method="sb", use_true_center=False) np.testing.assert_array_almost_equal( myImg.array, savedImg.array, 5, err_msg= "Using GSObject Exponential with GSParams() disagrees with expected result" ) # Use non-unity values. expon = galsim.Exponential(flux=1.7, scale_radius=0.91) gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8) expon2 = galsim.Exponential(flux=1.7, scale_radius=0.91, gsparams=gsp) assert expon2 != expon assert expon2 == expon.withGSParams(gsp) check_basic(expon, "Exponential") # Test photon shooting. do_shoot(expon, myImg, "Exponential") # Test kvalues do_kvalue(expon, myImg, "Exponential") # Check picklability do_pickle(expon, lambda x: x.drawImage(method='no_pixel')) do_pickle(expon) # Should raise an exception if both scale_radius and half_light_radius are provided. assert_raises(TypeError, galsim.Exponential, scale_radius=3, half_light_radius=1) # Or neither. assert_raises(TypeError, galsim.Exponential)
def get_catsim_galaxy(entry, filt, survey, no_disk=False, no_bulge=False, no_agn=False): """Returns a bulge/disk/agn Galsim galaxy profile based on entry. This function returns a composite galsim galaxy profile with bulge, disk and AGN based on the parameters in entry, and using observing conditions defined by the survey and the filter. Credit: WeakLensingDeblending (https://github.com/LSSTDESC/WeakLensingDeblending) Args: entry (astropy.table.Table): single astropy line containing information on the galaxy survey (btk.survey.Survey): BTK Survey object filt (btk.survey.Filter): BTK Filter object no_disk (bool): Sets the flux for the disk to zero no_bulge (bool): Sets the flux for the bulge to zero no_agn (bool): Sets the flux for the AGN to zero Returns: Galsim galaxy profile """ components = [] total_flux = get_flux(entry[filt.name + "_ab"], filt, survey) # Calculate the flux of each component in detected electrons. total_fluxnorm = entry["fluxnorm_disk"] + entry["fluxnorm_bulge"] + entry[ "fluxnorm_agn"] disk_flux = 0.0 if no_disk else entry[ "fluxnorm_disk"] / total_fluxnorm * total_flux bulge_flux = 0.0 if no_bulge else entry[ "fluxnorm_bulge"] / total_fluxnorm * total_flux agn_flux = 0.0 if no_agn else entry[ "fluxnorm_agn"] / total_fluxnorm * total_flux if disk_flux + bulge_flux + agn_flux == 0: raise SourceNotVisible if disk_flux > 0: beta_radians = np.radians(entry["pa_disk"]) if bulge_flux > 0: assert entry["pa_disk"] == entry[ "pa_bulge"], "Sersic components have different beta." a_d, b_d = entry["a_d"], entry["b_d"] disk_hlr_arcsecs = np.sqrt(a_d * b_d) disk_q = b_d / a_d disk = galsim.Exponential(flux=disk_flux, half_light_radius=disk_hlr_arcsecs).shear( q=disk_q, beta=beta_radians * galsim.radians) components.append(disk) if bulge_flux > 0: beta_radians = np.radians(entry["pa_bulge"]) a_b, b_b = entry["a_b"], entry["b_b"] bulge_hlr_arcsecs = np.sqrt(a_b * b_b) bulge_q = b_b / a_b bulge = galsim.DeVaucouleurs( flux=bulge_flux, half_light_radius=bulge_hlr_arcsecs).shear( q=bulge_q, beta=beta_radians * galsim.radians) components.append(bulge) if agn_flux > 0: agn = galsim.Gaussian(flux=agn_flux, sigma=1e-8) components.append(agn) profile = galsim.Add(components) return profile
def _buildParametric(record, gsparams=None, chromatic=False, bandpass=None, sed=None): # Get fit parameters. For 'sersicfit', the result is an array of 8 numbers for each # galaxy: # SERSICFIT[0]: intensity of light profile at the half-light radius. # SERSICFIT[1]: half-light radius measured along the major axis, in units of pixels # in the COSMOS lensing data reductions (0.03 arcsec). # SERSICFIT[2]: Sersic n. # SERSICFIT[3]: q, the ratio of minor axis to major axis length. # SERSICFIT[4]: boxiness, currently fixed to 0, meaning isophotes are all # elliptical. # SERSICFIT[5]: x0, the central x position in pixels. # SERSICFIT[6]: y0, the central y position in pixels. # SERSICFIT[7]: phi, the position angle in radians. If phi=0, the major axis is # lined up with the x axis of the image. # For 'bulgefit', the result is an array of 16 parameters that comes from doing a # 2-component sersic fit. The first 8 are the parameters for the disk, with n=1, and # the last 8 are for the bulge, with n=4. bparams = record['bulgefit'] sparams = record['sersicfit'] # Get the status flag for the fits. Entries 0 and 4 in 'fit_status' are relevant for # bulgefit and sersicfit, respectively. bstat = record['fit_status'][0] sstat = record['fit_status'][4] # Get the precomputed bulge-to-total flux ratio for the 2-component fits. dvc_btt = record['fit_dvc_btt'] # Get the precomputed median absolute deviation for the 1- and 2-component fits. # These quantities are used to ascertain whether the 2-component fit is really # justified, or if the 1-component Sersic fit is sufficient to describe the galaxy # light profile. bmad = record['fit_mad_b'] smad = record['fit_mad_s'] # First decide if we can / should use bulgefit, otherwise sersicfit. This decision # process depends on: the status flags for the fits, the bulge-to-total ratios (if near # 0 or 1, just use single component fits), the sizes for the bulge and disk (if <=0 then # use single component fits), the axis ratios for the bulge and disk (if <0.051 then use # single component fits), and a comparison of the median absolute deviations to see # which is better. The reason for the 0.051 cutoff is that the fits were bound at 0.05 # as a minimum, so anything below 0.051 generally means that the fitter hit the boundary # for the 2-component fits, typically meaning that we don't have enough information to # make reliable 2-component fits. use_bulgefit = True if (bstat < 1 or bstat > 4 or dvc_btt < 0.1 or dvc_btt > 0.9 or np.isnan(dvc_btt) or bparams[9] <= 0 or bparams[1] <= 0 or bparams[11] < 0.051 or bparams[3] < 0.051 or smad < bmad): use_bulgefit = False # Then check if sersicfit is viable; if not, this galaxy is a total failure. # Note that we can avoid including these in the catalog in the first place by using # `exclude_fail=True` when making the catalog. if sstat < 1 or sstat > 4 or sparams[1] <= 0 or sparams[0] <= 0: raise RuntimeError("Cannot make parametric model for this galaxy!") # If we're supposed to use the 2-component fits, get all the parameters. if use_bulgefit: # Bulge parameters: # Minor-to-major axis ratio: bulge_q = bparams[11] # Position angle, now represented as a galsim.Angle: bulge_beta = bparams[15] * galsim.radians # We have to convert from the stored half-light radius along the major axis, to an # azimuthally averaged one (multiplying by sqrt(bulge_q)). We also have to convert # to our native units of arcsec, from units of COSMOS pixels. bulge_hlr = cosmos_pix_scale * np.sqrt(bulge_q) * bparams[9] # The stored quantity is the surface brightness at the half-light radius. We have # to convert to total flux within an n=4 surface brightness profile. bulge_flux = 2.0 * np.pi * 3.607 * ( bulge_hlr**2) * bparams[8] / cosmos_pix_scale**2 # Disk parameters, defined analogously: disk_q = bparams[3] disk_beta = bparams[7] * galsim.radians disk_hlr = cosmos_pix_scale * np.sqrt(disk_q) * bparams[1] disk_flux = 2.0 * np.pi * 1.901 * ( disk_hlr**2) * bparams[0] / cosmos_pix_scale**2 bfrac = bulge_flux / (bulge_flux + disk_flux) # Make sure the bulge-to-total flux ratio is not nonsense. if bfrac < 0 or bfrac > 1 or np.isnan(bfrac): raise RuntimeError( "Cannot make parametric model for this galaxy") # Then make the two components of the galaxy. if chromatic: # We define the GSObjects with flux=1, then multiply by an SED defined to have # the appropriate (observed) magnitude at the redshift in the COSMOS passband. z = record['zphot'] target_bulge_mag = record['mag_auto'] - 2.5 * math.log10(bfrac) bulge_sed = sed[0].atRedshift(z).withMagnitude( target_bulge_mag, bandpass) bulge = galsim.DeVaucouleurs(half_light_radius=bulge_hlr, gsparams=gsparams) bulge *= bulge_sed target_disk_mag = record['mag_auto'] - 2.5 * math.log10( (1. - bfrac)) disk_sed = sed[1].atRedshift(z).withMagnitude( target_disk_mag, bandpass) disk = galsim.Exponential(half_light_radius=disk_hlr, gsparams=gsparams) disk *= disk_sed else: bulge = galsim.DeVaucouleurs(flux=bulge_flux, half_light_radius=bulge_hlr, gsparams=gsparams) disk = galsim.Exponential(flux=disk_flux, half_light_radius=disk_hlr, gsparams=gsparams) # Apply shears for intrinsic shape. if bulge_q < 1.: bulge = bulge.shear(q=bulge_q, beta=bulge_beta) if disk_q < 1.: disk = disk.shear(q=disk_q, beta=disk_beta) gal = bulge + disk else: # Do a similar manipulation to the stored quantities for the single Sersic profiles. gal_n = sparams[2] # Fudge this if it is at the edge of the allowed n values. Since GalSim (as of # #325 and #449) allow Sersic n in the range 0.3<=n<=6, the only problem is that # the fits occasionally go as low as n=0.2. The fits in this file only go to n=6, # so there is no issue with too-high values, but we also put a guard on that side # in case other samples are swapped in that go to higher value of sersic n. if gal_n < 0.3: gal_n = 0.3 if gal_n > 6.0: gal_n = 6.0 gal_q = sparams[3] gal_beta = sparams[7] * galsim.radians gal_hlr = cosmos_pix_scale * np.sqrt(gal_q) * sparams[1] # Below is the calculation of the full Sersic n-dependent quantity that goes into # the conversion from surface brightness to flux, which here we're calling # 'prefactor'. In the n=4 and n=1 cases above, this was precomputed, but here we # have to calculate for each value of n. tmp_ser = galsim.Sersic(gal_n, half_light_radius=gal_hlr, gsparams=gsparams) gal_flux = sparams[0] / tmp_ser.xValue( 0, gal_hlr) / cosmos_pix_scale**2 if chromatic: gal = galsim.Sersic(gal_n, flux=1., half_light_radius=gal_hlr, gsparams=gsparams) if gal_n < 1.5: use_sed = sed[1] # disk elif gal_n >= 1.5 and gal_n < 3.0: use_sed = sed[2] # intermediate else: use_sed = sed[0] # bulge target_mag = record['mag_auto'] z = record['zphot'] gal *= use_sed.atRedshift(z).withMagnitude( target_mag, bandpass) else: gal = galsim.Sersic(gal_n, flux=gal_flux, half_light_radius=gal_hlr, gsparams=gsparams) # Apply shears for intrinsic shape. if gal_q < 1.: gal = gal.shear(q=gal_q, beta=gal_beta) return gal
print "Sky noise: {}".format(etc.sigma_sky) try: os.mkdir(args.outdir) except: pass psfimg = psf.drawImage(nx=args.nx, ny=args.nx, scale=0.2) psfimg.write(os.path.join(args.outdir, 'psf.fits')) infile = h5py.File(args.h5read) for i, datum in enumerate(infile['data']): ra, dec, z, r, mag, e1, e2, g1, g2 = datum flux = etc.flux(mag) print i, mag, flux, e1, e2 gal = (galsim.Exponential(half_light_radius=0.3).shear(e1=e1, e2=e2).shear( e1=g1, e2=g2).withFlux(flux)) obj = galsim.Convolve(gal, psf) img = obj.drawImage(nx=args.nx, ny=args.nx, scale=0.2) img.addNoise(noise) h1 = galsim.FitsHeader() h1['RA'] = ra h1['DEC'] = dec h1['z'] = z h1['r'] = r h1['mag'] = mag h1['e1'] = e1 h1['e2'] = e2 h1['g1'] = g1 h1['g2'] = g2 img.header = h1
def test_photon_array(): """Test the basic methods of PhotonArray class """ nphotons = 1000 # First create from scratch photon_array = galsim.PhotonArray(nphotons) assert len(photon_array.x) == nphotons assert len(photon_array.y) == nphotons assert len(photon_array.flux) == nphotons assert not photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() # Initial values should all be 0 np.testing.assert_array_equal(photon_array.x, 0.) np.testing.assert_array_equal(photon_array.y, 0.) np.testing.assert_array_equal(photon_array.flux, 0.) # Check picklability do_pickle(photon_array) # Check assignment via numpy [:] photon_array.x[:] = 5 photon_array.y[:] = 17 photon_array.flux[:] = 23 np.testing.assert_array_equal(photon_array.x, 5.) np.testing.assert_array_equal(photon_array.y, 17.) np.testing.assert_array_equal(photon_array.flux, 23.) # Check assignment directly to the attributes photon_array.x = 25 photon_array.y = 37 photon_array.flux = 53 np.testing.assert_array_equal(photon_array.x, 25.) np.testing.assert_array_equal(photon_array.y, 37.) np.testing.assert_array_equal(photon_array.flux, 53.) # Now create from shooting a profile obj = galsim.Exponential(flux=1.7, scale_radius=2.3) rng = galsim.UniformDeviate(1234) photon_array = obj.shoot(nphotons, rng) orig_x = photon_array.x.copy() orig_y = photon_array.y.copy() orig_flux = photon_array.flux.copy() assert len(photon_array.x) == nphotons assert len(photon_array.y) == nphotons assert len(photon_array.flux) == nphotons assert not photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() # Check arithmetic ops photon_array.x *= 5 photon_array.y += 17 photon_array.flux /= 23 np.testing.assert_almost_equal(photon_array.x, orig_x * 5.) np.testing.assert_almost_equal(photon_array.y, orig_y + 17.) np.testing.assert_almost_equal(photon_array.flux, orig_flux / 23.) # Check picklability again with non-zero values do_pickle(photon_array) # Now assign to the optional arrays photon_array.dxdz = 0.17 assert photon_array.hasAllocatedAngles() assert not photon_array.hasAllocatedWavelengths() np.testing.assert_array_equal(photon_array.dxdz, 0.17) np.testing.assert_array_equal(photon_array.dydz, 0.) photon_array.dydz = 0.59 np.testing.assert_array_equal(photon_array.dxdz, 0.17) np.testing.assert_array_equal(photon_array.dydz, 0.59) # Start over to check that assigning to wavelength leaves dxdz, dydz alone. photon_array = obj.shoot(nphotons, rng) photon_array.wavelength = 500. assert photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() np.testing.assert_array_equal(photon_array.wavelength, 500) photon_array.dxdz = 0.23 photon_array.dydz = 0.88 photon_array.wavelength = 912. assert photon_array.hasAllocatedWavelengths() assert photon_array.hasAllocatedAngles() np.testing.assert_array_equal(photon_array.dxdz, 0.23) np.testing.assert_array_equal(photon_array.dydz, 0.88) np.testing.assert_array_equal(photon_array.wavelength, 912) # Check picklability again with non-zero values for everything do_pickle(photon_array)
def __generateGAL(self, re): self.gal = galsim.Exponential(flux=1.0, half_light_radius=re * self.galData.resolution)
e20[i] = 0 if ((e10[i]**2) + (e20[i]**2) > 0.64): e10[i] = 0.0 e20[i] = 0.0 gsparams=galsim.GSParams(folding_threshold=1.e-2,maxk_threshold=2.e-3,\ xvalue_accuracy=1.e-4,kvalue_accuracy=1.e-4,\ shoot_accuracy=1.e-4,minimum_fft_size=64) #psf1=galsim.Moffat(fwhm=psf_fwhm[0],beta=2.5,gsparams=gsparams) #to be continue #psf1=psf1.shear(e1=e1_psf,e2=e2_psf) psf1 = galsim.Gaussian(fwhm=psf_fwhm[0], gsparams=gsparams) #psf1=galsim.Kolmogorov(fwhm=psf_fwhm[0],gsparams=gsparams) gal1 = galsim.Gaussian(half_light_radius=2, 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) #to be continue psf = psf1 final_epsf_image = psf1.drawImage(scale=pixel_scale) gal = [gal1, gal2, gal3, gal4] BJe1 = [[], [], [], []] BJe2 = [[], [], [], []] RGe1 = [[], [], [], []] RGe2 = [[], [], [], []] Me1 = [[], [], [], []] Me2 = [[], [], [], []] Wucha = [[], [], [], []]
def _inject_fake_objects(self, mbobs): """ inject a simple model for quick tests """ import galsim iconf = self.config['inject'] model_name = iconf['model'] hlr = iconf['hlr'] flux = iconf['flux'] if model_name == 'exp': model = galsim.Exponential( half_light_radius=hlr, flux=flux, ) elif model_name == 'bdf': fracdev = iconf['fracdev'] model = galsim.Add( galsim.Exponential( half_light_radius=hlr, flux=(1 - fracdev), ), galsim.DeVaucouleurs( half_light_radius=hlr, flux=fracdev, )).withFlux(flux) else: raise ValueError('bad model: "%s"' % model_name) if 'psf' in iconf: psf_model = galsim.Gaussian(fwhm=iconf['psf']['fwhm'], ) method = 'fft' else: psf_model = None method = 'no_pixel' Tfake = ngmix.moments.fwhm_to_T(hlr / 0.5) for obslist in mbobs: obslist.meta['Tsky'] = Tfake for obs in obslist: gsimage = galsim.Image( obs.image.copy(), wcs=obs.jacobian.get_galsim_wcs(), ) if psf_model is None: psf_gsimage = galsim.Image( obs.psf.image / obs.psf.image.sum(), wcs=obs.psf.jacobian.get_galsim_wcs(), ) psf_to_conv = galsim.InterpolatedImage( psf_gsimage, #x_interpolant='lanczos15', ) obs.psf.image = psf_gsimage.array else: pshape = obs.psf.image.shape psf_gsimage = psf_model.drawImage( nx=pshape[1], ny=pshape[0], wcs=obs.psf.jacobian.get_galsim_wcs(), ) psf_to_conv = galsim.InterpolatedImage(psf_gsimage, ) obs.psf.image = psf_gsimage.array tmodel = galsim.Convolve( model, psf_to_conv, ) tmodel.drawImage( image=gsimage, method=method, ) image = gsimage.array wtmax = obs.weight.max() err = np.sqrt(1.0 / wtmax) image += self.rng.normal( scale=err, size=image.shape, ) obs.image = image
def test_gsparams(): """Test withGSParams with some non-default gsparams """ obj1 = galsim.Exponential(half_light_radius=1.7) obj2 = galsim.Pixel(scale=0.2) gsp = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4, maximum_fft_size=1.e4) gsp2 = galsim.GSParams(folding_threshold=1.e-2, maxk_threshold=1.e-2) # Convolve conv = galsim.Convolve(obj1, obj2) conv1 = conv.withGSParams(gsp) assert conv.gsparams == galsim.GSParams() assert conv1.gsparams == gsp assert conv1.obj_list[0].gsparams == gsp assert conv1.obj_list[1].gsparams == gsp conv2 = galsim.Convolve(obj1.withGSParams(gsp), obj2.withGSParams(gsp)) conv3 = galsim.Convolve(galsim.Exponential(half_light_radius=1.7, gsparams=gsp), galsim.Pixel(scale=0.2)) conv4 = galsim.Convolve(obj1, obj2, gsparams=gsp) assert conv != conv1 assert conv1 == conv2 assert conv1 == conv3 assert conv1 == conv4 print('stepk = ',conv.stepk, conv1.stepk) assert conv1.stepk < conv.stepk print('maxk = ',conv.maxk, conv1.maxk) assert conv1.maxk > conv.maxk conv5 = galsim.Convolve(obj1, obj2, gsparams=gsp, propagate_gsparams=False) assert conv5 != conv4 assert conv5.gsparams == gsp assert conv5.obj_list[0].gsparams == galsim.GSParams() assert conv5.obj_list[1].gsparams == galsim.GSParams() conv6 = conv5.withGSParams(gsp2) assert conv6 != conv5 assert conv6.gsparams == gsp2 assert conv6.obj_list[0].gsparams == galsim.GSParams() assert conv6.obj_list[1].gsparams == galsim.GSParams() # AutoConvolve conv = galsim.AutoConvolve(obj1) conv1 = conv.withGSParams(gsp) assert conv.gsparams == galsim.GSParams() assert conv1.gsparams == gsp assert conv1.orig_obj.gsparams == gsp conv2 = galsim.AutoConvolve(obj1.withGSParams(gsp)) conv3 = galsim.AutoConvolve(obj1, gsparams=gsp) assert conv != conv1 assert conv1 == conv2 assert conv1 == conv3 print('stepk = ',conv.stepk, conv1.stepk) assert conv1.stepk < conv.stepk print('maxk = ',conv.maxk, conv1.maxk) assert conv1.maxk > conv.maxk conv4 = galsim.AutoConvolve(obj1, gsparams=gsp, propagate_gsparams=False) assert conv4 != conv3 assert conv4.gsparams == gsp assert conv4.orig_obj.gsparams == galsim.GSParams() conv5 = conv4.withGSParams(gsp2) assert conv5 != conv4 assert conv5.gsparams == gsp2 assert conv5.orig_obj.gsparams == galsim.GSParams() # AutoCorrelate conv = galsim.AutoCorrelate(obj1) conv1 = conv.withGSParams(gsp) assert conv.gsparams == galsim.GSParams() assert conv1.gsparams == gsp assert conv1.orig_obj.gsparams == gsp conv2 = galsim.AutoCorrelate(obj1.withGSParams(gsp)) conv3 = galsim.AutoCorrelate(obj1, gsparams=gsp) assert conv != conv1 assert conv1 == conv2 assert conv1 == conv3 print('stepk = ',conv.stepk, conv1.stepk) assert conv1.stepk < conv.stepk print('maxk = ',conv.maxk, conv1.maxk) assert conv1.maxk > conv.maxk conv4 = galsim.AutoCorrelate(obj1, gsparams=gsp, propagate_gsparams=False) assert conv4 != conv3 assert conv4.gsparams == gsp assert conv4.orig_obj.gsparams == galsim.GSParams() conv5 = conv4.withGSParams(gsp2) assert conv5 != conv4 assert conv5.gsparams == gsp2 assert conv5.orig_obj.gsparams == galsim.GSParams() # Deconvolve conv = galsim.Convolve(obj1, galsim.Deconvolve(obj2)) conv1 = conv.withGSParams(gsp) assert conv.gsparams == galsim.GSParams() assert conv1.gsparams == gsp assert conv1.obj_list[0].gsparams == gsp assert conv1.obj_list[1].gsparams == gsp assert conv1.obj_list[1].orig_obj.gsparams == gsp conv2 = galsim.Convolve(obj1, galsim.Deconvolve(obj2.withGSParams(gsp))) conv3 = galsim.Convolve(obj1.withGSParams(gsp), galsim.Deconvolve(obj2)) conv4 = galsim.Convolve(obj1, galsim.Deconvolve(obj2, gsparams=gsp)) assert conv != conv1 assert conv1 == conv2 assert conv1 == conv3 assert conv1 == conv4 print('stepk = ',conv.stepk, conv1.stepk) assert conv1.stepk < conv.stepk print('maxk = ',conv.maxk, conv1.maxk) assert conv1.maxk > conv.maxk conv5 = galsim.Convolve(obj1, galsim.Deconvolve(obj2, gsparams=gsp, propagate_gsparams=False)) assert conv5 != conv4 assert conv5.gsparams == gsp assert conv5.obj_list[0].gsparams == gsp assert conv5.obj_list[1].gsparams == gsp assert conv5.obj_list[1].orig_obj.gsparams == galsim.GSParams() conv6 = conv5.withGSParams(gsp2) assert conv6 != conv5 assert conv6.gsparams == gsp2 assert conv6.obj_list[0].gsparams == gsp2 assert conv6.obj_list[1].gsparams == gsp2 assert conv6.obj_list[1].orig_obj.gsparams == galsim.GSParams() # FourierSqrt conv = galsim.Convolve(obj1, galsim.FourierSqrt(obj2)) conv1 = conv.withGSParams(gsp) assert conv.gsparams == galsim.GSParams() assert conv1.gsparams == gsp assert conv1.obj_list[0].gsparams == gsp assert conv1.obj_list[1].gsparams == gsp assert conv1.obj_list[1].orig_obj.gsparams == gsp conv2 = galsim.Convolve(obj1, galsim.FourierSqrt(obj2.withGSParams(gsp))) conv3 = galsim.Convolve(obj1.withGSParams(gsp), galsim.FourierSqrt(obj2)) conv4 = galsim.Convolve(obj1, galsim.FourierSqrt(obj2, gsparams=gsp)) assert conv != conv1 assert conv1 == conv2 assert conv1 == conv3 assert conv1 == conv4 print('stepk = ',conv.stepk, conv1.stepk) assert conv1.stepk < conv.stepk print('maxk = ',conv.maxk, conv1.maxk) assert conv1.maxk > conv.maxk conv5 = galsim.Convolve(obj1, galsim.FourierSqrt(obj2, gsparams=gsp, propagate_gsparams=False)) assert conv5 != conv4 assert conv5.gsparams == gsp assert conv5.obj_list[0].gsparams == gsp assert conv5.obj_list[1].gsparams == gsp assert conv5.obj_list[1].orig_obj.gsparams == galsim.GSParams() conv6 = conv5.withGSParams(gsp2) assert conv6 != conv5 assert conv6.gsparams == gsp2 assert conv6.obj_list[0].gsparams == gsp2 assert conv6.obj_list[1].gsparams == gsp2 assert conv6.obj_list[1].orig_obj.gsparams == galsim.GSParams()
def test_hsmparams_nodefault(): """Test that when non-default hsmparams are used, the results change.""" import time t1 = time.time() # First make some profile bulge = galsim.DeVaucouleurs(half_light_radius = 0.3) disk = galsim.Exponential(half_light_radius = 0.5) disk.applyShear(e1=0.2, e2=-0.3) psf = galsim.Kolmogorov(fwhm = 0.6) pix = galsim.Pixel(0.18) gal = bulge + disk # equal weighting, i.e., B/T=0.5 tot_gal = galsim.Convolve(gal, psf, pix) tot_psf = galsim.Convolve(psf, pix) tot_gal_image = tot_gal.draw(dx=0.18) tot_psf_image = tot_psf.draw(dx=0.18) # Check that recompute_flux changes give results that are as expected test_t = time.time() res = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image) dt = time.time() - test_t res2 = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image, recompute_flux = 'sum') assert(res.moments_amp < res2.moments_amp),'Incorrect behavior with recompute_flux=sum' res3 = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image, recompute_flux = 'none') assert(res3.moments_amp == 0),'Incorrect behavior with recompute_flux=none' # Check that results, timing change as expected with nsig_rg # For this, use Gaussian as galaxy and for ePSF, i.e., no extra pixel response p = galsim.Gaussian(fwhm=10.) g = galsim.Gaussian(fwhm=20.) g.applyShear(g1=0.5) obj = galsim.Convolve(g, p) im = obj.draw(dx=1.) psf_im = p.draw(dx=1.) test_t1 = time.time() g_res = galsim.hsm.EstimateShear(im, psf_im) test_t2 = time.time() g_res2 = galsim.hsm.EstimateShear(im, psf_im, hsmparams=galsim.hsm.HSMParams(nsig_rg=0.)) dt2 = time.time()-test_t2 dt1 = test_t2-test_t1 if test_timing: assert(dt2 > dt1),'Should take longer to estimate shear without truncation of galaxy' assert(not equal_hsmshapedata(g_res, g_res2)),'Results should differ with diff nsig_rg' # Check that results, timing change as expected with max_moment_nsig2 test_t2 = time.time() res2 = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image, hsmparams=galsim.hsm.HSMParams(max_moment_nsig2 = 9.)) dt2 = time.time() - test_t2 if test_timing: assert(dt2 < dt),'Should be faster to estimate shear with lower max_moment_nsig2' assert(not equal_hsmshapedata(res, res2)),'Outputs same despite change in max_moment_nsig2' assert(res.moments_sigma > res2.moments_sigma),'Sizes do not change as expected' assert(res.moments_amp > res2.moments_amp),'Amplitudes do not change as expected' # Check that max_amoment, max_ashift work as expected try: np.testing.assert_raises(RuntimeError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, hsmparams=galsim.hsm.HSMParams(max_amoment = 10.)) np.testing.assert_raises(RuntimeError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, guess_x_centroid=47., hsmparams=galsim.hsm.HSMParams(max_ashift=0.1)) except ImportError: print 'The assert_raises tests require nose' t2 = time.time() print 'time for %s = %.2f'%(funcname(),t2-t1)
def test_wavelength_sampler(): nphotons = 1000 obj = galsim.Exponential(flux=1.7, scale_radius=2.3) rng = galsim.UniformDeviate(1234) photon_array = obj.shoot(nphotons, rng) sed = galsim.SED(os.path.join(sedpath, 'CWW_E_ext.sed'), 'A', 'flambda').thin() bandpass = galsim.Bandpass(os.path.join(bppath, 'LSST_r.dat'), 'nm').thin() sampler = galsim.WavelengthSampler(sed, bandpass, rng) sampler.applyTo(photon_array) # Note: the underlying functionality of the sampleWavelengths function is tested # in test_sed.py. So here we are really just testing that the wrapper class is # properly writing to the photon_array.wavelengths array. assert photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() print('mean wavelength = ', np.mean(photon_array.wavelength)) print('min wavelength = ', np.min(photon_array.wavelength)) print('max wavelength = ', np.max(photon_array.wavelength)) assert np.min(photon_array.wavelength) > bandpass.blue_limit assert np.max(photon_array.wavelength) < bandpass.red_limit # This is a regression test based on the value at commit 134a119 np.testing.assert_allclose(np.mean(photon_array.wavelength), 622.755128, rtol=1.e-4) # If we use a flat SED (in photons/nm), then the mean sampled wavelength should very closely # match the bandpass effective wavelength. photon_array2 = galsim.PhotonArray(100000) sed2 = galsim.SED('1', 'nm', 'fphotons') sampler2 = galsim.WavelengthSampler(sed2, bandpass, rng) sampler2.applyTo(photon_array2) np.testing.assert_allclose( np.mean(photon_array2.wavelength), bandpass.effective_wavelength, rtol=0, atol=0.2, # 2 Angstrom accuracy is pretty good err_msg="Mean sampled wavelength not close to effective_wavelength") # Test that using this as a surface op works properly. # First do the shooting and clipping manually. im1 = galsim.Image(64, 64, scale=1) im1.setCenter(0, 0) photon_array.flux[photon_array.wavelength < 600] = 0. photon_array.addTo(im1) # Make a dummy surface op that clips any photons with lambda < 600 class Clip600(object): def applyTo(self, photon_array, local_wcs=None): photon_array.flux[photon_array.wavelength < 600] = 0. # Use (a new) sampler and clip600 as surface_ops in drawImage im2 = galsim.Image(64, 64, scale=1) im2.setCenter(0, 0) clip600 = Clip600() rng2 = galsim.BaseDeviate(1234) sampler2 = galsim.WavelengthSampler(sed, bandpass, rng2) obj.drawImage(im2, method='phot', n_photons=nphotons, use_true_center=False, surface_ops=[sampler2, clip600], rng=rng2) print('sum = ', im1.array.sum(), im2.array.sum()) np.testing.assert_array_equal(im1.array, im2.array)
def main(argv): """ Make images similar to that done for the Great08 challenge: - Each fits file is 10 x 10 postage stamps. (The real Great08 images are 100x100, but in the interest of making the Demo script a bit quicker, we only build 100 stars and 100 galaxies.) - Each postage stamp is 40 x 40 pixels. - One image is all stars. - A second image is all galaxies. - Applied shear is the same for each galaxy. - Galaxies are oriented randomly, but in pairs to cancel shape noise. - Noise is Poisson using a nominal sky value of 1.e6. - Galaxies are Exponential profiles. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo5") # Define some parameters we'll use below. # Normally these would be read in from some parameter file. nx_tiles = 10 # ny_tiles = 10 # stamp_xsize = 40 # stamp_ysize = 40 # random_seed = 6424512 # pixel_scale = 1.0 # arcsec / pixel sky_level = 1.e6 # ADU / arcsec^2 # Make output directory if not already present. if not os.path.isdir('output'): os.mkdir('output') psf_file_name = os.path.join('output', 'g08_psf.fits') psf_beta = 3 # psf_fwhm = 2.85 # arcsec (=pixels) psf_trunc = 2. * psf_fwhm # arcsec (=pixels) psf_e1 = -0.019 # psf_e2 = -0.007 # gal_file_name = os.path.join('output', 'g08_gal.fits') gal_signal_to_noise = 200 # Great08 "LowNoise" run gal_resolution = 0.98 # r_gal / r_psf (use r = half_light_radius) # Note: Great08 defined their resolution as r_obs / r_psf, using the convolved # size rather than the pre-convolved size. # Therefore, our r_gal/r_psf = 0.98 approximately corresponds to # their r_obs / r_psf = 1.4. gal_ellip_rms = 0.2 # using "distortion" definition of ellipticity: # e = (a^2-b^2)/(a^2+b^2), where a and b are the # semi-major and semi-minor axes, respectively. gal_ellip_max = 0.6 # Maximum value of e, to avoid getting near e=1. gal_g1 = 0.013 # Applied shear, using normal shear definition: gal_g2 = -0.008 # g = (a-b)/(a+b) shift_radius = 1.0 # arcsec (=pixels) logger.info('Starting demo script 5 using:') logger.info(' - image with %d x %d postage stamps', nx_tiles, ny_tiles) logger.info(' - postage stamps of size %d x %d pixels', stamp_xsize, stamp_ysize) logger.info(' - Moffat PSF (beta = %.1f, FWHM = %.2f, trunc = %.2f),', psf_beta, psf_fwhm, psf_trunc) logger.info(' - PSF ellip = (%.3f,%.3f)', psf_e1, psf_e2) logger.info(' - Exponential galaxies') logger.info(' - Resolution (r_gal / r_psf) = %.2f', gal_resolution) logger.info(' - Ellipticities have rms = %.1f, max = %.1f', gal_ellip_rms, gal_ellip_max) logger.info(' - Applied gravitational shear = (%.3f,%.3f)', gal_g1, gal_g2) logger.info(' - Poisson noise (sky level = %.1e).', sky_level) logger.info(' - Centroid shifts up to = %.2f pixels', shift_radius) # Define the PSF profile psf = galsim.Moffat(beta=psf_beta, fwhm=psf_fwhm, trunc=psf_trunc) # When something can be constructed from multiple sizes, e.g. Moffat, then # you can get any size out even if it wasn't the way the object was constructed. # In this case, we extract the half-light radius, even though we built it with fwhm. # We'll use this later to set the galaxy's half-light radius in terms of a resolution. psf_re = psf.getHalfLightRadius() psf = psf.shear(e1=psf_e1, e2=psf_e2) logger.debug('Made PSF profile') # Define the galaxy profile # First figure out the size we need from the resolution gal_re = psf_re * gal_resolution # Make the galaxy profile starting with flux = 1. gal = galsim.Exponential(flux=1., half_light_radius=gal_re) logger.debug('Made galaxy profile') # This profile is placed with different orientations and noise realizations # at each postage stamp in the gal image. gal_image = galsim.ImageF(stamp_xsize * nx_tiles - 1, stamp_ysize * ny_tiles - 1, scale=pixel_scale) psf_image = galsim.ImageF(stamp_xsize * nx_tiles - 1, stamp_ysize * ny_tiles - 1, scale=pixel_scale) shift_radius_sq = shift_radius**2 first_in_pair = True # Make pairs that are rotated by 90 degrees k = 0 for iy in range(ny_tiles): for ix in range(nx_tiles): # The normal procedure for setting random numbers in GalSim is to start a new # random number generator for each object using sequential seed values. # This sounds weird at first (especially if you were indoctrinated by Numerical # Recipes), but for the boost random number generator we use, the "random" # number sequences produced from sequential initial seeds are highly uncorrelated. # # The reason for this procedure is that when we use multiple processes to build # our images, we want to make sure that the results are deterministic regardless # of the way the objects get parcelled out to the different processes. # # Of course, this script isn't using multiple processes, so it isn't required here. # However, we do it nonetheless in order to get the same results as the config # version of this demo script (demo5.yaml). ud = galsim.UniformDeviate(random_seed + k) # Any kind of random number generator can take another RNG as its first # argument rather than a seed value. This makes both objects use the same # underlying generator for their pseudo-random values. gd = galsim.GaussianDeviate(ud, sigma=gal_ellip_rms) # The -1's in the next line are to provide a border of # 1 pixel between postage stamps b = galsim.BoundsI(ix * stamp_xsize + 1, (ix + 1) * stamp_xsize - 1, iy * stamp_ysize + 1, (iy + 1) * stamp_ysize - 1) sub_gal_image = gal_image[b] sub_psf_image = psf_image[b] # Great08 randomized the locations of the two galaxies in each pair, # but for simplicity, we just do them in sequential postage stamps. if first_in_pair: # Use a random orientation: beta = ud() * 2. * math.pi * galsim.radians # Determine the ellipticity to use for this galaxy. ellip = 1 while (ellip > gal_ellip_max): # Don't do `ellip = math.fabs(gd())` # Python basically implements this as a macro, so gd() is called twice! val = gd() ellip = math.fabs(val) first_in_pair = False else: # Use the previous ellip and beta + 90 degrees beta += 90 * galsim.degrees first_in_pair = True # Make a new copy of the galaxy with an applied e1/e2-type distortion # by specifying the ellipticity and a real-space position angle this_gal = gal.shear(e=ellip, beta=beta) # Apply the gravitational reduced shear by specifying g1/g2 this_gal = this_gal.shear(g1=gal_g1, g2=gal_g2) # Apply a random shift_radius: rsq = 2 * shift_radius_sq while (rsq > shift_radius_sq): dx = (2 * ud() - 1) * shift_radius dy = (2 * ud() - 1) * shift_radius rsq = dx**2 + dy**2 this_gal = this_gal.shift(dx, dy) # Note that the shifted psf that we create here is purely for the purpose of being able # to draw a separate, shifted psf image. We do not use it when convolving the galaxy # with the psf. this_psf = psf.shift(dx, dy) # Make the final image, convolving with the (unshifted) psf final_gal = galsim.Convolve([psf, this_gal]) # Draw the image final_gal.drawImage(sub_gal_image) # Now add an appropriate amount of noise to get our desired S/N # There are lots of definitions of S/N, but here is the one used by Great08 # We use a weighted integral of the flux: # S = sum W(x,y) I(x,y) / sum W(x,y) # N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2 # Now we assume that Var(I(x,y)) is constant so # Var(I(x,y)) = noise_var # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y). # Then a few things cancel and we find that # S/N = sqrt( sum I(x,y)^2 / noise_var ) # # The above procedure is encapsulated in the function image.addNoiseSNR which # sets the flux appropriately given the variance of the noise model. # In our case, noise_var = sky_level_pixel sky_level_pixel = sky_level * pixel_scale**2 noise = galsim.PoissonNoise(ud, sky_level=sky_level_pixel) sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise) # Draw the PSF image # No noise on PSF images. Just draw it as is. this_psf.drawImage(sub_psf_image) # For first instance, measure moments if ix == 0 and iy == 0: psf_shape = sub_psf_image.FindAdaptiveMom() temp_e = psf_shape.observed_shape.e if temp_e > 0.0: g_to_e = psf_shape.observed_shape.g / temp_e else: g_to_e = 0.0 logger.info( 'Measured best-fit elliptical Gaussian for first PSF image: ' ) logger.info(' g1, g2, sigma = %7.4f, %7.4f, %7.4f (pixels)', g_to_e * psf_shape.observed_shape.e1, g_to_e * psf_shape.observed_shape.e2, psf_shape.moments_sigma) x = b.center().x y = b.center().y logger.info( 'Galaxy (%d,%d): center = (%.0f,%0.f) (e,beta) = (%.4f,%.3f)', ix, iy, x, y, ellip, beta / galsim.radians) k = k + 1 logger.info('Done making images of postage stamps') # Now write the images to disk. psf_image.write(psf_file_name) logger.info('Wrote PSF file %s', psf_file_name) gal_image.write(gal_file_name) logger.info('Wrote image to %r', gal_file_name) # using %r adds quotes around filename for us
def test_photon_array(): """Test the basic methods of PhotonArray class """ nphotons = 1000 # First create from scratch photon_array = galsim.PhotonArray(nphotons) assert len(photon_array.x) == nphotons assert len(photon_array.y) == nphotons assert len(photon_array.flux) == nphotons assert not photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() # Initial values should all be 0 np.testing.assert_array_equal(photon_array.x, 0.) np.testing.assert_array_equal(photon_array.y, 0.) np.testing.assert_array_equal(photon_array.flux, 0.) # Check picklability do_pickle(photon_array) # Check assignment via numpy [:] photon_array.x[:] = 5 photon_array.y[:] = 17 photon_array.flux[:] = 23 np.testing.assert_array_equal(photon_array.x, 5.) np.testing.assert_array_equal(photon_array.y, 17.) np.testing.assert_array_equal(photon_array.flux, 23.) # Check assignment directly to the attributes photon_array.x = 25 photon_array.y = 37 photon_array.flux = 53 np.testing.assert_array_equal(photon_array.x, 25.) np.testing.assert_array_equal(photon_array.y, 37.) np.testing.assert_array_equal(photon_array.flux, 53.) # Now create from shooting a profile obj = galsim.Exponential(flux=1.7, scale_radius=2.3) rng = galsim.UniformDeviate(1234) photon_array = obj.shoot(nphotons, rng) orig_x = photon_array.x.copy() orig_y = photon_array.y.copy() orig_flux = photon_array.flux.copy() assert len(photon_array.x) == nphotons assert len(photon_array.y) == nphotons assert len(photon_array.flux) == nphotons assert not photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() # Check arithmetic ops photon_array.x *= 5 photon_array.y += 17 photon_array.flux /= 23 np.testing.assert_almost_equal(photon_array.x, orig_x * 5.) np.testing.assert_almost_equal(photon_array.y, orig_y + 17.) np.testing.assert_almost_equal(photon_array.flux, orig_flux / 23.) # Check picklability again with non-zero values do_pickle(photon_array) # Now assign to the optional arrays photon_array.dxdz = 0.17 assert photon_array.hasAllocatedAngles() assert not photon_array.hasAllocatedWavelengths() np.testing.assert_array_equal(photon_array.dxdz, 0.17) np.testing.assert_array_equal(photon_array.dydz, 0.) photon_array.dydz = 0.59 np.testing.assert_array_equal(photon_array.dxdz, 0.17) np.testing.assert_array_equal(photon_array.dydz, 0.59) # Start over to check that assigning to wavelength leaves dxdz, dydz alone. photon_array = obj.shoot(nphotons, rng) photon_array.wavelength = 500. assert photon_array.hasAllocatedWavelengths() assert not photon_array.hasAllocatedAngles() np.testing.assert_array_equal(photon_array.wavelength, 500) photon_array.dxdz = 0.23 photon_array.dydz = 0.88 photon_array.wavelength = 912. assert photon_array.hasAllocatedWavelengths() assert photon_array.hasAllocatedAngles() np.testing.assert_array_equal(photon_array.dxdz, 0.23) np.testing.assert_array_equal(photon_array.dydz, 0.88) np.testing.assert_array_equal(photon_array.wavelength, 912) # Check toggling is_corr assert not photon_array.isCorrelated() photon_array.setCorrelated() assert photon_array.isCorrelated() photon_array.setCorrelated(False) assert not photon_array.isCorrelated() photon_array.setCorrelated(True) assert photon_array.isCorrelated() # Check rescaling the total flux flux = photon_array.flux.sum() np.testing.assert_almost_equal(photon_array.getTotalFlux(), flux) photon_array.scaleFlux(17) np.testing.assert_almost_equal(photon_array.getTotalFlux(), 17 * flux) photon_array.setTotalFlux(199) np.testing.assert_almost_equal(photon_array.getTotalFlux(), 199) # Check rescaling the positions x = photon_array.x.copy() y = photon_array.y.copy() photon_array.scaleXY(1.9) np.testing.assert_almost_equal(photon_array.x, 1.9 * x) np.testing.assert_almost_equal(photon_array.y, 1.9 * y) # Check ways to assign to photons pa1 = galsim.PhotonArray(50) pa1.x = photon_array.x[:50] for i in range(50): pa1.y[i] = photon_array.y[i] pa1.flux[0:50] = photon_array.flux[:50] pa1.dxdz = photon_array.dxdz[:50] pa1.dydz = photon_array.dydz[:50] pa1.wavelength = photon_array.wavelength[:50] np.testing.assert_almost_equal(pa1.x, photon_array.x[:50]) np.testing.assert_almost_equal(pa1.y, photon_array.y[:50]) np.testing.assert_almost_equal(pa1.flux, photon_array.flux[:50]) np.testing.assert_almost_equal(pa1.dxdz, photon_array.dxdz[:50]) np.testing.assert_almost_equal(pa1.dydz, photon_array.dydz[:50]) np.testing.assert_almost_equal(pa1.wavelength, photon_array.wavelength[:50]) # Check assignAt pa2 = galsim.PhotonArray(100) pa2.assignAt(0, pa1) pa2.assignAt(50, pa1) np.testing.assert_almost_equal(pa2.x[:50], pa1.x) np.testing.assert_almost_equal(pa2.y[:50], pa1.y) np.testing.assert_almost_equal(pa2.flux[:50], pa1.flux) np.testing.assert_almost_equal(pa2.dxdz[:50], pa1.dxdz) np.testing.assert_almost_equal(pa2.dydz[:50], pa1.dydz) np.testing.assert_almost_equal(pa2.wavelength[:50], pa1.wavelength) np.testing.assert_almost_equal(pa2.x[50:], pa1.x) np.testing.assert_almost_equal(pa2.y[50:], pa1.y) np.testing.assert_almost_equal(pa2.flux[50:], pa1.flux) np.testing.assert_almost_equal(pa2.dxdz[50:], pa1.dxdz) np.testing.assert_almost_equal(pa2.dydz[50:], pa1.dydz) np.testing.assert_almost_equal(pa2.wavelength[50:], pa1.wavelength) # Error if it doesn't fit. assert_raises(ValueError, pa2.assignAt, 90, pa1) # Test some trivial usage of makeFromImage zero = galsim.Image(4, 4, init_value=0) photons = galsim.PhotonArray.makeFromImage(zero) print('photons = ', photons) assert len(photons) == 16 np.testing.assert_array_equal(photons.flux, 0.) ones = galsim.Image(4, 4, init_value=1) photons = galsim.PhotonArray.makeFromImage(ones) print('photons = ', photons) assert len(photons) == 16 np.testing.assert_almost_equal(photons.flux, 1.) tens = galsim.Image(4, 4, init_value=8) photons = galsim.PhotonArray.makeFromImage(tens, max_flux=5.) print('photons = ', photons) assert len(photons) == 32 np.testing.assert_almost_equal(photons.flux, 4.) assert_raises(ValueError, galsim.PhotonArray.makeFromImage, zero, max_flux=0.) assert_raises(ValueError, galsim.PhotonArray.makeFromImage, zero, max_flux=-2) # Check some other errors undef = galsim.Image() assert_raises(galsim.GalSimUndefinedBoundsError, pa2.addTo, undef) # Check picklability again with non-zero values for everything do_pickle(photon_array)
def test_add_flux_scaling(): """Test flux scaling for Add. """ import time t1 = time.time() # decimal point to go to for parameter value comparisons param_decimal = 12 # init with Gaussian and Exponential only (should be ok given last tests) obj = galsim.Add([ galsim.Gaussian(sigma=test_sigma, flux=test_flux * .5), galsim.Exponential(scale_radius=test_scale, flux=test_flux * .5) ]) obj *= 2. np.testing.assert_almost_equal( obj.getFlux(), test_flux * 2., decimal=param_decimal, err_msg="Flux param inconsistent after __imul__.") obj = galsim.Add([ galsim.Gaussian(sigma=test_sigma, flux=test_flux * .5), galsim.Exponential(scale_radius=test_scale, flux=test_flux * .5) ]) obj /= 2. np.testing.assert_almost_equal( obj.getFlux(), test_flux / 2., decimal=param_decimal, err_msg="Flux param inconsistent after __idiv__.") obj = galsim.Add([ galsim.Gaussian(sigma=test_sigma, flux=test_flux * .5), galsim.Exponential(scale_radius=test_scale, flux=test_flux * .5) ]) 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.Add([ galsim.Gaussian(sigma=test_sigma, flux=test_flux * .5), galsim.Exponential(scale_radius=test_scale, flux=test_flux * .5) ]) 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.Add([ galsim.Gaussian(sigma=test_sigma, flux=test_flux * .5), galsim.Exponential(scale_radius=test_scale, flux=test_flux * .5) ]) 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 main(argv): """ Make a fits image cube using parameters from an input catalog - The number of images in the cube matches the number of rows in the catalog. - Each image size is computed automatically by GalSim based on the Nyquist size. - Only galaxies. No stars. - PSF is Moffat - Each galaxy is bulge plus disk: deVaucouleurs + Exponential. - The catalog's columns are: 0 PSF beta (Moffat exponent) 1 PSF FWHM 2 PSF e1 3 PSF e2 4 PSF trunc 5 Disc half-light-radius 6 Disc e1 7 Disc e2 8 Bulge half-light-radius 9 Bulge e1 10 Bulge e2 11 Galaxy dx (the two components have same center) 12 Galaxy dy - Applied shear is the same for each galaxy - Noise is Poisson using a nominal sky value of 1.e6 """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo4") # Define some parameters we'll use below and make directories if needed. cat_file_name = os.path.join('input', 'galsim_default_input.asc') if not os.path.isdir('output'): os.mkdir('output') multi_file_name = os.path.join('output', 'multi.fits') random_seed = 8241573 sky_level = 1.e6 # ADU / arcsec^2 pixel_scale = 1.0 # arcsec / pixel (size units in input catalog are pixels) gal_flux = 1.e6 # arbitrary choice, makes nice (not too) noisy images gal_g1 = -0.009 # gal_g2 = 0.011 # xsize = 64 # pixels ysize = 64 # pixels logger.info('Starting demo script 4 using:') logger.info(' - parameters taken from catalog %r', cat_file_name) logger.info(' - Moffat PSF (parameters from catalog)') logger.info(' - pixel scale = %.2f', pixel_scale) logger.info(' - Bulge + Disc galaxies (parameters from catalog)') logger.info(' - Applied gravitational shear = (%.3f,%.3f)', gal_g1, gal_g2) logger.info(' - Poisson noise (sky level = %.1e).', sky_level) # Read in the input catalog cat = galsim.Catalog(cat_file_name) # save a list of the galaxy images in the "images" list variable: images = [] for k in range(cat.nobjects): # Initialize the (pseudo-)random number generator that we will be using below. # Use a different random seed for each object to get different noise realizations. # Using sequential random seeds here is safer than it sounds. We use Mersenne Twister # random number generators that are designed to be used with this kind of seeding. # However, to be extra safe, we actually initialize one random number generator with this # seed, generate and throw away two random values with that, and then use the next value # to seed a completely different Mersenne Twister RNG. The result is that successive # RNGs created this way produce very independent random number streams. rng = galsim.BaseDeviate(random_seed + k + 1) # Take the Moffat beta from the first column (called 0) of the input catalog: # Note: cat.get(k,col) returns a string. To get the value as a float, use either # cat.getFloat(k,col) or float(cat.get(k,col)) beta = cat.getFloat(k, 0) # A Moffat's size may be either scale_radius, fwhm, or half_light_radius. # Here we use fwhm, taking from the catalog as well. fwhm = cat.getFloat(k, 1) # A Moffat profile may be truncated if desired # The units for this are expected to be arcsec (or specifically -- whatever units # you are using for all the size values as defined by the pixel_scale). trunc = cat.getFloat(k, 4) # Note: You may omit the flux, since the default is flux=1. psf = galsim.Moffat(beta=beta, fwhm=fwhm, trunc=trunc) # Take the (e1, e2) shape parameters from the catalog as well. psf = psf.shear(e1=cat.getFloat(k, 2), e2=cat.getFloat(k, 3)) # Galaxy is a bulge + disk with parameters taken from the catalog: disk = galsim.Exponential(flux=0.6, half_light_radius=cat.getFloat(k, 5)) disk = disk.shear(e1=cat.getFloat(k, 6), e2=cat.getFloat(k, 7)) bulge = galsim.DeVaucouleurs(flux=0.4, half_light_radius=cat.getFloat(k, 8)) bulge = bulge.shear(e1=cat.getFloat(k, 9), e2=cat.getFloat(k, 10)) # The flux of an Add object is the sum of the component fluxes. # Note that in demo3.py, a similar addition was performed by the binary operator "+". gal = galsim.Add([disk, bulge]) # This flux may be overridden by withFlux. The relative fluxes of the components # remains the same, but the total flux is set to gal_flux. gal = gal.withFlux(gal_flux) gal = gal.shear(g1=gal_g1, g2=gal_g2) # The center of the object is normally placed at the center of the postage stamp image. # You can change that with shift: gal = gal.shift(dx=cat.getFloat(k, 11), dy=cat.getFloat(k, 12)) final = galsim.Convolve([psf, gal]) # Draw the profile image = galsim.ImageF(xsize, ysize) final.drawImage(image, scale=pixel_scale) # Add Poisson noise to the image: image.addNoise(galsim.PoissonNoise(rng, sky_level * pixel_scale**2)) logger.info('Drew image for object at row %d in the input catalog' % k) # Add the image to our list of images images.append(image) # Now write the images to a multi-extension fits file. Each image will be in its own HDU. galsim.fits.writeMulti(images, multi_file_name) logger.info('Images written to multi-extension fits file %r', multi_file_name)
def test_hlr(): """Test the calculateHLR method. """ import time t1 = time.time() # Compare the calculation for a simple Gaussian. g1 = galsim.Gaussian(sigma=5, flux=1.7) print 'g1 native hlr = ',g1.half_light_radius print 'g1.calculateHLR = ',g1.calculateHLR() print 'nyquist scale = ',g1.nyquistScale() # These should be exactly equal. np.testing.assert_equal(g1.half_light_radius, g1.calculateHLR(), err_msg="Gaussian.calculateHLR() returned wrong value.") # Check for a convolution of two Gaussians. Should be equivalent, but now will need to # do the calculation. g2 = galsim.Convolve(galsim.Gaussian(sigma=3, flux=1.3), galsim.Gaussian(sigma=4, flux=23)) test_hlr = g2.calculateHLR() print 'g2.calculateHLR = ',test_hlr print 'ratio - 1 = ',test_hlr/g1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/g1.half_light_radius, 1.0, decimal=1, err_msg="Gaussian.calculateHLR() is not accurate.") # The default scale is only accurate to around 1 dp. Using scale = 0.1 is accurate to 3 dp. # Note: Nyquist scale is about 4.23 for this profile. test_hlr = g2.calculateHLR(scale=0.1) print 'g2.calculateHLR(scale=0.1) = ',test_hlr print 'ratio - 1 = ',test_hlr/g1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/g1.half_light_radius, 1.0, decimal=3, err_msg="Gaussian.calculateHLR(scale=0.1) is not accurate.") # Finally, we don't expect this to be accurate, but make sure the code can handle having # more than half the flux in the central pixel. test_hlr = g2.calculateHLR(scale=15) print 'g2.calculateHLR(scale=15) = ',test_hlr print 'ratio - 1 = ',test_hlr/g1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/g1.half_light_radius/10, 0.1, decimal=1, err_msg="Gaussian.calculateHLR(scale=15) is not accurate.") # Next, use an Exponential profile e1 = galsim.Exponential(scale_radius=5, flux=1.7) print 'e1 native hlr = ',e1.half_light_radius print 'e1.calculateHLR = ',e1.calculateHLR() print 'nyquist scale = ',e1.nyquistScale() # These should be exactly equal. np.testing.assert_equal(e1.half_light_radius, e1.calculateHLR(), err_msg="Exponential.calculateHLR() returned wrong value.") # Check for a convolution with a delta function. Should be equivalent, but now will need to # do the calculation. e2 = galsim.Convolve(galsim.Exponential(scale_radius=5, flux=1.3), galsim.Gaussian(sigma=1.e-4, flux=23)) test_hlr = e2.calculateHLR() print 'e2.calculateHLR = ',test_hlr print 'ratio - 1 = ',test_hlr/e1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=1, err_msg="Exponential.calculateHLR() is not accurate.") # The default scale is only accurate to around 1 dp. Using scale = 0.1 is accurate to 3 dp. # Note: Nyquist scale is about 1.57 for this profile. # We can also decrease the size, which should still be accurate, but maybe a little faster. # Go a bit more that 2*hlr in units of the pixels. size = int(2.2 * e1.half_light_radius / 0.1) test_hlr = e2.calculateHLR(scale=0.1, size=size) print 'e2.calculateHLR(scale=0.1) = ',test_hlr print 'ratio - 1 = ',test_hlr/e1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=3, err_msg="Exponential.calculateHLR(scale=0.1) is not accurate.") # Check that it works if the centroid is not at the origin e3 = e2.shift(2,3) test_hlr = e3.calculateHLR(scale=0.1) print 'e3.calculateHLR(scale=0.1) = ',test_hlr print 'ratio - 1 = ',test_hlr/e1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=3, err_msg="shifted Exponential HLR is not accurate.") # Can set a centroid manually. This should be equivalent to the default. print 'e3.centroid = ',e3.centroid() test_hlr = e3.calculateHLR(scale=0.1, centroid=e3.centroid()) np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=3, err_msg="shifted HLR with explicit centroid is not accurate.") # The calculateHLR method can also return other radii like r90, rather than r50 using the # parameter flux_fraction. This is also analytic for Exponential r90 = 3.889720170 * e1.scale_radius test_r90 = e2.calculateHLR(scale=0.1, flux_frac=0.9) print 'r90 = ',r90 print 'e2.calculateHLR(scale=0.1, flux_frac=0.9) = ',test_r90 print 'ratio - 1 = ',test_r90/r90-1 np.testing.assert_almost_equal(test_r90/r90, 1.0, decimal=3, err_msg="Exponential r90 calculation is not accurate.") # Check the image version. im = e1.drawImage(scale=0.1) test_hlr = im.calculateHLR(flux=e1.flux) print 'im.calculateHLR() = ',test_hlr print 'ratio - 1 = ',test_hlr/e1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=3, err_msg="image.calculateHLR is not accurate.") # Check that a non-square image works correctly. Also, not centered anywhere in particular. #bounds = galsim.BoundsI(-1234, -1234+size*2, 8234, 8234+size) bounds = galsim.BoundsI(1, 1+size*2, 1, 1+size) #offset = galsim.PositionD(29,1) offset = galsim.PositionD(0,0) im = e1.drawImage(scale=0.1, bounds=bounds, offset=offset) test_hlr = im.calculateHLR(flux=e1.flux, center=im.trueCenter()+offset) print 'im.calculateHLR() = ',test_hlr print 'ratio - 1 = ',test_hlr/e1.half_light_radius-1 np.testing.assert_almost_equal(test_hlr/e1.half_light_radius, 1.0, decimal=3, err_msg="non-square image.calculateHLR is not accurate.") t2 = time.time() print 'time for %s = %.2f'%(funcname(),t2-t1)
def test_hsmparams_nodefault(): """Test that when non-default hsmparams are used, the results change.""" import time # First make some profile bulge = galsim.DeVaucouleurs(half_light_radius=0.3) disk = galsim.Exponential(half_light_radius=0.5) disk = disk.shear(e1=0.2, e2=-0.3) psf = galsim.Kolmogorov(fwhm=0.6) gal = bulge + disk # equal weighting, i.e., B/T=0.5 tot_gal = galsim.Convolve(gal, psf) tot_gal_image = tot_gal.drawImage(scale=0.18) tot_psf_image = psf.drawImage(scale=0.18) # Check that recompute_flux changes give results that are as expected test_t = time.time() res = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image) dt = time.time() - test_t res2 = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image, recompute_flux='sum') assert (res.moments_amp < res2.moments_amp), 'Incorrect behavior with recompute_flux=sum' res3 = galsim.hsm.EstimateShear(tot_gal_image, tot_psf_image, recompute_flux='none') assert ( res3.moments_amp == 0), 'Incorrect behavior with recompute_flux=none' # Check that results, timing change as expected with nsig_rg # For this, use Gaussian as galaxy and for ePSF, i.e., no extra pixel response p = galsim.Gaussian(fwhm=10.) g = galsim.Gaussian(fwhm=20.) g = g.shear(g1=0.5) obj = galsim.Convolve(g, p) # HSM allows a slop of 1.e-8 on nsig_rg, which means that default float32 images don't # actually end up with different result when using nsig_rg=0. rather than 3. im = obj.drawImage(scale=1., method='no_pixel', dtype=float) psf_im = p.drawImage(scale=1., method='no_pixel', dtype=float) test_t1 = time.time() g_res = galsim.hsm.EstimateShear(im, psf_im) test_t2 = time.time() g_res2 = galsim.hsm.EstimateShear( im, psf_im, hsmparams=galsim.hsm.HSMParams(nsig_rg=0.)) dt2 = time.time() - test_t2 dt1 = test_t2 - test_t1 if test_timing: assert ( dt2 > dt1 ), 'Should take longer to estimate shear without truncation of galaxy' assert (not equal_hsmshapedata( g_res, g_res2)), 'Results should differ with diff nsig_rg' assert g_res != g_res2, 'Results should differ with diff nsig_rg' # Check that results, timing change as expected with max_moment_nsig2 test_t2 = time.time() res2 = galsim.hsm.EstimateShear( tot_gal_image, tot_psf_image, hsmparams=galsim.hsm.HSMParams(max_moment_nsig2=9.)) dt2 = time.time() - test_t2 if test_timing: assert ( dt2 < dt ), 'Should be faster to estimate shear with lower max_moment_nsig2' assert (not equal_hsmshapedata( res, res2)), 'Outputs same despite change in max_moment_nsig2' assert res != res2, 'Outputs same despite change in max_moment_nsig2' assert (res.moments_sigma > res2.moments_sigma), 'Sizes do not change as expected' assert (res.moments_amp > res2.moments_amp), 'Amplitudes do not change as expected' # Check that max_amoment, max_ashift work as expected assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, hsmparams=galsim.hsm.HSMParams(max_amoment=10.)) assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, guess_centroid=galsim.PositionD(47., tot_gal_image.true_center.y), hsmparams=galsim.hsm.HSMParams(max_ashift=0.1))