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# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23import math 

24import numpy as np 

25import unittest 

26 

27import lsst.utils.tests 

28import lsst.afw.image as afwImage 

29import lsst.afw.detection as afwDetection 

30import lsst.afw.geom as afwGeom 

31import lsst.geom as geom 

32import lsst.afw.math as afwMath 

33import lsst.afw.table as afwTable 

34import lsst.daf.base as dafBase 

35import lsst.meas.algorithms as measAlg 

36from lsst.meas.base import SingleFrameMeasurementTask 

37# register the PSF determiner 

38import lsst.meas.extensions.psfex.psfexPsfDeterminer 

39assert lsst.meas.extensions.psfex.psfexPsfDeterminer # make pyflakes happy 

40 

41try: 

42 display 

43except NameError: 

44 display = False 

45else: 

46 import lsst.afw.display as afwDisplay 

47 afwDisplay.setDefaultMaskTransparency(75) 

48 

49 

50def psfVal(ix, iy, x, y, sigma1, sigma2, b): 

51 """Return the value at (ix, iy) of a double Gaussian 

52 (N(0, sigma1^2) + b*N(0, sigma2^2))/(1 + b) 

53 centered at (x, y) 

54 """ 

55 dx, dy = x - ix, y - iy 

56 theta = np.radians(30) 

57 ab = 1.0/0.75 # axis ratio 

58 c, s = np.cos(theta), np.sin(theta) 

59 u, v = c*dx - s*dy, s*dx + c*dy 

60 

61 return (math.exp(-0.5*(u**2 + (v*ab)**2)/sigma1**2) 

62 + b*math.exp(-0.5*(u**2 + (v*ab)**2)/sigma2**2))/(1 + b) 

63 

64 

65class SpatialModelPsfTestCase(unittest.TestCase): 

66 """A test case for SpatialModelPsf""" 

67 

68 def measure(self, footprintSet, exposure): 

69 """Measure a set of Footprints, returning a SourceCatalog""" 

70 catalog = afwTable.SourceCatalog(self.schema) 

71 if display: 

72 afwDisplay.Display(frame=0).mtv(exposure, title="Original") 

73 

74 footprintSet.makeSources(catalog) 

75 

76 self.measureSources.run(catalog, exposure) 

77 return catalog 

78 

79 def setUp(self): 

80 config = SingleFrameMeasurementTask.ConfigClass() 

81 config.slots.apFlux = 'base_CircularApertureFlux_12_0' 

82 self.schema = afwTable.SourceTable.makeMinimalSchema() 

83 

84 self.measureSources = SingleFrameMeasurementTask(self.schema, config=config) 

85 

86 width, height = 110, 301 

87 

88 self.mi = afwImage.MaskedImageF(geom.ExtentI(width, height)) 

89 self.mi.set(0) 

90 sd = 3 # standard deviation of image 

91 self.mi.getVariance().set(sd*sd) 

92 self.mi.getMask().addMaskPlane("DETECTED") 

93 

94 self.ksize = 31 # size of desired kernel 

95 

96 sigma1 = 1.75 

97 sigma2 = 2*sigma1 

98 

99 self.exposure = afwImage.makeExposure(self.mi) 

100 self.exposure.setPsf(measAlg.DoubleGaussianPsf(self.ksize, self.ksize, 

101 1.5*sigma1, 1, 0.1)) 

102 cdMatrix = np.array([1.0, 0.0, 0.0, 1.0]) 

103 cdMatrix.shape = (2, 2) 

104 wcs = afwGeom.makeSkyWcs(crpix=geom.PointD(0, 0), 

105 crval=geom.SpherePoint(0.0, 0.0, geom.degrees), 

106 cdMatrix=cdMatrix) 

107 self.exposure.setWcs(wcs) 

108 

109 # 

110 # Make a kernel with the exactly correct basis functions. 

111 # Useful for debugging 

112 # 

113 basisKernelList = [] 

114 for sigma in (sigma1, sigma2): 

