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#!/usr/bin/env python 

2# 

3# LSST Data Management System 

4# 

5# Copyright 2008-2016 AURA/LSST. 

6# 

7# This product includes software developed by the 

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

9# 

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

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

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

13# (at your option) any later version. 

14# 

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

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

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

18# GNU General Public License for more details. 

19# 

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

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

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

23# 

24import os 

25import numpy as np 

26import unittest 

27import itertools 

28 

29import lsst.afw.image as afwImage 

30import lsst.afw.math as afwMath 

31from lsst.daf.base import PropertySet 

32import lsst.meas.base as base 

33import lsst.meas.algorithms as algorithms 

34import lsst.afw.detection as afwDetection 

35import lsst.afw.table as afwTable 

36import lsst.afw.geom as afwGeom 

37import lsst.geom as geom 

38import lsst.afw.geom.ellipses as afwEll 

39import lsst.utils.tests 

40import lsst.meas.extensions.shapeHSM 

41 

42SIZE_DECIMALS = 2 # Number of decimals for equality in sizes 

43SHAPE_DECIMALS = 3 # Number of decimals for equality in shapes 

44 

45# The following values are pulled directly from GalSim's test_hsm.py: 

46file_indices = [0, 2, 4, 6, 8] 

47x_centroid = [35.888, 19.44, 8.74, 20.193, 57.94] 

48y_centroid = [19.845, 25.047, 11.92, 38.93, 27.73] 

49sky_var = [35.01188, 35.93418, 35.15456, 35.11146, 35.16454] 

50correction_methods = ["KSB", "BJ", "LINEAR", "REGAUSS"] 

51# Note: expected results give shear for KSB and distortion for others, but the results below have 

52# converted KSB expected results to distortion for the sake of consistency 

53e1_expected = np.array([ 

54 [0.467603106752, 0.381211727, 0.398856937, 0.401755571], 

55 [0.28618443944, 0.199222784, 0.233883543, 0.234257525], 

56 [0.271533794146, 0.158049396, 0.183517068, 0.184893412], 

57 [-0.293754156071, -0.457024541, 0.123946584, -0.609233462], 

58 [0.557720893779, 0.374143023, 0.714147448, 0.435404409]]) 

59e2_expected = np.array([ 

60 [-0.867225166489, -0.734855778, -0.777027588, -0.774684891], 

61 [-0.469354341577, -0.395520479, -0.502540961, -0.464466257], 

62 [-0.519775291311, -0.471589061, -0.574750641, -0.529664935], 

63 [0.345688365839, -0.342047099, 0.120603755, -0.44609129428863525], 

64 [0.525728304099, 0.370691830, 0.702724807, 0.433999442]]) 

65resolution_expected = np.array([ 

66 [0.796144249, 0.835624917, 0.835624917, 0.827796187], 

67 [0.685023735, 0.699602704, 0.699602704, 0.659457638], 

68 [0.634736458, 0.651040481, 0.651040481, 0.614663396], 

69 [0.477027015, 0.477210752, 0.477210752, 0.423157447], 

70 [0.595205998, 0.611824797, 0.611824797, 0.563582092]]) 

71sigma_e_expected = np.array([ 

72 [0.016924826, 0.014637648, 0.014637648, 0.014465546], 

73 [0.075769504, 0.073602324, 0.073602324, 0.064414520], 

74 [0.110253112, 0.106222900, 0.106222900, 0.099357106], 

75 [0.185276702, 0.184300955, 0.184300955, 0.173478300], 

76 [0.073020065, 0.070270966, 0.070270966, 0.061856263]]) 

77# End of GalSim's values 

78 

79# These values calculated using GalSim's HSM as part of GalSim 

80galsim_e1 = np.array([ 

81 [0.399292618036, 0.381213068962, 0.398856908083, 0.401749581099], 

82 [0.155929282308, 0.199228107929, 0.233882278204, 0.234371587634], 

83 [0.150018423796, 0.158052951097, 0.183515056968, 0.184561833739], 

84 [-2.6984937191, -0.457033962011, 0.123932465911, -0.60886412859], 

85 [0.33959621191, 0.374140143394, 0.713756918907, 0.43560180068], 

86]) 

