Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2This file defines the model classes that wrap PSFs from 

3galsim into the CatSim interface 

4""" 

5 

6from builtins import object 

7import numpy 

8import galsim 

9 

10__all__ = ["PSFbase", "DoubleGaussianPSF", "SNRdocumentPSF", 

11 "Kolmogorov_and_Gaussian_PSF"] 

12 

13class PSFbase(object): 

14 """ 

15 This is the base class for wrappers of GalSim's PSF classes. To apply a PSF to GalSim images 

16 using the GalSim Instance Catalog and GalSim Interpreter, the user must define a daughter 

17 class of this class and instantiate it as the member variable self.PSF in the GalSim Instance Catalog. 

18 

19 Any Daughter class of this class must have a member method _getPSF which accepts the coordinates 

20 xPupil and yPupil in arcseconds as kwargs. This method will instantiate a psf object at those 

21 coordinates and return it. 

22 

23 The method applyPSF is defined in this class and should not be overwritten. It handles the task of actually 

24 convolving the PSF returned by _getPSF. 

25 

26 Consult GalSim's documentation to see what kinds of PSFs are available. 

27 

28 See the classes DoubleGaussianPSF and SNRdocumentPSF below for example implementations. 

29 

30 See galSimCompoundGenerator.py and galSimStarGenerator.py for example usages. 

31 """ 

32 

33 def _getPSF(self, xPupil=None, yPupil=None): 

34 """ 

35 If it had been implemented, this would return a GalSim PSF instantiation at the 

36 coordinates and wavelength specified and returned it to applyPSF. As it is, this 

37 class has not been implemented and is left to the user to implement in Daughter 

38 classes of PSFbase. 

39 

40 @param [in] xPupil the x coordinate on the pupil in arc seconds 

41 

42 @param [in] yPupil the y coordinate on the pupil in arc seconds 

43 """ 

44 

45 raise NotImplementedError("There is not _getPSF for PSFbase; define a daughter class and define your own") 

46 

47 def applyPSF(self, xPupil=None, yPupil=None, obj=None, **kwargs): 

48 """ 

49 Apply the PSF to a GalSim GSObject 

50 

51 This method accepts the x and y pupil coordinates in arc seconds as well 

52 as a GalSim GSObject. The method calculates the PSF parameters based on xPupil 

53 and yPupil, constructs a Galsim GSObject corresponding to the PSF function, and convolves 

54 the PSF with the GSObject, returning the result of the convolution. 

55 

56 In the case of point sources, this object returns the raw PSF, rather than attempting 

57 a convolution (since there is nothing to convolve with). 

58 

59 @param [in] xPupil the x pupil coordinate in arc seconds 

60 

61 @param [in] yPupil the y pupil coordinate in arc seconds 

62 

63 @param [in] obj is a GalSim GSObject (an astronomical object) with which 

64 to convolve the PSF (optional) 

65 """ 

66 

67 #use the user-defined _getPSF method to calculate the PSF at these specific 

68 #coordinates and (optionally) wavelength 

69 psf = self._getPSF(xPupil=xPupil, yPupil=yPupil, **kwargs) 

70 

71 if obj is None: 

72 #if there is no object, use a DeltaFunction as a point source 

73 obj = galsim.DeltaFunction() 

74 

75 #convolve obj with the psf 

76 if isinstance(psf, galsim.Convolution): 

77 # If the psf is itself a Convolution object, convolve obj 

78 # with the individual components to ensure that the 

79 # obj_list of the returned obj lists those components 

80 # separately. 

81 return galsim.Convolution([obj] + psf.obj_list) 

82 else: 

83 return galsim.Convolve(obj, psf) 

84 

85 def __eq__(self, rhs): 

86 """ 

87 Compare types and underlying galsim ._cached_psf attributes for 

88 equality test. 

89 """ 

90 return (type(self) == type(rhs) 

91 and self._cached_psf == rhs._cached_psf) 

92 

93class DoubleGaussianPSF(PSFbase): 

94 """ 

95 This is an example implementation of a wavelength- and position-independent 

96 Double Gaussian PSF. See the documentation in PSFbase to learn how it is used. 

97 """ 

98 

99 def __init__(self, fwhm1=0.6, fwhm2=0.12, wgt1=1.0, wgt2=0.1): 

100 """ 

101 @param [in] fwhm1 is the Full Width at Half Max of the first Gaussian in arcseconds 

102 

103 @param [in] fwhm2 is the Full Width at Half Max of the second Gaussian in arcseconds 

104 

105 @param [in] wgt1 is the dimensionless coefficient normalizing the first Gaussian 

106 

107 @param [in] wgt2 is the dimensionless coefficient normalizing the second Gaussian 

108 

109 The total PSF will be 

110 

111 (wgt1 * G(sig1) + wgt2 * G(sig2))/(wgt1 + wgt2) 

112 

113 where G(sigN) denotes a normalized Gaussian with a standard deviation that gives 

114 a Full Width at Half Max of fwhmN. (Integrating a two-dimensional Gaussian, we find 

115 that sig = fwhm/2.355) 

