def time_silicon_accumulate(): nx = 1000 ny = 1000 nobj = 1000 photons_per_obj = 10000 flux_per_photon = 1 rng = galsim.UniformDeviate(314159) sensor = galsim.SiliconSensor(rng=rng.duplicate(), diffusion_factor=0.0) im = galsim.ImageF(nx, ny) num_photons = nobj * photons_per_obj photons = galsim.PhotonArray(num_photons) rng.generate(photons.x) photons.x *= nx photons.x += 0.5 rng.generate(photons.y) photons.y *= ny photons.y += 0.5 photons.flux = flux_per_photon t1 = time.time() sensor.accumulate(photons, im) t2 = time.time() print('Time = ', t2 - t1)
def get_photon_array(image, nphotons, rng): # Simpler method that has all the pixels with flux=1. # Might be too slow, in which case consider switching to the above code. photon_array = galsim.PhotonArray(int(nphotons)) # Generate the x,y values. rng.generate(photon_array.x) # 0..1 so far photon_array.x *= (image.xmax - image.xmin + 1) photon_array.x += image.xmin - 0.5 # Now from xmin-0.5 .. xmax+0.5 rng.generate(photon_array.y) photon_array.y *= (image.ymax - image.ymin + 1) photon_array.y += image.ymin - 0.5 # Flux in this case is simple. All flux = 1. photon_array.flux = 1 return photon_array
def get_bundled_photon_array(image, nphotons, nbundles_per_pix, rng): # A convenient way to do that is to have the fluxes of the # bundles be generated from a Poisson distribution. # Make a PhotonArray to hold the sky photons npix = np.prod(image.array.shape) nbundles = npix * nbundles_per_pix flux_per_bundle = np.float(nphotons) / nbundles #print('npix = ',npix) #print('nbundles = ',nbundles) #print('flux_per_bundle = ',flux_per_bundle) photon_array = galsim.PhotonArray(int(nbundles)) # Generate the x,y values. xx, yy = np.meshgrid(np.arange(image.xmin, image.xmax + 1), np.arange(image.ymin, image.ymax + 1)) xx = xx.ravel() yy = yy.ravel() assert len(xx) == npix assert len(yy) == npix xx = np.repeat(xx, nbundles_per_pix) yy = np.repeat(yy, nbundles_per_pix) # If the photon_array is smaller than xx and yy, # randomly select the corresponding number of xy values. if photon_array.size() < len(xx): index = (np.random.permutation(np.arange( len(xx)))[:photon_array.size()], ) xx = xx[index] yy = yy[index] assert len(xx) == photon_array.size() assert len(yy) == photon_array.size() galsim.random.permute(rng, xx, yy) # Randomly reshuffle in place # The above values are pixel centers. Add a random offset within each pixel. rng.generate(photon_array.x) # Random values from 0..1 photon_array.x -= 0.5 rng.generate(photon_array.y) photon_array.y -= 0.5 photon_array.x += xx photon_array.y += yy # Set the flux of the photons flux_pd = galsim.PoissonDeviate(rng, mean=flux_per_bundle) flux_pd.generate(photon_array.flux) return photon_array
def get_photon_array(self, image, nphotons): """ Generate an array of photons randomly distributed over the surface of the sensor. """ photon_array = galsim.PhotonArray(int(nphotons)) # Generate the x,y values. self.randomNumbers.generate(photon_array.x) # 0..1 so far photon_array.x *= (image.xmax - image.xmin + 1) photon_array.x += image.xmin - 0.5 # Now from xmin-0.5 .. xmax+0.5 self.randomNumbers.generate(photon_array.y) photon_array.y *= (image.ymax - image.ymin + 1) photon_array.y += image.ymin - 0.5 photon_array.flux = 1 return photon_array
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.SBProfile.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.SBProfile.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 test_resume(): """Test that the resume option for accumulate works properly. """ # Note: This test is based on a script devel/lsst/treering_skybg_check.py rng = galsim.UniformDeviate(314159) if __name__ == "__main__": flux_per_pixel = 40 nx = 200 ny = 200 block_size = int(1.2e5) nrecalc = 1.e6 else: flux_per_pixel = 40 nx = 20 ny = 20 block_size = int(1.3e3) nrecalc = 1.e4 expected_num_photons = nx * ny * flux_per_pixel pd = galsim.PoissonDeviate(rng, mean=expected_num_photons) num_photons = int(pd()) # Poisson realization of the given expected number of photons. #nrecalc = num_photons / 2 # Only recalc once. flux_per_photon = 1 print('num_photons = ',num_photons,' .. expected = ',expected_num_photons) # Use treerings to make sure that aspect of the setup is preserved properly on resume treering_func = galsim.SiliconSensor.simple_treerings(0.5, 250.) treering_center = galsim.PositionD(-1000,0) sensor1 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc, treering_func=treering_func, treering_center=treering_center) sensor2 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc, treering_func=treering_func, treering_center=treering_center) sensor3 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc, treering_func=treering_func, treering_center=treering_center) waves = galsim.WavelengthSampler(sed = galsim.SED('1', 'nm', 'fphotons'), bandpass = galsim.Bandpass('LSST_r.dat', 'nm'), rng=rng) angles = galsim.FRatioAngles(1.2, 0.4, rng) im1 = galsim.ImageF(nx,ny) # Will not use resume im2 = galsim.ImageF(nx,ny) # Will use resume im3 = galsim.ImageF(nx,ny) # Will run all photons in one pass t_resume = 0 t_no_resume = 0 all_photons = galsim.PhotonArray(num_photons) n_added = 0 first = True while num_photons > 0: print(num_photons,'photons left. image min/max =',im1.array.min(),im1.array.max()) nphot = min(block_size, num_photons) num_photons -= nphot t0 = time.time() photons = galsim.PhotonArray(int(nphot)) rng.generate(photons.x) # 0..1 so far photons.x *= nx photons.x += 0.5 # Now from xmin-0.5 .. xmax+0.5 rng.generate(photons.y) photons.y *= ny photons.y += 0.5 photons.flux = flux_per_photon waves.applyTo(photons) angles.applyTo(photons) all_photons.x[n_added:n_added+nphot] = photons.x all_photons.y[n_added:n_added+nphot] = photons.y all_photons.flux[n_added:n_added+nphot] = photons.flux all_photons.dxdz[n_added:n_added+nphot] = photons.dxdz all_photons.dydz[n_added:n_added+nphot] = photons.dydz all_photons.wavelength[n_added:n_added+nphot] = photons.wavelength n_added += nphot t1 = time.time() sensor1.accumulate(photons, im1) t2 = time.time() sensor2.accumulate(photons, im2, resume = not first) first = False t3 = time.time() print('Times = ',t1-t0,t2-t1,t3-t2) t_resume += t3-t2 t_no_resume += t2-t1 print('max diff = ',np.max(np.abs(im1.array - im2.array))) print('max rel diff = ',np.max(np.abs(im1.array - im2.array)/np.abs(im2.array))) np.testing.assert_almost_equal(im2.array/expected_num_photons, im1.array/expected_num_photons, decimal=5) print('Time with resume = ',t_resume) print('Time without resume = ',t_no_resume) assert t_resume < t_no_resume # The resume path should be exactly the same as doing all the photons at once. sensor3.accumulate(all_photons, im3) np.testing.assert_array_equal(im2.array, im3.array) # If resume is used either with the wrong image or on the first call to accumulate, then # this should raise an exception. assert_raises(RuntimeError, sensor3.accumulate, all_photons, im1, resume=True) sensor4 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc, treering_func=treering_func, treering_center=treering_center) assert_raises(RuntimeError, sensor4.accumulate, all_photons, im1, resume=True)
def test_silicon(): """Test the basic construction and use of the SiliconSensor class. """ # Note: Use something quite small in terms of npixels so the B/F effect kicks in without # requiring a ridiculous number of photons obj = galsim.Gaussian(flux=10000, sigma=0.3) # We'll draw the same object using SiliconSensor, Sensor, and the default (sensor=None) im1 = galsim.ImageD(64, 64, scale=0.3) # Will use sensor=silicon im2 = galsim.ImageD(64, 64, scale=0.3) # Will use sensor=simple im3 = galsim.ImageD(64, 64, scale=0.3) # Will use sensor=None rng1 = galsim.BaseDeviate(5678) rng2 = galsim.BaseDeviate(5678) rng3 = galsim.BaseDeviate(5678) silicon = galsim.SiliconSensor(rng=rng1, diffusion_factor=0.0) simple = galsim.Sensor() # Start with photon shooting, since that's more straightforward. obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1) obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2) obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3) # First, im2 and im3 should be exactly equal. np.testing.assert_array_equal(im2.array, im3.array) # im1 should be similar, but not equal np.testing.assert_almost_equal(im1.array/obj.flux, im2.array/obj.flux, decimal=2) # Now use a different seed for 3 to see how much of the variation is just from randomness. rng3.seed(234241) obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3) r1 = im1.calculateMomentRadius(flux=obj.flux) r2 = im2.calculateMomentRadius(flux=obj.flux) r3 = im3.calculateMomentRadius(flux=obj.flux) print('Flux = %.0f: sum peak radius'%obj.flux) print('im1: %.1f %.2f %f'%(im1.array.sum(),im1.array.max(), r1)) print('im2: %.1f %.2f %f'%(im2.array.sum(),im2.array.max(), r2)) print('im3: %.1f %.2f %f'%(im3.array.sum(),im3.array.max(), r3)) # Fluxes should all equal obj.flux np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6) # Sizes are all about equal since flux is not large enough for B/F to be significant # Variance of Irr for Gaussian with Poisson noise is # Var(Irr) = Sum(I r^4) = 4Irr [using Gaussian kurtosis = 8sigma^2, Irr = 2sigma^2] # r = sqrt(Irr/flux), so sigma(r) = 1/2 r sqrt(Var(Irr))/Irr = 1/sqrt(flux) # Use 2sigma for below checks. sigma_r = 1. / np.sqrt(obj.flux) * im1.scale np.testing.assert_allclose(r1, r2, atol=2.*sigma_r) np.testing.assert_allclose(r2, r3, atol=2.*sigma_r) # Repeat with 100X more photons where the brighter-fatter effect should kick in more. obj *= 100 rng1 = galsim.BaseDeviate(5678) rng2 = galsim.BaseDeviate(5678) rng3 = galsim.BaseDeviate(5678) obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1) obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2) obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3) r1 = im1.calculateMomentRadius(flux=obj.flux) r2 = im2.calculateMomentRadius(flux=obj.flux) r3 = im3.calculateMomentRadius(flux=obj.flux) print('Flux = %.0f: sum peak radius'%obj.flux) print('im1: %.1f %.2f %f'%(im1.array.sum(),im1.array.max(), r1)) print('im2: %.1f %.2f %f'%(im2.array.sum(),im2.array.max(), r2)) print('im3: %.1f %.2f %f'%(im3.array.sum(),im3.array.max(), r3)) # Fluxes should still be fine. np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6) # Sizes for 2,3 should be about equal, but 1 should be larger. sigma_r = 1. / np.sqrt(obj.flux) * im1.scale print('check |r2-r3| = %f <? %f'%(np.abs(r2-r3), 2.*sigma_r)) np.testing.assert_allclose(r2, r3, atol=2.*sigma_r) print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r)) assert r1 - r3 > 2*sigma_r # Check that it is really responding to flux, not number of photons. # Using fewer shot photons will mean each one encapsulates several electrons at once. obj.drawImage(im1, method='phot', n_photons=1000, poisson_flux=False, sensor=silicon, rng=rng1) obj.drawImage(im2, method='phot', n_photons=1000, poisson_flux=False, sensor=simple, rng=rng2) obj.drawImage(im3, method='phot', n_photons=1000, poisson_flux=False, rng=rng3) r1 = im1.calculateMomentRadius(flux=obj.flux) r2 = im2.calculateMomentRadius(flux=obj.flux) r3 = im3.calculateMomentRadius(flux=obj.flux) print('Flux = %.0f: sum peak radius'%obj.flux) print('im1: %.1f %.2f %f'%(im1.array.sum(),im1.array.max(), r1)) print('im2: %.1f %.2f %f'%(im2.array.sum(),im2.array.max(), r2)) print('im3: %.1f %.2f %f'%(im3.array.sum(),im3.array.max(), r3)) np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6) print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r)) assert r1 - r3 > 2*sigma_r # Can also get the stronger BF effect with the strength parameter. obj /= 100 # Back to what it originally was. rng1 = galsim.BaseDeviate(5678) rng2 = galsim.BaseDeviate(5678) rng3 = galsim.BaseDeviate(5678) silicon = galsim.SiliconSensor(name='lsst_itl_8', strength=100., rng=rng1, diffusion_factor=0.0) obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1) obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2) obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3) r1 = im1.calculateMomentRadius(flux=obj.flux) r2 = im2.calculateMomentRadius(flux=obj.flux) r3 = im3.calculateMomentRadius(flux=obj.flux) print('Flux = %.0f: sum peak radius'%obj.flux) print('im1: %.1f %.2f %f'%(im1.array.sum(),im1.array.max(), r1)) print('im2: %.1f %.2f %f'%(im2.array.sum(),im2.array.max(), r2)) print('im3: %.1f %.2f %f'%(im3.array.sum(),im3.array.max(), r3)) # Fluxes should still be fine. np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6) np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6) np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6) # Sizes for 2,3 should be about equal, but 1 should be larger. sigma_r = 1. / np.sqrt(obj.flux) * im1.scale print('check |r2-r3| = %f <? %f'%(np.abs(r2-r3), 2.*sigma_r)) np.testing.assert_allclose(r2, r3, atol=2.*sigma_r) print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r)) assert r1 - r3 > 2*sigma_r / 100 # Check the construction with an explicit name s0 = galsim.SiliconSensor(rng=rng1) name = os.path.join(galsim.meta_data.share_dir, 'sensors', 'lsst_itl_8') s1 = galsim.SiliconSensor(name=name, strength=1.0, rng=rng1, diffusion_factor=1.0, qdist=3, nrecalc=10000) assert s0 == s1 s1 = galsim.SiliconSensor(name, 1.0, rng1, 1.0, 3, 10000) assert s0 == s1 s2 = galsim.SiliconSensor(rng=rng1, name='lsst_itl_8') assert s0 == s2 s3 = galsim.SiliconSensor(rng=rng1, strength=10.) s4 = galsim.SiliconSensor(rng=rng1, diffusion_factor=2.0) s5 = galsim.SiliconSensor(rng=rng1, qdist=4) s6 = galsim.SiliconSensor(rng=rng1, nrecalc=12345) s7 = galsim.SiliconSensor(name=name, strength=1.5, rng=rng1, diffusion_factor=1.3, qdist=4, nrecalc=12345) for s in [ s3, s4, s5, s6, s7 ]: assert silicon != s assert s != s0 do_pickle(s0) do_pickle(s1) do_pickle(s7) assert_raises(OSError, galsim.SiliconSensor, name='junk') assert_raises(OSError, galsim.SiliconSensor, name='output') assert_raises(TypeError, galsim.SiliconSensor, rng=3.4) assert_raises(TypeError, galsim.SiliconSensor, 'lsst_itl_8', rng1) # Invalid to accumulate onto undefined image. photons = galsim.PhotonArray(3) image = galsim.ImageD() with assert_raises(galsim.GalSimUndefinedBoundsError): simple.accumulate(photons, image) with assert_raises(galsim.GalSimUndefinedBoundsError): silicon.accumulate(photons, image)
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_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 test_convolve(): nphotons = 1000000 obj = galsim.Gaussian(flux=1.7, sigma=2.3) rng = galsim.UniformDeviate(1234) pa1 = obj.shoot(nphotons, rng) pa2 = obj.shoot(nphotons, rng) # If not correlated then convolve is deterministic conv_x = pa1.x + pa2.x conv_y = pa1.y + pa2.y conv_flux = pa1.flux * pa2.flux * nphotons np.testing.assert_allclose(np.sum(pa1.flux), 1.7) np.testing.assert_allclose(np.sum(pa2.flux), 1.7) np.testing.assert_allclose(np.sum(conv_flux), 1.7 * 1.7) np.testing.assert_allclose(np.sum(pa1.x**2) / nphotons, 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(pa2.x**2) / nphotons, 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(conv_x**2) / nphotons, 2. * 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(pa1.y**2) / nphotons, 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(pa2.y**2) / nphotons, 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(conv_y**2) / nphotons, 2. * 2.3**2, rtol=0.01) pa3 = galsim.PhotonArray(nphotons) pa3.assignAt(0, pa1) # copy from pa1 pa3.convolve(pa2) np.testing.assert_allclose(pa3.x, conv_x) np.testing.assert_allclose(pa3.y, conv_y) np.testing.assert_allclose(pa3.flux, conv_flux) # If one of them is correlated, it is still deterministic. pa3.assignAt(0, pa1) pa3.setCorrelated() pa3.convolve(pa2) np.testing.assert_allclose(pa3.x, conv_x) np.testing.assert_allclose(pa3.y, conv_y) np.testing.assert_allclose(pa3.flux, conv_flux) pa3.assignAt(0, pa1) pa3.setCorrelated(False) pa2.setCorrelated() pa3.convolve(pa2) np.testing.assert_allclose(pa3.x, conv_x) np.testing.assert_allclose(pa3.y, conv_y) np.testing.assert_allclose(pa3.flux, conv_flux) # But if both are correlated, then it's not this simple. pa3.assignAt(0, pa1) pa3.setCorrelated() assert pa3.isCorrelated() assert pa2.isCorrelated() pa3.convolve(pa2) with assert_raises(AssertionError): np.testing.assert_allclose(pa3.x, conv_x) with assert_raises(AssertionError): np.testing.assert_allclose(pa3.y, conv_y) np.testing.assert_allclose(np.sum(pa3.flux), 1.7 * 1.7) np.testing.assert_allclose(np.sum(pa3.x**2) / nphotons, 2 * 2.3**2, rtol=0.01) np.testing.assert_allclose(np.sum(pa3.y**2) / nphotons, 2 * 2.3**2, rtol=0.01) # Error to have different lengths pa4 = galsim.PhotonArray(50, pa1.x[:50], pa1.y[:50], pa1.flux[:50]) assert_raises(galsim.GalSimError, pa1.convolve, pa4)
def test_focus_depth(): bd = galsim.BaseDeviate(1234) for _ in range(100): # Test that FocusDepth is additive photon_array = galsim.PhotonArray(1000) photon_array2 = galsim.PhotonArray(1000) photon_array.x = 0.0 photon_array.y = 0.0 photon_array2.x = 0.0 photon_array2.y = 0.0 galsim.FRatioAngles(1.234, obscuration=0.606, rng=bd).applyTo(photon_array) photon_array2.dxdz[:] = photon_array.dxdz photon_array2.dydz[:] = photon_array.dydz fd1 = galsim.FocusDepth(1.1) fd2 = galsim.FocusDepth(2.2) fd3 = galsim.FocusDepth(3.3) fd1.applyTo(photon_array) fd2.applyTo(photon_array) fd3.applyTo(photon_array2) np.testing.assert_allclose(photon_array.x, photon_array2.x, rtol=0, atol=1e-15) np.testing.assert_allclose(photon_array.y, photon_array2.y, rtol=0, atol=1e-15) # Assuming focus is at x=y=0, then # intrafocal (depth < 0) => (x > 0 => dxdz < 0) # extrafocal (depth > 0) => (x > 0 => dxdz > 0) # We applied an extrafocal operation above, so check for corresponding # relation between x, dxdz np.testing.assert_array_less(0, photon_array.x * photon_array.dxdz) # transforming by depth and -depth is null fd4 = galsim.FocusDepth(-3.3) fd4.applyTo(photon_array) np.testing.assert_allclose(photon_array.x, 0.0, rtol=0, atol=1e-15) np.testing.assert_allclose(photon_array.y, 0.0, rtol=0, atol=1e-15) # Check that invalid photon array is trapped pa = galsim.PhotonArray(10) fd = galsim.FocusDepth(1.0) with np.testing.assert_raises(galsim.GalSimError): fd.applyTo(pa) # Check that we can infer depth from photon positions before and after... for _ in range(100): photon_array = galsim.PhotonArray(1000) photon_array2 = galsim.PhotonArray(1000) ud = galsim.UniformDeviate(bd) ud.generate(photon_array.x) ud.generate(photon_array.y) photon_array.x -= 0.5 photon_array.y -= 0.5 galsim.FRatioAngles(1.234, obscuration=0.606, rng=bd).applyTo(photon_array) photon_array2.x[:] = photon_array.x photon_array2.y[:] = photon_array.y photon_array2.dxdz[:] = photon_array.dxdz photon_array2.dydz[:] = photon_array.dydz depth = ud() - 0.5 galsim.FocusDepth(depth).applyTo(photon_array2) np.testing.assert_allclose( (photon_array2.x - photon_array.x) / photon_array.dxdz, depth) np.testing.assert_allclose( (photon_array2.y - photon_array.y) / photon_array.dydz, depth) np.testing.assert_allclose(photon_array.dxdz, photon_array2.dxdz) np.testing.assert_allclose(photon_array.dydz, photon_array2.dydz)
def test_refract(): ud = galsim.UniformDeviate(57721) for _ in range(1000): photon_array = galsim.PhotonArray(1000, flux=1) ud.generate(photon_array.dxdz) ud.generate(photon_array.dydz) photon_array.dxdz *= 1.2 # -0.6 to 0.6 photon_array.dydz *= 1.2 photon_array.dxdz -= 0.6 photon_array.dydz -= 0.6 # copy for testing later dxdz0 = np.array(photon_array.dxdz) dydz0 = np.array(photon_array.dydz) index_ratio = ud() * 4 + 0.25 # 0.25 to 4.25 refract = galsim.Refraction(index_ratio) refract.applyTo(photon_array) # Triangle is length 1 in the z direction and length sqrt(dxdz**2+dydz**2) # in the 'r' direction. rsqr0 = dxdz0**2 + dydz0**2 sintheta0 = np.sqrt(rsqr0) / np.sqrt(1 + rsqr0) # See if total internal reflection applies w = sintheta0 < index_ratio np.testing.assert_array_equal(photon_array.dxdz[~w], np.nan) np.testing.assert_array_equal(photon_array.dydz[~w], np.nan) np.testing.assert_array_equal(photon_array.flux, np.where(w, 1.0, 0.0)) sintheta0 = sintheta0[w] dxdz0 = dxdz0[w] dydz0 = dydz0[w] dxdz1 = photon_array.dxdz[w] dydz1 = photon_array.dydz[w] rsqr1 = dxdz1**2 + dydz1**2 sintheta1 = np.sqrt(rsqr1) / np.sqrt(1 + rsqr1) # Check Snell's law np.testing.assert_allclose(sintheta0, index_ratio * sintheta1) # Check azimuthal angle stays constant phi0 = np.arctan2(dydz0, dxdz0) phi1 = np.arctan2(dydz1, dxdz1) np.testing.assert_allclose(phi0, phi1) # Check plane of refraction is perpendicular to (0,0,1) np.testing.assert_allclose(np.dot( np.cross( np.stack([dxdz0, dydz0, -np.ones(len(dxdz0))], axis=1), np.stack([dxdz1, dydz1, -np.ones(len(dxdz1))], axis=1), ), [0, 0, 1]), 0.0, rtol=0, atol=1e-13) # Try a wavelength dependent index_ratio index_ratio = lambda w: np.where(w < 1, 1.1, 2.2) photon_array = galsim.PhotonArray(100) ud.generate(photon_array.wavelength) ud.generate(photon_array.dxdz) ud.generate(photon_array.dydz) photon_array.dxdz *= 1.2 # -0.6 to 0.6 photon_array.dydz *= 1.2 photon_array.dxdz -= 0.6 photon_array.dydz -= 0.6 photon_array.wavelength *= 2 # 0 to 2 dxdz0 = photon_array.dxdz.copy() dydz0 = photon_array.dydz.copy() refract_func = galsim.Refraction(index_ratio=index_ratio) refract_func.applyTo(photon_array) dxdz_func = photon_array.dxdz.copy() dydz_func = photon_array.dydz.copy() photon_array.dxdz = dxdz0.copy() photon_array.dydz = dydz0.copy() refract11 = galsim.Refraction(index_ratio=1.1) refract11.applyTo(photon_array) dxdz11 = photon_array.dxdz.copy() dydz11 = photon_array.dydz.copy() photon_array.dxdz = dxdz0.copy() photon_array.dydz = dydz0.copy() refract22 = galsim.Refraction(index_ratio=2.2) refract22.applyTo(photon_array) dxdz22 = photon_array.dxdz.copy() dydz22 = photon_array.dydz.copy() w = photon_array.wavelength < 1 np.testing.assert_allclose(dxdz_func, np.where(w, dxdz11, dxdz22)) np.testing.assert_allclose(dydz_func, np.where(w, dydz11, dydz22))
import galsim # Make blank image im = galsim.ImageF(31, 31, init_value=0) # Set the coordinates so 0,0 refers to the central pixel. #im.setCenter(0,0) im.setCenter(0, 0) # Make 80K photons with position 0,0 photons = galsim.PhotonArray(800000) # """The PhotonArray class encapsulates the concept of a collection of photons incident on # a detector. # A PhotonArray object is not typically constructed directly by the user. Rather, it is # typically constructed as the return value of the `GSObject.shoot` method. # At this point, the photons only have x,y,flux values. Then there are a number of classes # that perform various modifications to the photons such as giving them wavelengths or # inclination angles or removing some due to fringing or vignetting. # TODO: fringing, vignetting, and angles are not implemented yet, but we expect them to # be implemented soon, so the above paragraph is a bit aspirational atm. # Attributes # ---------- # A PhotonArray instance has the following attributes, each of which is a numpy array: # - x,y the incidence positions at the top of the detector # - flux the flux of the photons # - dxdz, dydz the tangent of the inclination angles in each direction # - wavelength the wavelength of the photons # Unlike most GalSim objects (but like Images), PhotonArrays are mutable. It is permissible # to write values to the above attributes with code like # >>> photon_array.x += numpy.random.random(1000) * 0.01