87galsim_e2 = np.array([ 

88 [-0.74053555727, -0.734855830669, -0.777024209499, -0.774700462818], 

89 [-0.25573053956, -0.395517915487, -0.50251352787, -0.464388132095], 

90 [-0.287168383598, -0.471584022045, -0.574719130993, -0.5296921134], 

91 [3.1754450798, -0.342054128647, 0.120592080057, -0.446093201637], 

92 [0.320115834475, 0.370669454336, 0.702303349972, 0.433968126774], 

93]) 

94galsim_resolution = np.array([ 

95 [0.79614430666, 0.835625052452, 0.835625052452, 0.827822327614], 

96 [0.685023903847, 0.699601829052, 0.699601829052, 0.659438848495], 

97 [0.634736537933, 0.651039719582, 0.651039719582, 0.614759743214], 

98 [0.477026551962, 0.47721144557, 0.47721144557, 0.423227936029], 

99 [0.595205545425, 0.611821532249, 0.611821532249, 0.563564240932], 

100]) 

101galsim_err = np.array([ 

102 [0.0169247947633, 0.0146376201883, 0.0146376201883, 0.0144661813974], 

103 [0.0757696777582, 0.0736026018858, 0.0736026018858, 0.0644160583615], 

104 [0.110252402723, 0.106222368777, 0.106222368777, 0.0993555411696], 

105 [0.185278102756, 0.184301897883, 0.184301897883, 0.17346136272], 

106 [0.0730196461082, 0.0702708885074, 0.0702708885074, 0.0618583671749], 

107]) 

108 

109moments_expected = np.array([ # sigma, e1, e2 

110 [2.24490427971, 0.336240686301, -0.627372910656], 

111 [1.9031778574, 0.150566105384, -0.245272792302], 

112 [1.77790760994, 0.112286123389, -0.286203939641], 

113 [1.45464873314, -0.155597168978, -0.102008266223], 

114 [1.63144648075, 0.22886961923, 0.228813588897], 

115]) 

116centroid_expected = np.array([ # x, y 

117 [36.218247328, 20.5678722157], 

118 [20.325744838, 25.4176650386], 

119 [9.54257706283, 12.6134786199], 

120 [20.6407850048, 39.5864802706], 

121 [58.5008586442, 28.2850942049], 

122]) 

123 

124round_moments_expected = np.array([ # sigma, e1, e2, flux, x, y 

125 [2.40270376205, 0.197810277343, -0.372329413891, 3740.22436523, 36.4032272633, 20.4847916447], 

126 [1.89714717865, 0.046496052295, -0.0987404286861, 776.709594727, 20.2893584046, 25.4230368047], 

127 [1.77995181084, 0.0416346564889, -0.143147706985, 534.59197998, 9.51994111869, 12.6250775205], 

128 [1.46549296379, -0.0831127092242, -0.0628845766187, 348.294403076, 20.6242279632, 39.5941625731], 

129 [1.64031589031, 0.0867517963052, 0.0940798297524, 793.374450684, 58.4728765002, 28.2686937854], 

130]) 

131 

132 

133def makePluginAndCat(alg, name, control=None, metadata=False, centroid=None): 

134 print("Making plugin ", alg, name) 

135 if control is None: 

136 control = alg.ConfigClass() 

137 schema = afwTable.SourceTable.makeMinimalSchema() 

138 if centroid: 

139 schema.addField(centroid + "_x", type=float) 

140 schema.addField(centroid + "_y", type=float) 

141 schema.addField(centroid + "_flag", type='Flag') 

142 schema.getAliasMap().set("slot_Centroid", centroid) 

143 if metadata: 

144 plugin = alg(control, name, schema, PropertySet()) 

145 else: 

146 plugin = alg(control, name, schema) 

147 cat = afwTable.SourceCatalog(schema) 

148 if centroid: 

149 cat.defineCentroid(centroid) 

150 return plugin, cat 