116 

117 Because this PSF depends on neither position nor wavelength, this __init__ method 

118 will instantiate a PSF and cache it. It is this cached psf that will be returned 

119 whenever _getPSF is called in this class. 

120 """ 

121 

122 r1 = fwhm1/2.355 

123 r2 = fwhm2/2.355 

124 norm = 1.0/(wgt1 + wgt2) 

125 

126 gaussian1 = galsim.Gaussian(sigma=r1) 

127 gaussian2 = galsim.Gaussian(sigma=r2) 

128 

129 self._cached_psf = norm*(wgt1*gaussian1 + wgt2*gaussian2) 

130 

131 def _getPSF(self, xPupil=None, yPupil=None, **kwargs): 

132 """ 

133 Return a the PSF to be convolved with sources. 

134 

135 @param [in] xPupil the x coordinate on the pupil in arc seconds 

136 

137 @param [in] yPupil the y coordinate on the pupil in arc seconds 

138 

139 Because this specific PSF depends on neither wavelength nor position, 

140 it will just return the cached PSF function. 

141 """ 

142 return self._cached_psf 

143 

144 

145 

146class SNRdocumentPSF(DoubleGaussianPSF): 

147 """ 

148 This is an example implementation of a wavelength- and position-independent 

149 Double Gaussian PSF. See the documentation in PSFbase to learn how it is used. 

150 

151 This specific PSF comes from equation(30) of the signal-to-noise document (LSE-40), 

152 which can be found at 

153 

154 www.astro.washington.edu/users/ivezic/Astr511/LSST_SNRdoc.pdf 

155 """ 

156 

157 def __init__(self, fwhm=0.6, pixel_scale=0.2, gsparams=None): 

158 """ 

159 @param [in] fwhm is the Full Width at Half Max of the total PSF. This is given in 

160 arcseconds. The default value of 0.6 comes from a FWHM of 3 pixels with a pixel scale 

161 of 0.2 arcseconds per pixel. 

162 

163 Because this PSF depends on neither position nor wavelength, this __init__ method 

164 will instantiate a PSF and cache it. It is this cached psf that will be returned 

165 whenever _getPSF is called in this class. 

166 """ 

167 

168 #the expression below is derived by solving equation (30) of the signal-to-noise 

169 #document (www.astro.washington.edu/uses/ivezic/Astr511/LSST_SNRdoc.pdf) 

170 #for r at half the maximum of the PSF 

171 alpha = fwhm/2.3835 

172 

173 eff_pixel_sigma_sq = pixel_scale*pixel_scale/12.0 

174 

175 sigma = numpy.sqrt(alpha*alpha - eff_pixel_sigma_sq) 

176 gaussian1 = galsim.Gaussian(sigma=sigma, gsparams=gsparams) 

177 

178 sigma = numpy.sqrt(4.0*alpha*alpha - eff_pixel_sigma_sq) 

179 gaussian2 = galsim.Gaussian(sigma=sigma, gsparams=gsparams) 

180 

181 self._cached_psf = 0.909*(gaussian1 + 0.1*gaussian2) 

182 

183 

184class Kolmogorov_and_Gaussian_PSF(PSFbase): 

185 """ 

186 This PSF class is based on David Kirkby's presentation to the DESC Survey Simulations 

187 working group on 23 March 2017. 

188 

189 https://confluence.slac.stanford.edu/pages/viewpage.action?spaceKey=LSSTDESC&title=SSim+2017-03-23 

190 

191 (you will need a SLAC Confluence account to access that link) 

192 """ 

193 

194 def __init__(self, airmass=1.2, rawSeeing=0.7, band='r', gsparams=None): 

195 """ 

196 Parameters 

197 ---------- 

198 airmass 

199 

200 rawSeeing is the FWHM seeing at zenith at 500 nm in arc seconds 

201 (provided by OpSim) 

202 

203 band is the bandpass of the observation [u,g,r,i,z,y] 

204 """ 

205 # This code was provided by David Kirkby in a private communication 

206 

207 wlen_eff = dict(u=365.49, g=480.03, r=622.20, i=754.06, z=868.21, y=991.66)[band] 

208 # wlen_eff is from Table 2 of LSE-40 (y=y2) 

209 

210 FWHMatm = rawSeeing * (wlen_eff / 500.) ** -0.3 * airmass ** 0.6 

211 # From LSST-20160 eqn (4.1) 

212 

213 FWHMsys = numpy.sqrt(0.25**2 + 0.3**2 + 0.08**2) * airmass ** 0.6 

214 # From LSST-20160 eqn (4.2) 

215 

216 atm = galsim.Kolmogorov(fwhm=FWHMatm, gsparams=gsparams) 

217 sys = galsim.Gaussian(fwhm=FWHMsys, gsparams=gsparams) 

218 psf = galsim.Convolve((atm, sys)) 

219 

220 self._cached_psf = psf 

221 

222 def _getPSF(self, xPupil=None, yPupil=None, **kwargs): 

223 return self._cached_psf