115 basisKernel = afwMath.AnalyticKernel(self.ksize, self.ksize, 

116 afwMath.GaussianFunction2D(sigma, sigma)) 

117 basisImage = afwImage.ImageD(basisKernel.getDimensions()) 

118 basisKernel.computeImage(basisImage, True) 

119 basisImage /= np.sum(basisImage.getArray()) 

120 

121 if sigma == sigma1: 

122 basisImage0 = basisImage 

123 else: 

124 basisImage -= basisImage0 

125 

126 basisKernelList.append(afwMath.FixedKernel(basisImage)) 

127 

128 order = 1 # 1 => up to linear 

129 spFunc = afwMath.PolynomialFunction2D(order) 

130 

131 exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc) 

132 exactKernel.setSpatialParameters([[1.0, 0, 0], 

133 [0.0, 0.5*1e-2, 0.2e-2]]) 

134 

135 rand = afwMath.Random() # make these tests repeatable by setting seed 

136 

137 addNoise = True 

138 

139 if addNoise: 

140 im = self.mi.getImage() 

141 afwMath.randomGaussianImage(im, rand) # N(0, 1) 

142 im *= sd # N(0, sd^2) 

143 del im 

144 

145 xarr, yarr = [], [] 

146 

147 for x, y in [(20, 20), (60, 20), 

148 (30, 35), 

149 (50, 50), 

150 (20, 90), (70, 160), (25, 265), (75, 275), (85, 30), 

151 (50, 120), (70, 80), 

152 (60, 210), (20, 210), 

153 ]: 

154 xarr.append(x) 

155 yarr.append(y) 

156 

157 for x, y in zip(xarr, yarr): 

158 dx = rand.uniform() - 0.5 # random (centered) offsets 

159 dy = rand.uniform() - 0.5 

160 

161 k = exactKernel.getSpatialFunction(1)(x, y) # functional variation of Kernel ... 

162 b = (k*sigma1**2/((1 - k)*sigma2**2)) # ... converted double Gaussian's "b" 

163 

164 # flux = 80000 - 20*x - 10*(y/float(height))**2 

165 flux = 80000*(1 + 0.1*(rand.uniform() - 0.5)) 

166 I0 = flux*(1 + b)/(2*np.pi*(sigma1**2 + b*sigma2**2)) 

167 for iy in range(y - self.ksize//2, y + self.ksize//2 + 1): 

168 if iy < 0 or iy >= self.mi.getHeight(): 

169 continue 

170 

171 for ix in range(x - self.ksize//2, x + self.ksize//2 + 1): 

172 if ix < 0 or ix >= self.mi.getWidth(): 

173 continue 

174 

175 II = I0*psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b) 

176 Isample = rand.poisson(II) if addNoise else II 

177 self.mi.image[ix, iy, afwImage.LOCAL] += Isample 

178 self.mi.variance[ix, iy, afwImage.LOCAL] += II 

179 

180 bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height)) 

181 self.cellSet = afwMath.SpatialCellSet(bbox, 100) 

182 

183 self.footprintSet = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(100), "DETECTED") 

184 

185 self.catalog = self.measure(self.footprintSet, self.exposure) 

186 

187 for source in self.catalog: 

188 try: 

189 cand = measAlg.makePsfCandidate(source, self.exposure) 

190 self.cellSet.insertCandidate(cand) 

191 

192 except Exception as e: 

193 print(e) 

194 continue 

195 

196 def tearDown(self): 

197 del self.cellSet 

198 del self.exposure 

199 del self.mi 

200 del self.footprintSet 

201 del self.catalog 

202 del self.schema 

203 del self.measureSources 

204 

205 def setupDeterminer(self, exposure): 

206 """Setup the starSelector and psfDeterminer""" 

207 starSelectorClass = measAlg.sourceSelectorRegistry["objectSize"] 

208 starSelectorConfig = starSelectorClass.ConfigClass() 

209 starSelectorConfig.sourceFluxField = "base_GaussianFlux_instFlux" 