151 

152 

153class ShapeTestCase(unittest.TestCase): 

154 """A test case for shape measurement""" 

155 

156 def setUp(self): 

157 

158 # load the known values 

159 self.dataDir = os.path.join(os.getenv('MEAS_EXTENSIONS_SHAPEHSM_DIR'), "tests", "data") 

160 self.bkgd = 1000.0 # standard for atlas image 

161 self.offset = geom.Extent2I(1234, 1234) 

162 self.xy0 = geom.Point2I(5678, 9876) 

163 

164 def tearDown(self): 

165 del self.offset 

166 del self.xy0 

167 

168 def runMeasurement(self, algorithmName, imageid, x, y, v): 

169 """Run the measurement algorithm on an image""" 

170 # load the test image 

171 imgFile = os.path.join(self.dataDir, "image.%d.fits" % imageid) 

172 img = afwImage.ImageF(imgFile) 

173 img -= self.bkgd 

174 nx, ny = img.getWidth(), img.getHeight() 

175 msk = afwImage.Mask(geom.Extent2I(nx, ny), 0x0) 

176 var = afwImage.ImageF(geom.Extent2I(nx, ny), v) 

177 mimg = afwImage.MaskedImageF(img, msk, var) 

178 msk.getArray()[:] = np.where(np.fabs(img.getArray()) < 1.0e-8, msk.getPlaneBitMask("BAD"), 0) 

179 

180 # Put it in a bigger image, in case it matters 

181 big = afwImage.MaskedImageF(self.offset + mimg.getDimensions()) 

182 big.getImage().set(0) 

183 big.getMask().set(0) 

184 big.getVariance().set(v) 

185 subBig = afwImage.MaskedImageF(big, geom.Box2I(big.getXY0() + self.offset, mimg.getDimensions())) 

186 subBig.assign(mimg) 

187 mimg = big 

188 mimg.setXY0(self.xy0) 

189 

190 exposure = afwImage.makeExposure(mimg) 

191 cdMatrix = np.array([1.0/(2.53*3600.0), 0.0, 0.0, 1.0/(2.53*3600.0)]) 

192 cdMatrix.shape = (2, 2) 

193 exposure.setWcs(afwGeom.makeSkyWcs(crpix=geom.Point2D(1.0, 1.0), 

194 crval=geom.SpherePoint(0, 0, geom.degrees), 

195 cdMatrix=cdMatrix)) 

196 

197 # load the corresponding test psf 

198 psfFile = os.path.join(self.dataDir, "psf.%d.fits" % imageid) 

199 psfImg = afwImage.ImageD(psfFile) 

200 psfImg -= self.bkgd 

201 

202 kernel = afwMath.FixedKernel(psfImg) 

203 kernelPsf = algorithms.KernelPsf(kernel) 

204 exposure.setPsf(kernelPsf) 

205 

206 # perform the shape measurement 

207 msConfig = base.SingleFrameMeasurementConfig() 

208 alg = base.SingleFramePlugin.registry[algorithmName].PluginClass.AlgClass 

209 control = base.SingleFramePlugin.registry[algorithmName].PluginClass.ConfigClass().makeControl() 

210 msConfig.algorithms.names = [algorithmName] 

211 # Note: It is essential to remove the floating point part of the position for the 

212 # Algorithm._apply. Otherwise, when the PSF is realised it will have been warped 

213 # to account for the sub-pixel offset and we won't get *exactly* this PSF. 

214 plugin, table = makePluginAndCat(alg, algorithmName, control, centroid="centroid") 

215 center = geom.Point2D(int(x), int(y)) + geom.Extent2D(self.offset + geom.Extent2I(self.xy0)) 

216 source = table.makeRecord() 

217 source.set("centroid_x", center.getX()) 

218 source.set("centroid_y", center.getY()) 

219 source.setFootprint(afwDetection.Footprint(afwGeom.SpanSet(exposure.getBBox(afwImage.PARENT)))) 

220 plugin.measure(source, exposure) 

221 

222 return source 

223 

224 def testHsmShape(self): 

