def test_decam_psf(): # Just testing that doesn't crash for the moment fn = os.path.join(batoid.datadir, "DECam", "DECam.yaml") config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) stampSize = 1.0 # arcsec nx = 64 focalLength = 10.0 # guess if __name__ == '__main__': thetas = [0.0, 1800.0, 3960.0] # arcsec else: thetas = [3960.0] for theta in thetas: print(theta / 3600.0) dirCos = batoid.utils.gnomicToDirCos(0.0, theta / 206265) rays = batoid.circularGrid(10.0, 1.95, 0.5, dirCos[0], dirCos[1], -dirCos[2], 10, 100, 620e-9, 1.0, batoid.Air()) telescope.traceInPlace(rays) rays.trimVignettedInPlace() xs = rays.x - np.mean(rays.x) ys = rays.y - np.mean(rays.y) xs *= 206265 / focalLength # meters to arcsec ys *= 206265 / focalLength # Need to add half-pixel offset xs += stampSize / nx / 2 ys += stampSize / nx / 2 dx = stampSize / nx * focalLength / 206265 # meters psf = batoid.huygensPSF(telescope, 0.0, theta / 206265, 620e-9, nx=nx, dx=dx, dy=dx) if __name__ == '__main__': import matplotlib.pyplot as plt fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111) ax.imshow(psf.array, extent=np.r_[-1, 1, -1, 1] * stampSize / 2) ax.scatter(xs, ys, s=5, c='r', alpha=0.5) ax.set_title("DECam PSF field={:5.2f}".format(theta / 3600.0)) ax.set_xlabel("arcsec") ax.set_ylabel("arcsec") fig.tight_layout() plt.show()
def test_hsc_psf(): # Just testing that doesn't crash for the moment telescope = batoid.Optic.fromYaml("HSC.yaml") stampSize = 0.75 # arcsec nx = 64 focalLength = 15.0 # guess if __name__ == '__main__': thetas = [0.0, 1350.0, 2700.0] # arcsec else: thetas = [2700.0] for theta in thetas: print(theta / 3600.0) dirCos = batoid.utils.gnomonicToDirCos(0.0, theta / 206265) rays = batoid.circularGrid(20.0, 4.1, 0.9, dirCos[0], dirCos[1], dirCos[2], 10, 100, 620e-9, 1.0, batoid.ConstMedium(1.0)) telescope.traceInPlace(rays) rays.trimVignettedInPlace() xs = rays.x - np.mean(rays.x) ys = rays.y - np.mean(rays.y) xs *= 206265 / focalLength # meters to arcsec ys *= 206265 / focalLength # Need to add half-pixel offset xs += stampSize / nx / 2 ys += stampSize / nx / 2 dx = stampSize / nx * focalLength / 206265 # meters psf = batoid.huygensPSF(telescope, 0.0, theta / 206265, 620e-9, nx=nx, dx=dx, dy=dx) if __name__ == '__main__': import matplotlib.pyplot as plt fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111) ax.imshow(psf.array, extent=np.r_[-1, 1, -1, 1] * stampSize / 2) ax.scatter(xs, ys, s=5, c='r', alpha=0.5) ax.set_title("HSC PSF field={:5.2f}".format(theta / 3600.0)) ax.set_xlabel("arcsec") ax.set_ylabel("arcsec") fig.tight_layout() plt.show()
def test_HSC_huygensPSF(): fn = os.path.join(directory, "testdata", "HSC_huygensPSF.txt") with open(fn) as f: Zarr = np.loadtxt(f, skiprows=21) Zarr = Zarr[::-1] # Need to invert, probably just a Zemax convention... telescope = batoid.Optic.fromYaml("HSC_no_obsc.yaml") thx = np.deg2rad(0.0) thy = np.deg2rad(0.75) wavelength = 750e-9 nx = 512 dx = 0.25e-6 print("computing Huygens PSF") hPSF = batoid.huygensPSF(telescope, thx, thy, wavelength, nx=nx, projection='zemax', dx=dx, nxOut=256) print("Done") # Normalize images Zarr /= np.sum(Zarr) hPSF.array /= np.sum(hPSF.array) Zmax = np.max(Zarr) Zarr /= Zmax hPSF.array /= Zmax # Use GalSim InterpolateImage to align and subtract ii = galsim.InterpolatedImage(galsim.Image(hPSF.array, scale=0.25), normalization='sb') # Now setup an optimizer to fit for x/y shift def resid(params): p = params.valuesdict() model = ii.shift(p['dx'], p['dy']) * np.exp(p['dlogflux']) img = model.drawImage(method='sb', scale=0.25, nx=256, ny=256) r = (img.array - Zarr).ravel() return r params = lmfit.Parameters() params.add('dx', value=0.0) params.add('dy', value=0.0) params.add('dlogflux', value=0.0) print("Aligning") opt = lmfit.minimize(resid, params) print("Done") p = opt.params.valuesdict() model = ii.shift(p['dx'], p['dy']) * np.exp(p['dlogflux']) optImg = model.drawImage(method='sb', scale=0.25, nx=256, ny=256) np.testing.assert_allclose(Zarr, optImg.array, rtol=0, atol=3e-2) Zmom = galsim.hsm.FindAdaptiveMom(galsim.Image(Zarr, scale=0.25)) bmom = galsim.hsm.FindAdaptiveMom(optImg) np.testing.assert_allclose(Zmom.observed_shape.g1, bmom.observed_shape.g1, rtol=0, atol=0.01) np.testing.assert_allclose(Zmom.observed_shape.g2, bmom.observed_shape.g2, rtol=0, atol=1e-7) np.testing.assert_allclose(Zmom.moments_sigma, bmom.moments_sigma, rtol=0, atol=0.1)
def test_HSC_huygensPSF(): fn = os.path.join(directory, "testdata", "HSC_huygensPSF.txt") with open(fn) as f: Zarr = np.loadtxt(f, skiprows=21) Zarr = Zarr[::-1] # Need to invert, probably just a Zemax convention... telescope = batoid.Optic.fromYaml("HSC_no_obsc.yaml") thx = np.deg2rad(0.0) thy = np.deg2rad(0.75) wavelength = 750e-9 nx = 128 dx = 0.25e-6 print("computing Huygens PSF") hPSF = batoid.huygensPSF( telescope, thx, thy, projection='zemax', wavelength=wavelength, nx=nx, dx=dx, nxOut=256, reference='mean' ) print("Done") # Normalize images Zarr /= np.sum(Zarr) hPSF.array /= np.sum(hPSF.array) Zmax = np.max(Zarr) Zarr /= Zmax hPSF.array /= Zmax # Use GalSim InterpolateImage to align and subtract ii = galsim.InterpolatedImage( galsim.Image(hPSF.array, scale=0.25), normalization='sb' ) # Now setup an optimizer to fit for x/y shift def modelimg(params, ii=ii): dx, dy, dlogflux = params model = ii.shift(dx, dy)*np.exp(dlogflux) return model.drawImage(method='sb', scale=0.25, nx=256, ny=256) def resid(params, ii=ii, Zarr=Zarr): img = modelimg(params, ii=ii) r = (img.array - Zarr).ravel() return r kwargs = dict(ii=ii, Zarr=Zarr) print("Aligning") result = least_squares(resid, np.array([0.0, 0.0, 0.0]), kwargs=kwargs) optImg = modelimg(result.x, ii=ii) print("Done") np.testing.assert_allclose(Zarr, optImg.array, rtol=0, atol=3e-2) Zmom = galsim.hsm.FindAdaptiveMom(galsim.Image(Zarr, scale=0.25)) bmom = galsim.hsm.FindAdaptiveMom(optImg) np.testing.assert_allclose( Zmom.observed_shape.g1, bmom.observed_shape.g1, rtol=0, atol=0.01 ) np.testing.assert_allclose( Zmom.observed_shape.g2, bmom.observed_shape.g2, rtol=0, atol=1e-7 ) np.testing.assert_allclose( Zmom.moments_sigma, bmom.moments_sigma, rtol=0, atol=0.1 )
def test_LSST_huygensPSF(plot=False): thxs = [0.0, 0.0, 0.0, 1.176] thys = [0.0, 1.225, 1.75, 1.176] fns = ["LSST_hpsf_0.0_0.0.txt", "LSST_hpsf_0.0_1.225.txt", "LSST_hpsf_0.0_1.75.txt", "LSST_hpsf_1.176_1.176.txt"] if __name__ != "__main__": thxs = thxs[2:3] thys = thys[2:3] fns = fns[2:3] for thx, thy, fn in zip(thxs, thys, fns): fn = os.path.join(directory, "testdata", fn) with open(fn, encoding='utf-16-le') as f: Zpsf = np.loadtxt(f, skiprows=21) Zpsf = Zpsf[::-1] # Need to invert, probably just a Zemax convention... Zpsf /= np.max(Zpsf) telescope = batoid.Optic.fromYaml("LSST_g_500.yaml") thx = np.deg2rad(thx) thy = np.deg2rad(thy) wavelength = 500e-9 bpsf = batoid.huygensPSF( telescope, thx, thy, wavelength, nx=128, # telescope, thx, thy, wavelength, nx=1024, reference='chief', projection='zemax', dx=0.289e-6, nxOut=64 ) bpsf.array /= np.max(bpsf.array) # Use GalSim InterpolateImage to align and subtract ii = galsim.InterpolatedImage( galsim.Image(bpsf.array, scale=1.0), normalization='sb' ) # Now setup an optimizer to fit for x/y shift def modelimg(params, ii=ii): dx, dy, dlogflux = params model = ii.shift(dx, dy)*np.exp(dlogflux) return model.drawImage(method='sb', scale=1.0, nx=64, ny=64) def resid(params, ii=ii, Zpsf=Zpsf): img = modelimg(params, ii=ii) r = (img.array - Zpsf).ravel() return r kwargs = dict(ii=ii, Zpsf=Zpsf) print("Aligning") result = least_squares(resid, np.array([0.0, 0.0, 0.0]), kwargs=kwargs) optImg = modelimg(result.x, ii=ii) print("Done") if plot: import matplotlib.pyplot as plt fig, axes = plt.subplots(ncols=3, figsize=(10,3)) i0 = axes[0].imshow(optImg.array) i1 = axes[1].imshow(Zpsf) i2 = axes[2].imshow(optImg.array-Zpsf) plt.colorbar(i0, ax=axes[0]) plt.colorbar(i1, ax=axes[1]) plt.colorbar(i2, ax=axes[2]) plt.tight_layout() plt.show() if thy not in [0.0, 1.176]: fig, ax = plt.subplots(figsize=(6, 4)) ax.plot(optImg.array[:,32], c='g') ax.plot(Zpsf[:,32], c='b') ax.plot((optImg.array-Zpsf)[:,32], c='r') plt.show()
def test_huygens_paraboloid(plot=False): if __name__ == '__main__': obscurations = [0.0, 0.25, 0.5, 0.75] else: obscurations = [0.25] print("Testing HuygensPSF") # Just do a single parabolic mirror test focalLength = 1.5 diam = 0.3 R = 2*focalLength for obscuration in obscurations: telescope = batoid.CompoundOptic( items = [ batoid.Mirror( batoid.Paraboloid(R), name="Mirror", obscuration=batoid.ObscNegation( batoid.ObscAnnulus(0.5*obscuration*diam, 0.5*diam) ) ), batoid.Detector( batoid.Plane(), name="detector", coordSys=batoid.CoordSys(origin=[0, 0, focalLength]) ) ], pupilSize=diam, backDist=10.0, inMedium=batoid.ConstMedium(1.0) ) airy_size = 1.22*500e-9/diam * 206265 print() print("Airy radius: {:4.2f} arcsec".format(airy_size)) # Start with the HuygensPSF npix = 96 size = 3.0 # arcsec dsize = size/npix dsize_X = dsize*focalLength/206265 # meters psf = batoid.huygensPSF( telescope, 0.0, 0.0, 500e-9, nx=npix, dx=dsize_X, dy=dsize_X ) psf.array /= np.max(psf.array) scale = np.sqrt(np.abs(np.linalg.det(psf.primitiveVectors))) # meters scale *= 206265/focalLength # arcsec obj = galsim.Airy(lam=500, diam=diam, obscuration=obscuration) # Need to shift by half a pixel. obj = obj.shift(scale/2, scale/2) im = obj.drawImage(nx=npix, ny=npix, scale=scale, method='no_pixel') arr = im.array/np.max(im.array) gs_mom = galsim.hsm.FindAdaptiveMom(im) psfim = galsim.Image(psf.array) jt_mom = galsim.hsm.FindAdaptiveMom(psfim) print("GalSim shape: ", gs_mom.observed_shape) print("batoid shape: ", jt_mom.observed_shape) print("GalSim centroid: ", gs_mom.moments_centroid) print("batoid centroid: ", jt_mom.moments_centroid) print("GalSim size: ", gs_mom.moments_sigma) print("batoid size: ", jt_mom.moments_sigma) print("GalSim rho4: ", gs_mom.moments_rho4) print("batoid rho4: ", jt_mom.moments_rho4) np.testing.assert_allclose( gs_mom.observed_shape.g1, jt_mom.observed_shape.g1, rtol=0.0, atol=3e-3 ) np.testing.assert_allclose( gs_mom.observed_shape.g2, jt_mom.observed_shape.g2, rtol=0.0, atol=3e-3 ) np.testing.assert_allclose( gs_mom.moments_centroid.x, jt_mom.moments_centroid.x, rtol=0.0, atol=1e-9 ) np.testing.assert_allclose( gs_mom.moments_centroid.y, jt_mom.moments_centroid.y, rtol=0.0, atol=1e-9 ) np.testing.assert_allclose( gs_mom.moments_sigma, jt_mom.moments_sigma, rtol=1e-2 # why not better?! ) np.testing.assert_allclose( gs_mom.moments_rho4, jt_mom.moments_rho4, rtol=2e-2 ) if plot: size = scale*npix import matplotlib.pyplot as plt fig = plt.figure(figsize=(15, 4)) ax1 = fig.add_subplot(131) im1 = ax1.imshow( np.log10(arr), extent=np.r_[-1,1,-1,1]*size/2, vmin=-7, vmax=0 ) plt.colorbar(im1, ax=ax1, label=r'$\log_{10}$ flux') ax1.set_title('GalSim') ax1.set_xlabel("arcsec") ax1.set_ylabel("arcsec") sizeX = dsize_X * npix * 1e6 # microns ax2 = fig.add_subplot(132) im2 = ax2.imshow( np.log10(psf.array), extent=np.r_[-1,1,-1,1]*sizeX/2, vmin=-7, vmax=0 ) plt.colorbar(im2, ax=ax2, label=r'$\log_{10}$ flux') ax2.set_title('batoid') ax2.set_xlabel(r"$\mu m$") ax2.set_ylabel(r"$\mu m$") ax3 = fig.add_subplot(133) im3 = ax3.imshow( (psf.array-arr)/np.max(arr), vmin=-0.01, vmax=0.01, cmap='seismic' ) plt.colorbar(im3, ax=ax3, label="(batoid-GalSim)/max(GalSim)") ax3.set_title('resid') ax3.set_xlabel(r"$\mu m$") ax3.set_ylabel(r"$\mu m$") fig.tight_layout() plt.show()
def test_huygensPSF(): telescope = batoid.Optic.fromYaml("LSST_r.yaml") # Test that we can infer dy from dx properly psf1 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=10e-6, reference='mean') psf2 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=10e-6, dy=10e-6, reference='mean') assert np.array_equal(psf1.primitiveVectors, psf2.primitiveVectors) np.testing.assert_allclose(psf1.array, psf2.array, rtol=1e-14, atol=1e-15) # Test vector vs scalar dx,dy psf1 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=[10e-6, 0], dy=[0, 11e-6], reference='mean') psf2 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=10e-6, dy=11e-6, reference='mean') assert np.array_equal(psf1.primitiveVectors, psf2.primitiveVectors) np.testing.assert_allclose(psf1.array, psf2.array, rtol=1e-14, atol=1e-15) # Should still work with reference = 'chief' psf3 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=[10e-6, 0], dy=[0, 11e-6], reference='chief') psf4 = batoid.huygensPSF(telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=64, nxOut=32, dx=10e-6, dy=11e-6, reference='chief') assert np.array_equal(psf1.primitiveVectors, psf3.primitiveVectors) assert not np.allclose(psf1.array, psf3.array, rtol=1e-14, atol=1e-15) assert np.array_equal(psf3.primitiveVectors, psf4.primitiveVectors) np.testing.assert_allclose(psf3.array, psf4.array, rtol=1e-14, atol=1e-15) # And just cover nx odd batoid.huygensPSF( telescope, np.deg2rad(0.1), np.deg2rad(0.1), 620e-9, nx=63, )