def test_opt_indiv_aberrations(): """Test that aberrations specified by name match those specified in `aberrations` list.""" screen1 = galsim.OpticalScreen(diam=4.0, tip=0.2, tilt=0.3, defocus=0.4, astig1=0.5, astig2=0.6, coma1=0.7, coma2=0.8, trefoil1=0.9, trefoil2=1.0, spher=1.1) screen2 = galsim.OpticalScreen(diam=4.0, aberrations=[ 0.0, 0.0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1 ]) psf1 = galsim.PhaseScreenList(screen1).makePSF(diam=4.0, lam=500.0) psf2 = galsim.PhaseScreenList(screen2).makePSF(diam=4.0, lam=500.0) np.testing.assert_array_equal( psf1.img, psf2.img, "Individually specified aberrations differs from aberrations specified as list." )
def test_aperture(): """Test various ways to construct Apertures.""" # Simple tests for constructing and pickling Apertures. aper1 = galsim.Aperture(diam=1.7) im = galsim.fits.read(os.path.join(imgdir, pp_file)) aper2 = galsim.Aperture(diam=1.7, pupil_plane_im=im) aper3 = galsim.Aperture(diam=1.7, nstruts=4, gsparams=galsim.GSParams(maximum_fft_size=4096)) do_pickle(aper1) do_pickle(aper2) do_pickle(aper3) # Automatically created Aperture should match one created via OpticalScreen aper1 = galsim.Aperture(diam=1.7) aper2 = galsim.Aperture(diam=1.7, lam=500, screen_list=[galsim.OpticalScreen(diam=1.7)]) err_str = ("Aperture created implicitly using Airy does not match Aperture created using " "OpticalScreen.") assert aper1 == aper2, err_str assert_raises(ValueError, galsim.Aperture, 1.7, obscuration=-0.3) assert_raises(ValueError, galsim.Aperture, 1.7, obscuration=1.1) assert_raises(ValueError, galsim.Aperture, -1.7) assert_raises(ValueError, galsim.Aperture, 0) assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, circular_pupil=False) assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, nstruts=2) assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_thick=0.01) assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_angle=5*galsim.degrees) assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_angle=5*galsim.degrees) assert_raises(ValueError, galsim.Aperture, 1.7, screen_list=[galsim.OpticalScreen(diam=1)]) # rho is a convenience property that can be useful when debugging, but isn't used in the # main code base. np.testing.assert_almost_equal(aper1.rho, aper1.u * 2./1.7 + 1j * aper1.v * 2./1.7) # Some other functions that aren't used by anything anymore, but were useful in development. for lam in [300, 550, 1200]: stepk = aper1._getStepK(lam=lam) maxk = aper1._getMaxK(lam=lam) scale = aper1._sky_scale(lam=lam) size = aper1._sky_size(lam=lam) np.testing.assert_almost_equal(stepk, 2.*np.pi/size) np.testing.assert_almost_equal(maxk, np.pi/scale) # If the constructed pupil plane would be too large, raise an error assert_raises(galsim.GalSimFFTSizeError, galsim.Aperture, 1.7, pupil_plane_scale=1.e-4) # Similar if the given image is too large. # Here, we change gsparams.maximum_fft_size, rather than build a really large image to load. with assert_raises(galsim.GalSimFFTSizeError): galsim.Aperture(1.7, pupil_plane_im=im, gsparams=galsim.GSParams(maximum_fft_size=64)) # Other choices just give warnings with assert_warns(galsim.GalSimWarning): galsim.Aperture(diam=1.7, pupil_plane_size=3, pupil_plane_scale=0.03) im.wcs = None # Otherwise get an error. with assert_warns(galsim.GalSimWarning): galsim.Aperture(diam=1.7, pupil_plane_im=im, pupil_plane_scale=0.03)
def _wavefront_gradient(self, u, v, t, theta): # remap theta to prevent extrapolation beyond a radius of 1.708 degrees, which is the # radius of the outermost sampling point. fudgeFactor = 1.708 / 2.04 z = self.oz.cartesian_coeff(theta[0] / galsim.degrees * fudgeFactor, theta[1] / galsim.degrees * fudgeFactor) Z = galsim.OpticalScreen(diam=8.36, obscuration=0.61, aberrations=[0] * 4 + list(z), annular_zernike=True) return Z._wavefront_gradient(u, v, t, theta)
def test_aperture(): """Test various ways to construct Apertures.""" # Simple tests for constructing and pickling Apertures. aper1 = galsim.Aperture(diam=1.0) im = galsim.fits.read(os.path.join(imgdir, pp_file)) aper2 = galsim.Aperture(diam=1.0, pupil_plane_im=im) do_pickle(aper1) do_pickle(aper2) # Automatically created Aperture should match one created via OpticalScreen aper1 = galsim.Aperture(diam=1.0) aper2 = galsim.Aperture(diam=1.0, lam=500, screen_list=[galsim.OpticalScreen()]) err_str = ("Aperture created implicitly using Airy does not match Aperture created using " "OpticalScreen.") assert aper1 == aper2, err_str
def test_gc(): """Make sure that pending psfs don't leak memory. """ import gc atm = galsim.Atmosphere(screen_size=10.0, altitude=0, r0_500=0.15, suppress_warning=True) # First check that no PhaseScreenPSFs are known to the garbage collector assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # Make a PhaseScreenPSF and check that it's known to the garbage collector psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) assert any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # If we delete it, it disappears everywhere del psf gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # If we draw one using photon-shooting, it still exists in _pending psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) psf.drawImage(nx=10, ny=10, scale=0.2, method='phot', n_photons=100) assert psf in [p[1]() for p in atm._pending] # If we draw even one of many using fft, _pending gets completely emptied psf2 = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) psf.drawImage(nx=10, ny=10, scale=0.2) assert atm._pending == [] # And if then deleted, they again don't exist anywhere del psf, psf2 gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # A corner case revealed in coverage tests: # Make sure that everything still works if some, but not all static pending PSFs are deleted. screen = galsim.OpticalScreen(diam=1.1) phaseScreenList = galsim.PhaseScreenList(screen) psf1 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) psf2 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) psf3 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) del psf2 psf1.drawImage(nx=10, ny=10, scale=0.2) del psf1, psf3 assert phaseScreenList._pending == [] gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])
def _build_optics(self): # from galsim examples/great3/cgc.yaml rms_aberration = 0.26 names = [ "defocus", "astig1", "astig2", "coma1", "coma2", "trefoil1", "trefoil2", "spher" ] weights = np.array([0.13, 0.13, 0.14, 0.06, 0.06, 0.05, 0.06, 0.03]) weights /= np.sqrt(np.sum(weights**2)) weights *= rms_aberration kwargs = {k: a * self.rng.normal() for k, a in zip(names, weights)} opt = galsim.OpticalScreen(lam_0=self.lam, diam=self.diam, obscuration=self.obscuration, **kwargs) # order them so I know where things are for later... _screens = galsim.PhaseScreenList() _screens.append(opt) for i in range(len(self._screens)): _screens.append(self._screens[i]) self._screens = _screens
def test_ne(): """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals.""" pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file)) # Test galsim.Aperture __ne__ objs = [galsim.Aperture(diam=1.0), galsim.Aperture(diam=1.1), galsim.Aperture(diam=1.0, oversampling=1.5), galsim.Aperture(diam=1.0, pad_factor=1.5), galsim.Aperture(diam=1.0, circular_pupil=False), galsim.Aperture(diam=1.0, obscuration=0.3), galsim.Aperture(diam=1.0, nstruts=3), galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2), galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im, pupil_angle=10.0*galsim.degrees)] all_obj_diff(objs) # Test AtmosphericScreen __ne__ rng = galsim.BaseDeviate(1) objs = [galsim.AtmosphericScreen(10.0, rng=rng), galsim.AtmosphericScreen(1.0, rng=rng), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.01), galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.02), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.998, time_step=0.02), galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1), galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0), ] all_obj_diff(objs) objs.append(galsim.AtmosphericScreen(10.0, rng=rng)) objs[-1].instantiate() # Should still all be __ne__, but first and last will have the same hash this time. assert hash(objs[0]) == hash(objs[-1]) all_obj_diff(objs, check_hash=False) # Test OpticalScreen __ne__ objs = [galsim.OpticalScreen(diam=1.0), galsim.OpticalScreen(diam=1.0, tip=1.0), galsim.OpticalScreen(diam=1.0, tilt=1.0), galsim.OpticalScreen(diam=1.0, defocus=1.0), galsim.OpticalScreen(diam=1.0, astig1=1.0), galsim.OpticalScreen(diam=1.0, astig2=1.0), galsim.OpticalScreen(diam=1.0, coma1=1.0), galsim.OpticalScreen(diam=1.0, coma2=1.0), galsim.OpticalScreen(diam=1.0, trefoil1=1.0), galsim.OpticalScreen(diam=1.0, trefoil2=1.0), galsim.OpticalScreen(diam=1.0, spher=1.0), galsim.OpticalScreen(diam=1.0, spher=1.0, lam_0=100.0), galsim.OpticalScreen(diam=1.0, aberrations=[0,0,1.1]), # tip=1.1 ] all_obj_diff(objs) # Test PhaseScreenList __ne__ atm = galsim.Atmosphere(10.0, vx=1.0) objs = [galsim.PhaseScreenList(atm), galsim.PhaseScreenList(objs), # Reuse list of OpticalScreens above galsim.PhaseScreenList(objs[0:2])] all_obj_diff(objs) # Test PhaseScreenPSF __ne__ psl = galsim.PhaseScreenList(atm) objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')] stepk = objs[0].stepk maxk = objs[0].maxk objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)] all_obj_diff(objs)
def test_phase_screen_list(): """Test list-like behaviors of PhaseScreenList.""" rng = galsim.BaseDeviate(1234) rng2 = galsim.BaseDeviate(123) aper = galsim.Aperture(diam=1.0) ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, time_step=0.01, rng=rng) assert ar1._time == 0.0, "AtmosphericScreen initialized with non-zero time." do_pickle(ar1) do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, 0.0).sum()) do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) t = np.empty_like(aper.u) ud = galsim.UniformDeviate(rng.duplicate()) ud.generate(t.ravel()) t *= 0.1 # Only do a few boiling steps do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, t).sum()) do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, t))) # Try seeking backwards assert ar1._time > 0.0 ar1._seek(0.0) # But not before t=0.0 with assert_raises(ValueError): ar1._seek(-1.0) # Check that L0=np.inf and L0=None yield the same thing here too. ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, time_step=0.01, rng=rng) # Before ar2 is instantiated, it's unequal to ar1, even though they were initialized with the # same arguments (the hashes are the same though). After both have been instantiated with the # same range of k (ar1 through use of .wavefront() and ar2 explicitly), then they are equal ( # and the hashes are still the same). assert hash(ar1) == hash(ar2) assert ar1 != ar2 ar2.instantiate() assert ar1 == ar2 assert hash(ar1) == hash(ar2) # Create a couple new screens with different types/parameters ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, time_step=0.015, rng=rng2) ar2.instantiate() assert ar1 != ar2 ar3 = galsim.OpticalScreen(diam=1.0, aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1], obscuration=0.3, annular_zernike=True) do_pickle(ar3) do_pickle(ar3, func=lambda x:x.wavefront(aper.u, aper.v).sum()) do_pickle(ar3, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v))) atm = galsim.Atmosphere(screen_size=30.0, altitude=[0.0, 1.0], speed=[1.0, 2.0], direction=[0.0*galsim.degrees, 120*galsim.degrees], r0_500=0.15, rng=rng) atm.append(ar3) do_pickle(atm) do_pickle(atm, func=lambda x:x.wavefront(aper.u, aper.v, 0.0, theta0).sum()) do_pickle(atm, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__ atm2 = atm[:-1] # Refers to first n-1 screens assert atm != atm2 # Append a different screen to the end of atm2 atm2.append(ar2) assert atm != atm2 # Swap the last screen in atm2 for the one that should match atm. del atm2[-1] atm2.append(atm[-1]) assert atm == atm2 with assert_raises(TypeError): atm['invalid'] with assert_raises(IndexError): atm[3] # Test building from empty PhaseScreenList atm3 = galsim.PhaseScreenList() atm3.extend(atm2) assert atm == atm3 # Test constructing from existing PhaseScreenList atm4 = galsim.PhaseScreenList(atm3) del atm4[-1] assert atm != atm4 atm4.append(atm[-1]) assert atm == atm4 # Test swap atm4[0], atm4[1] = atm4[1], atm4[0] assert atm != atm4 atm4[0], atm4[1] = atm4[1], atm4[0] assert atm == atm4 wf = atm.wavefront(aper.u, aper.v, None, theta0) wf2 = atm2.wavefront(aper.u, aper.v, None, theta0) wf3 = atm3.wavefront(aper.u, aper.v, None, theta0) wf4 = atm4.wavefront(aper.u, aper.v, None, theta0) np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent") # Check copy import copy # Shallow copy copies by reference. atm5 = copy.copy(atm) assert atm[0] == atm5[0] assert atm[0] is atm5[0] atm._seek(1.0) assert atm[0]._time == 1.0, "Wrong time for AtmosphericScreen" assert atm[0] == atm5[0] assert atm[0] is atm5[0] # Deepcopy actually makes an indepedent object in memory. atm5 = copy.deepcopy(atm) assert atm[0] == atm5[0] assert atm[0] is not atm5[0] atm._seek(2.0) assert atm[0]._time == 2.0, "Wrong time for AtmosphericScreen" # But we still get equality, since this doesn't depend on mutable internal state: assert atm[0] == atm5[0] # Constructor should accept both list and indiv layers as arguments. atm6 = galsim.PhaseScreenList(atm[0]) atm7 = galsim.PhaseScreenList([atm[0]]) assert atm6 == atm7 do_pickle(atm6, func=lambda x:x.wavefront(aper.u, aper.v, None, theta0).sum()) do_pickle(atm6, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) atm6 = galsim.PhaseScreenList(atm[0], atm[1]) atm7 = galsim.PhaseScreenList([atm[0], atm[1]]) atm8 = galsim.PhaseScreenList(atm[0:2]) # Slice returns PhaseScreenList, so this works too. assert atm6 == atm7 assert atm6 == atm8 # Check some actual derived PSFs too, not just phase screens. Use a small pupil_plane_size and # relatively large pupil_plane_scale to speed up the unit test. atm._reset() assert atm[0]._time == 0.0, "Wrong time for AtmosphericScreen" kwargs = dict(exptime=0.05, time_step=0.01, diam=1.1, lam=1000.0) psf = atm.makePSF(**kwargs) do_pickle(psf) do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1)) psf2 = atm2.makePSF(**kwargs) psf3 = atm3.makePSF(**kwargs) psf4 = atm4.makePSF(**kwargs) np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent") # Check errors in u,v,t shapes. assert_raises(ValueError, ar1.wavefront, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar1.wavefront, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar1.wavefront, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar1.wavefront_gradient, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) assert_raises(ValueError, ar3.wavefront, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar3.wavefront, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar3.wavefront_gradient, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar3.wavefront_gradient, aper.u[:-1,:-1], aper.v)
def make_movie(args): rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # Generate 1D Gaussian random fields for each aberration. t = np.arange(-args.n/2, args.n/2) corr = np.exp(-0.5*t**2/args.ell**2) pk = np.fft.fft(np.fft.fftshift(corr)) ak = np.sqrt(2*pk) phi = np.random.uniform(size=(args.n, args.jmax)) zk = ak[:, None]*np.exp(2j*np.pi*phi) aberrations = args.n/2*np.fft.ifft(zk, axis=0).real measured_std = np.mean(np.std(aberrations, axis=0)) aberrations *= args.sigma/measured_std aberrations -= np.mean(aberrations, axis=0) # For the atmosphere screens, we first estimates weights, so that the turbulence is dominated by # the lower layers consistent with direct measurements. The specific values we use are from # SCIDAR measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') alts = np.max(Ellerbroek_alts)*np.arange(args.nlayers)/(args.nlayers-1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append(u()*args.max_speed) # Use a random speed between 0 and args.max_speed dirn.append(u()*360*galsim.degrees) # And an isotropically distributed wind direction. r0_500.append(args.r0_500*weights[i]**(-3./5)) print("Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m." .format(alts[i], spd[i]*dirn[i].cos(), spd[i]*dirn[i].sin(), r0_500[i])) if args.nlayers > 0: # Make two identical Atmospheres. They will diverge when one gets drawn using Fourier # optics and the other gets drawn with geometric optics. fft_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng.duplicate(), screen_size=args.screen_size, screen_scale=args.screen_scale) geom_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng.duplicate(), screen_size=args.screen_size, screen_scale=args.screen_scale) else: fft_atm = galsim.PhaseScreenList() geom_atm = galsim.PhaseScreenList() # Before either of this has been instantiated, they are identical assert fft_atm == geom_atm # If any AtmosphericScreens are included, we manually instantiate here so we can have a # uniformly updating ProgressBar both here and below when actually drawing PSFs. Normally, it's # okay to let the atms automatically instantiate, which happens when the first PSF is drawn, or # the first wavefront is queried. if args.nlayers > 0: print("Instantiating screens") with ProgressBar(2*args.nlayers) as bar: fft_atm.instantiate(_bar=bar) r0 = args.r0_500*(args.lam/500)**1.2 geom_atm.instantiate(kmax=0.2/r0, _bar=bar) # After instantiation, they're only equal if there's no atmosphere. assert fft_atm != geom_atm # Setup Fourier and geometric apertures fft_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration, pad_factor=args.pad_factor, oversampling=args.oversampling, nstruts=args.nstruts, strut_thick=args.strut_thick, strut_angle=args.strut_angle*galsim.degrees) geom_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration, pad_factor=args.geom_oversampling, oversampling=0.5, nstruts=args.nstruts, strut_thick=args.strut_thick, strut_angle=args.strut_angle*galsim.degrees) scale = args.size/args.nx extent = np.r_[-1,1,-1,1]*args.size/2 fft_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale) geom_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale) # Code to setup the Matplotlib animation. metadata = dict(title="FFT vs geom movie", artist='Matplotlib') writer = anim.FFMpegWriter(fps=15, bitrate=10000, metadata=metadata) fig = Figure(facecolor='k', figsize=(16, 9)) FigureCanvasAgg(fig) fft_ax = fig.add_axes([0.07, 0.08, 0.36, 0.9]) fft_ax.set_xlabel("Arcsec") fft_ax.set_ylabel("Arcsec") fft_ax.set_title("Fourier Optics") fft_im = fft_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent, vmin=0.0, vmax=args.vmax) # Axis for the wavefront image on the right. geom_ax = fig.add_axes([0.50, 0.08, 0.36, 0.9]) geom_ax.set_xlabel("Arcsec") geom_ax.set_ylabel("Arcsec") geom_ax.set_title("Geometric Optics") geom_im = geom_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent, vmin=0.0, vmax=args.vmax) # Color items white to show up on black background for ax in [fft_ax, geom_ax]: for _, spine in ax.spines.items(): spine.set_color('w') ax.title.set_color('w') ax.xaxis.label.set_color('w') ax.yaxis.label.set_color('w') ax.tick_params(axis='both', colors='w') ztext = [] for i in range(2, args.jmax+1): x = 0.88 y = 0.1 + (args.jmax-i)/args.jmax*0.8 ztext.append(fig.text(x, y, "Z{:d} = {:5.3f}".format(i, 0.0))) ztext[-1].set_color('w') M_fft = fft_ax.text(0.02, 0.955, '', transform=fft_ax.transAxes) M_fft.set_color('w') M_geom = geom_ax.text(0.02, 0.955, '', transform=geom_ax.transAxes) M_geom.set_color('w') etext_fft = fft_ax.text(0.02, 0.91, '', transform=fft_ax.transAxes) etext_fft.set_color('w') etext_geom = geom_ax.text(0.02, 0.91, '', transform=geom_ax.transAxes) etext_geom.set_color('w') fft_mom = np.empty((args.n, 8), dtype=float) geom_mom = np.empty((args.n, 8), dtype=float) fullpath = args.out+"movie.mp4" subdir, filename = os.path.split(fullpath) if subdir and not os.path.isdir(subdir): os.makedirs(subdir) print("Drawing PSFs") with ProgressBar(args.n) as bar: with writer.saving(fig, fullpath, 100): t0 = 0.0 for i, aberration in enumerate(aberrations): optics = galsim.OpticalScreen(args.diam, obscuration=args.obscuration, aberrations=[0]+aberration.tolist()) fft_psl = galsim.PhaseScreenList(fft_atm._layers+[optics]) geom_psl = galsim.PhaseScreenList(geom_atm._layers+[optics]) fft_psf = fft_psl.makePSF( lam=args.lam, aper=fft_aper, t0=t0, exptime=args.time_step) geom_psf = geom_psl.makePSF( lam=args.lam, aper=geom_aper, t0=t0, exptime=args.time_step) fft_img0 = fft_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale) geom_img0 = geom_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale, method='phot', n_photons=100000) t0 += args.time_step if args.accumulate: fft_img_sum += fft_img0 geom_img_sum += geom_img0 fft_img = fft_img_sum/(i+1) geom_img = geom_img_sum/(i+1) else: fft_img = fft_img0 geom_img = geom_img0 fft_im.set_array(fft_img.array) geom_im.set_array(geom_img.array) for j, ab in enumerate(aberration): if j == 0: continue ztext[j-1].set_text("Z{:d} = {:5.3f}".format(j+1, ab)) # Calculate simple estimate of ellipticity mom_fft = galsim.utilities.unweighted_moments(fft_img, origin=fft_img.true_center) mom_geom = galsim.utilities.unweighted_moments(geom_img, origin=geom_img.true_center) e_fft = galsim.utilities.unweighted_shape(mom_fft) e_geom = galsim.utilities.unweighted_shape(mom_geom) Is = ("$M_x$={: 6.4f}, $M_y$={: 6.4f}, $M_{{xx}}$={:6.4f}," " $M_{{yy}}$={:6.4f}, $M_{{xy}}$={: 6.4f}") M_fft.set_text(Is.format(mom_fft['Mx']*fft_img.scale, mom_fft['My']*fft_img.scale, mom_fft['Mxx']*fft_img.scale**2, mom_fft['Myy']*fft_img.scale**2, mom_fft['Mxy']*fft_img.scale**2)) M_geom.set_text(Is.format(mom_geom['Mx']*geom_img.scale, mom_geom['My']*geom_img.scale, mom_geom['Mxx']*geom_img.scale**2, mom_geom['Myy']*geom_img.scale**2, mom_geom['Mxy']*geom_img.scale**2)) etext_fft.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format( e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2)) etext_geom.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format( e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2)) fft_mom[i] = (mom_fft['Mx']*fft_img.scale, mom_fft['My']*fft_img.scale, mom_fft['Mxx']*fft_img.scale**2, mom_fft['Myy']*fft_img.scale**2, mom_fft['Mxy']*fft_img.scale**2, e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2) geom_mom[i] = (mom_geom['Mx']*geom_img.scale, mom_geom['My']*geom_img.scale, mom_geom['Mxx']*geom_img.scale**2, mom_geom['Myy']*geom_img.scale**2, mom_geom['Mxy']*geom_img.scale**2, e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2) writer.grab_frame(facecolor=fig.get_facecolor()) bar.update() def symmetrize_axis(ax): xlim = ax.get_xlim() ylim = ax.get_ylim() lim = min(xlim[0], ylim[0]), max(xlim[1], ylim[1]) ax.set_xlim(lim) ax.set_ylim(lim) ax.plot(lim, lim) # Centroid plot fig = Figure(figsize=(10, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 2, 1)) axes.append(fig.add_subplot(1, 2, 2)) axes[0].scatter(fft_mom[:, 0], geom_mom[:, 0]) axes[1].scatter(fft_mom[:, 1], geom_mom[:, 1]) axes[0].set_title("Mx") axes[1].set_title("My") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"centroid.png", dpi=300) # Second moment plot fig = Figure(figsize=(16, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 3, 1)) axes.append(fig.add_subplot(1, 3, 2)) axes.append(fig.add_subplot(1, 3, 3)) axes[0].scatter(fft_mom[:, 2], geom_mom[:, 2]) axes[1].scatter(fft_mom[:, 3], geom_mom[:, 3]) axes[2].scatter(fft_mom[:, 4], geom_mom[:, 4]) axes[0].set_title("Mxx") axes[1].set_title("Myy") axes[2].set_title("Mxy") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"2ndMoment.png", dpi=300) # Ellipticity plot fig = Figure(figsize=(16, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 3, 1)) axes.append(fig.add_subplot(1, 3, 2)) axes.append(fig.add_subplot(1, 3, 3)) axes[0].scatter(fft_mom[:, 5], geom_mom[:, 5]) axes[1].scatter(fft_mom[:, 6], geom_mom[:, 6]) axes[2].scatter(fft_mom[:, 7], geom_mom[:, 7]) axes[0].set_title("e1") axes[1].set_title("e2") axes[2].set_title("rsqr") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"ellipticity.png", dpi=300)
def test_ne(): """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals.""" import copy pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file)) # Test galsim.Aperture __ne__ objs = [galsim.Aperture(diam=1.0), galsim.Aperture(diam=1.1), galsim.Aperture(diam=1.0, oversampling=1.5), galsim.Aperture(diam=1.0, pad_factor=1.5), galsim.Aperture(diam=1.0, circular_pupil=False), galsim.Aperture(diam=1.0, obscuration=0.3), galsim.Aperture(diam=1.0, nstruts=3), galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2), galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im, pupil_angle=10.0*galsim.degrees)] all_obj_diff(objs) # Test AtmosphericScreen __ne__ rng = galsim.BaseDeviate(1) objs = [galsim.AtmosphericScreen(10.0, rng=rng), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), # advance this one below galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999), galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0), galsim.AtmosphericScreen(10.0, rng=rng, time_step=0.1), galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1), galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0), ] objs[2].advance() all_obj_diff(objs) # Test OpticalScreen __ne__ objs = [galsim.OpticalScreen(), galsim.OpticalScreen(tip=1.0), galsim.OpticalScreen(tilt=1.0), galsim.OpticalScreen(defocus=1.0), galsim.OpticalScreen(astig1=1.0), galsim.OpticalScreen(astig2=1.0), galsim.OpticalScreen(coma1=1.0), galsim.OpticalScreen(coma2=1.0), galsim.OpticalScreen(trefoil1=1.0), galsim.OpticalScreen(trefoil2=1.0), galsim.OpticalScreen(spher=1.0), galsim.OpticalScreen(spher=1.0, lam_0=100.0), galsim.OpticalScreen(aberrations=[0,0,1.1]), # tip=1.1 ] all_obj_diff(objs) # Test PhaseScreenList __ne__ atm = galsim.Atmosphere(10.0, vx=1.0) objs = [galsim.PhaseScreenList(atm), galsim.PhaseScreenList(copy.deepcopy(atm)), # advance down below galsim.PhaseScreenList(objs), # Reuse list of OpticalScreens above galsim.PhaseScreenList(objs[0:2])] objs[1].advance() all_obj_diff(objs) # Test PhaseScreenPSF __ne__ objs[0].reset() psl = galsim.PhaseScreenList(atm) objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0), galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)] # advanced so differs psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')] stepk = objs[0].stepK() maxk = objs[0].maxK() psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)] all_obj_diff(objs)
def test_phase_screen_list(): """Test list-like behaviors of PhaseScreenList.""" rng = galsim.BaseDeviate(1234) rng2 = galsim.BaseDeviate(123) aper = galsim.Aperture(diam=1.0) ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, rng=rng) do_pickle(ar1) do_pickle(ar1, func=lambda x: x.tab2d(12.3, 45.6)) do_pickle(ar1, func=lambda x: x.wavefront(aper).sum()) # Check that L0=np.inf and L0=None yield the same thing here too. ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, rng=rng) assert ar1 == ar2 # Create a couple new screens with different types/parameters ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, rng=rng2) assert ar1 != ar2 ar3 = galsim.OpticalScreen(aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1]) do_pickle(ar3) do_pickle(ar3, func=lambda x:x.wavefront(aper).sum()) atm = galsim.Atmosphere(screen_size=30.0, altitude=[0.0, 1.0], speed=[1.0, 2.0], direction=[0.0*galsim.degrees, 120*galsim.degrees], r0_500=0.15, rng=rng) atm.append(ar3) do_pickle(atm) do_pickle(atm, func=lambda x:x.wavefront(aper).sum()) # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__ atm2 = galsim.PhaseScreenList(atm[:-1]) # Refers to first n-1 screens assert atm != atm2 # Append a different screen to the end of atm2 atm2.append(ar2) assert atm != atm2 # Swap the last screen in atm2 for the one that should match atm. del atm2[-1] atm2.append(atm[-1]) assert atm == atm2 # Test building from empty PhaseScreenList atm3 = galsim.PhaseScreenList() atm3.extend(atm2) assert atm == atm2 # Test constructing from existing PhaseScreenList atm4 = galsim.PhaseScreenList(atm3) del atm4[-1] assert atm != atm4 atm4.append(atm[-1]) assert atm == atm4 # Test swap atm4[0], atm4[1] = atm4[1], atm4[0] assert atm != atm4 atm4[0], atm4[1] = atm4[1], atm4[0] assert atm == atm4 wf = atm.wavefront(aper) wf2 = atm2.wavefront(aper) wf3 = atm3.wavefront(aper) wf4 = atm4.wavefront(aper) np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent") # Check copy import copy # Shallow copy copies by reference. atm5 = copy.copy(atm) assert atm[0] == atm5[0] assert atm[0] is atm5[0] atm.advance() assert atm[0] == atm5[0] assert atm[0] is atm5[0] # Deepcopy actually makes an indepedent object in memory. atm5 = copy.deepcopy(atm) assert atm[0] == atm5[0] assert atm[0] is not atm5[0] atm.advance() assert atm[0] != atm5[0] # Constructor should accept both list and indiv layers as arguments. atm6 = galsim.PhaseScreenList(atm[0]) atm7 = galsim.PhaseScreenList([atm[0]]) assert atm6 == atm7 atm6 = galsim.PhaseScreenList(atm[0], atm[1]) atm7 = galsim.PhaseScreenList([atm[0], atm[1]]) atm8 = galsim.PhaseScreenList(atm[0:2]) # Slice returns PhaseScreenList, so this works too. assert atm6 == atm7 assert atm6 == atm8 # Check some actual derived PSFs too, not just phase screens. Use a small pupil_plane_size and # relatively large pupil_plane_scale to speed up the unit test. atm.advance_by(1.0) do_pickle(atm) atm.reset() kwargs = dict(exptime=0.06, diam=1.0, lam=1000.0) psf = atm.makePSF(**kwargs) do_pickle(psf) do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1)) # Need to reset atm2 since both atm and atm2 reference the same layer objects (not copies). # Not sure if this is a feature or a bug, but it's also how regular python lists work. atm2.reset() psf2 = atm2.makePSF(**kwargs) atm3.reset() psf3 = atm3.makePSF(**kwargs) atm4.reset() psf4 = atm4.makePSF(**kwargs) np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent")
def evalZernike(coefficients, coords): # Use GalSim internals to evaluate Zernike polynomials. aberrations = [0] aberrations.extend(coefficients) screen = galsim.OpticalScreen(aberrations=aberrations, diam=1) return screen.wavefront(coords[:, 0], coords[:, 1])