225 """Test that we can instantiate and play with a measureShape""" 

226 

227 nFail = 0 

228 msg = "" 

229 

230 for (algNum, algName), (i, imageid) in itertools.product(enumerate(correction_methods), 

231 enumerate(file_indices)): 

232 algorithmName = "ext_shapeHSM_HsmShape" + algName[0:1].upper() + algName[1:].lower() 

233 

234 source = self.runMeasurement(algorithmName, imageid, x_centroid[i], y_centroid[i], sky_var[i]) 

235 

236 ########################################## 

237 # see how we did 

238 if algName in ("KSB"): 

239 # Need to convert g1,g2 --> e1,e2 because GalSim has done that 

240 # for the expected values ("for consistency") 

241 g1 = source.get(algorithmName + "_g1") 

242 g2 = source.get(algorithmName + "_g2") 

243 scale = 2.0/(1.0 + g1**2 + g2**2) 

244 e1 = g1*scale 

245 e2 = g2*scale 

246 sigma = source.get(algorithmName + "_sigma") 

247 else: 

248 e1 = source.get(algorithmName + "_e1") 

249 e2 = source.get(algorithmName + "_e2") 

250 sigma = 0.5*source.get(algorithmName + "_sigma") 

251 resolution = source.get(algorithmName + "_resolution") 

252 flags = source.get(algorithmName + "_flag") 

253 

254 tests = [ 

255 # label known-value measured tolerance 

256 ["e1", float(e1_expected[i][algNum]), e1, 0.5*10**-SHAPE_DECIMALS], 

257 ["e2", float(e2_expected[i][algNum]), e2, 0.5*10**-SHAPE_DECIMALS], 

258 ["resolution", float(resolution_expected[i][algNum]), resolution, 0.5*10**-SIZE_DECIMALS], 

259 

260 # sigma won't match exactly because 

261 # we're using skyvar=mean(var) instead of measured value ... expected a difference 

262 ["sigma", float(sigma_e_expected[i][algNum]), sigma, 0.07], 

263 ["shapeStatus", 0, flags, 0], 

264 ] 

265 

266 for test in tests: 

267 label, know, hsm, limit = test 

268 err = hsm - know 

269 msgTmp = "%-12s %s %5s: %6.6f %6.6f (val-known) = %.3g\n" % (algName, imageid, 

270 label, know, hsm, err) 

271 if not np.isfinite(err) or abs(err) > limit: 

272 msg += msgTmp 

273 nFail += 1 

274 

275 self.assertAlmostEqual(g1 if algName in ("KSB") else e1, galsim_e1[i][algNum], SHAPE_DECIMALS) 

276 self.assertAlmostEqual(g2 if algName in ("KSB") else e2, galsim_e2[i][algNum], SHAPE_DECIMALS) 

277 self.assertAlmostEqual(resolution, galsim_resolution[i][algNum], SIZE_DECIMALS) 

278 self.assertAlmostEqual(sigma, galsim_err[i][algNum], delta=0.07) 

279 

280 self.assertEqual(nFail, 0, "\n"+msg) 

281 

282 def testHsmSourceMoments(self): 

283 for (i, imageid) in enumerate(file_indices): 

284 source = self.runMeasurement("ext_shapeHSM_HsmSourceMoments", imageid, 

285 x_centroid[i], y_centroid[i], sky_var[i]) 

286 x = source.get("ext_shapeHSM_HsmSourceMoments_x") 

287 y = source.get("ext_shapeHSM_HsmSourceMoments_y") 

288 xx = source.get("ext_shapeHSM_HsmSourceMoments_xx") 

289 yy = source.get("ext_shapeHSM_HsmSourceMoments_yy") 

290 xy = source.get("ext_shapeHSM_HsmSourceMoments_xy") 

291 

292 # Centroids from GalSim use the FITS lower-left corner of 1,1 

293 offset = self.xy0 + self.offset 

294 self.assertAlmostEqual(x - offset.getX(), centroid_expected[i][0] - 1, 3) 