210 starSelectorConfig.badFlags = ["base_PixelFlags_flag_edge", 

211 "base_PixelFlags_flag_interpolatedCenter", 

212 "base_PixelFlags_flag_saturatedCenter", 

213 "base_PixelFlags_flag_crCenter", 

214 ] 

215 starSelectorConfig.widthStdAllowed = 0.5 # Set to match when the tolerance of the test was set 

216 

217 self.starSelector = starSelectorClass(config=starSelectorConfig) 

218 

219 self.makePsfCandidates = measAlg.MakePsfCandidatesTask() 

220 

221 psfDeterminerClass = measAlg.psfDeterminerRegistry["psfex"] 

222 psfDeterminerConfig = psfDeterminerClass.ConfigClass() 

223 width, height = exposure.getMaskedImage().getDimensions() 

224 psfDeterminerConfig.sizeCellX = width 

225 psfDeterminerConfig.sizeCellY = height//3 

226 psfDeterminerConfig.spatialOrder = 1 

227 

228 self.psfDeterminer = psfDeterminerClass(psfDeterminerConfig) 

229 

230 def subtractStars(self, exposure, catalog, chi_lim=-1): 

231 """Subtract the exposure's PSF from all the sources in catalog""" 

232 mi, psf = exposure.getMaskedImage(), exposure.getPsf() 

233 

234 subtracted = mi.Factory(mi, True) 

235 for s in catalog: 

236 xc, yc = s.getX(), s.getY() 

237 bbox = subtracted.getBBox(afwImage.PARENT) 

238 if bbox.contains(geom.PointI(int(xc), int(yc))): 

239 try: 

240 measAlg.subtractPsf(psf, subtracted, xc, yc) 

241 except Exception: 

242 pass 

243 chi = subtracted.Factory(subtracted, True) 

244 var = subtracted.getVariance() 

245 np.sqrt(var.getArray(), var.getArray()) # inplace sqrt 

246 chi /= var 

247 

248 if display: 

249 afwDisplay.Display(frame=1).mtv(subtracted, title="Subtracted") 

250 afwDisplay.Display(frame=2).mtv(chi, title="Chi") 

251 xc, yc = exposure.getWidth()//2, exposure.getHeight()//2 

252 afwDisplay.Display(frame=3).mtv(psf.computeImage(geom.Point2D(xc, yc)), 

253 title="Psf %.1f,%.1f" % (xc, yc)) 

254 

255 chi_min, chi_max = np.min(chi.getImage().getArray()), np.max(chi.getImage().getArray()) 

256 if False: 

257 print(chi_min, chi_max) 

258 

259 if chi_lim > 0: 

260 self.assertGreater(chi_min, -chi_lim) 

261 self.assertLess(chi_max, chi_lim) 

262 

263 def testPsfexDeterminer(self): 

264 """Test the (Psfex) psfDeterminer on subImages""" 

265 

266 self.setupDeterminer(self.exposure) 

267 metadata = dafBase.PropertyList() 

268 

269 stars = self.starSelector.run(self.catalog, exposure=self.exposure) 

270 psfCandidateList = self.makePsfCandidates.run(stars.sourceCat, exposure=self.exposure).psfCandidates 

271 psf, cellSet = self.psfDeterminer.determinePsf(self.exposure, psfCandidateList, metadata) 

272 self.exposure.setPsf(psf) 

273 

274 # Test how well we can subtract the PSF model 

275 self.subtractStars(self.exposure, self.catalog, chi_lim=5.6) 

276 

277 # Test PsfexPsf.computeBBox 

278 self.assertEqual(psf.computeBBox(), psf.computeKernelImage().getBBox()) 

279 self.assertEqual(psf.computeBBox(), psf.getKernel().getBBox()) 

280 

281 

282class TestMemory(lsst.utils.tests.MemoryTestCase): 

283 pass 

284 

285 

286def setup_module(module): 

287 lsst.utils.tests.init() 

288 

289 

290if __name__ == "__main__": 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true

291 lsst.utils.tests.init() 

292 unittest.main()