295 self.assertAlmostEqual(y - offset.getY(), centroid_expected[i][1] - 1, 3) 

296 

297 expected = afwEll.Quadrupole(afwEll.SeparableDistortionDeterminantRadius( 

298 moments_expected[i][1], moments_expected[i][2], moments_expected[i][0])) 

299 

300 self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS) 

301 self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS) 

302 self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS) 

303 

304 def testHsmSourceMomentsRound(self): 

305 for (i, imageid) in enumerate(file_indices): 

306 source = self.runMeasurement("ext_shapeHSM_HsmSourceMomentsRound", imageid, 

307 x_centroid[i], y_centroid[i], sky_var[i]) 

308 x = source.get("ext_shapeHSM_HsmSourceMomentsRound_x") 

309 y = source.get("ext_shapeHSM_HsmSourceMomentsRound_y") 

310 xx = source.get("ext_shapeHSM_HsmSourceMomentsRound_xx") 

311 yy = source.get("ext_shapeHSM_HsmSourceMomentsRound_yy") 

312 xy = source.get("ext_shapeHSM_HsmSourceMomentsRound_xy") 

313 flux = source.get("ext_shapeHSM_HsmSourceMomentsRound_Flux") 

314 

315 # Centroids from GalSim use the FITS lower-left corner of 1,1 

316 offset = self.xy0 + self.offset 

317 self.assertAlmostEqual(x - offset.getX(), round_moments_expected[i][4] - 1, 3) 

318 self.assertAlmostEqual(y - offset.getY(), round_moments_expected[i][5] - 1, 3) 

319 

320 expected = afwEll.Quadrupole(afwEll.SeparableDistortionDeterminantRadius( 

321 round_moments_expected[i][1], round_moments_expected[i][2], round_moments_expected[i][0])) 

322 self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS) 

323 self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS) 

324 self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS) 

325 

326 self.assertAlmostEqual(flux, round_moments_expected[i][3], SHAPE_DECIMALS) 

327 

328 def testHsmPsfMoments(self): 

329 for width in (2.0, 3.0, 4.0): 

330 psf = afwDetection.GaussianPsf(35, 35, width) 

331 exposure = afwImage.ExposureF(45, 56) 

332 exposure.getMaskedImage().set(1.0, 0, 1.0) 

333 exposure.setPsf(psf) 

334 

335 # perform the shape measurement 

336 msConfig = base.SingleFrameMeasurementConfig() 

337 msConfig.algorithms.names = ["ext_shapeHSM_HsmPsfMoments"] 

338 plugin, cat = makePluginAndCat(lsst.meas.extensions.shapeHSM.HsmPsfMomentsAlgorithm, 

339 "ext_shapeHSM_HsmPsfMoments", centroid="centroid", 

340 control=lsst.meas.extensions.shapeHSM.HsmPsfMomentsControl()) 

341 source = cat.addNew() 

342 source.set("centroid_x", 23) 

343 source.set("centroid_y", 34) 

344 offset = geom.Point2I(23, 34) 

345 tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset) 

346 source.setFootprint(afwDetection.Footprint(tmpSpans)) 

347 plugin.measure(source, exposure) 

348 x = source.get("ext_shapeHSM_HsmPsfMoments_x") 

349 y = source.get("ext_shapeHSM_HsmPsfMoments_y") 

350 xx = source.get("ext_shapeHSM_HsmPsfMoments_xx") 

351 yy = source.get("ext_shapeHSM_HsmPsfMoments_yy") 

352 xy = source.get("ext_shapeHSM_HsmPsfMoments_xy") 

353 

354 self.assertAlmostEqual(x, 0.0, 3) 

355 self.assertAlmostEqual(y, 0.0, 3) 

356 

357 expected = afwEll.Quadrupole(afwEll.Axes(width, width, 0.0)) 

358 

359 self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS) 

360 self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS) 

361 self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS) 

362 

363 

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

365 pass 

366 

367 

368def setup_module(module): 

369 lsst.utils.tests.init() 

370 

371 

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

373 lsst.utils.tests.init() 

374 unittest.main()