Coverage for tests/test_measure.py: 24%

162 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-08 03:08 -0700

1# This file is part of meas_algorithms. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import os 

23import unittest 

24import math 

25import warnings 

26 

27import lsst.geom 

28import lsst.afw.detection as afwDetection 

29import lsst.afw.image as afwImage 

30import lsst.afw.math as afwMath 

31import lsst.afw.table as afwTable 

32from lsst.log import Log 

33import lsst.meas.base as measBase 

34import lsst.meas.algorithms as algorithms 

35import lsst.meas.algorithms.testUtils as testUtils 

36import lsst.pex.config as pexConfig 

37import lsst.utils.tests 

38 

39# Change the level to Log.DEBUG or Log.TRACE to see debug messages 

40Log.getLogger("lsst.measurement").setLevel(Log.INFO) 

41 

42try: 

43 type(display) 

44except NameError: 

45 display = False 

46else: 

47 import lsst.afw.display as afwDisplay 

48 afwDisplay.setDefaultMaskTransparency(75) 

49 

50# Determine if we have afwdata 

51try: 

52 afwdataDir = lsst.utils.getPackageDir('afwdata') 

53except Exception: 

54 afwdataDir = None 

55 

56 

57def toString(*args): 

58 """toString written in python""" 

59 if len(args) == 1: 

60 args = args[0] 

61 

62 y, x0, x1 = args 

63 return "%d: %d..%d" % (y, x0, x1) 

64 

65 

66class FindAndMeasureTestCase(lsst.utils.tests.TestCase): 

67 """A test case detecting and measuring objects.""" 

68 

69 def setUp(self): 

70 self.mi = afwImage.MaskedImageF(os.path.join(afwdataDir, 

71 "CFHT", "D4", "cal-53535-i-797722_1.fits")) 

72 

73 self.FWHM = 5 

74 self.psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*math.sqrt(2*math.log(2)))) 

75 

76 if False: # use full image, trimmed to data section 

77 self.XY0 = lsst.geom.PointI(32, 2) 

78 self.mi = self.mi.Factory(self.mi, lsst.geom.BoxI(self.XY0, lsst.geom.PointI(2079, 4609)), 

79 afwImage.LOCAL) 

80 self.mi.setXY0(lsst.geom.PointI(0, 0)) 

81 else: # use sub-image 

82 self.XY0 = lsst.geom.PointI(824, 140) 

83 self.mi = self.mi.Factory(self.mi, lsst.geom.BoxI(self.XY0, lsst.geom.ExtentI(256, 256)), 

84 afwImage.LOCAL) 

85 

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

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

88 

89 def tearDown(self): 

90 del self.mi 

91 del self.psf 

92 del self.exposure 

93 

94 @unittest.skipUnless(afwdataDir, "afwdata not available") 

95 def testDetection(self): 

96 """Test object detection""" 

97 # 

98 # Fix defects 

99 # 

100 # Mask known bad pixels 

101 # 

102 badPixels = testUtils.makeDefectList() 

103 algorithms.interpolateOverDefects(self.mi, self.psf, badPixels) 

104 

105 # 

106 # Subtract background 

107 # 

108 bgGridSize = 64 # was 256 ... but that gives only one region and the spline breaks 

109 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.NATURAL_SPLINE) 

110 bctrl.setNxSample(int(self.mi.getWidth()/bgGridSize) + 1) 

111 bctrl.setNySample(int(self.mi.getHeight()/bgGridSize) + 1) 

112 backobj = afwMath.makeBackground(self.mi.getImage(), bctrl) 

113 

114 self.mi.getImage()[:] -= backobj.getImageF() 

115 # 

116 # Remove CRs 

117 # 

118 crConfig = algorithms.FindCosmicRaysConfig() 

119 algorithms.findCosmicRays(self.mi, self.psf, 0, pexConfig.makePropertySet(crConfig)) 

120 # 

121 # We do a pretty good job of interpolating, so don't propagagate the convolved CR/INTRP bits 

122 # (we'll keep them for the original CR/INTRP pixels) 

123 # 

124 savedMask = self.mi.getMask().Factory(self.mi.getMask(), True) 

125 saveBits = savedMask.getPlaneBitMask("CR") | \ 

126 savedMask.getPlaneBitMask("BAD") | \ 

127 savedMask.getPlaneBitMask("INTRP") # Bits to not convolve 

128 savedMask &= saveBits 

129 

130 msk = self.mi.getMask() 

131 msk &= ~saveBits # Clear the saved bits 

132 del msk 

133 # 

134 # Smooth image 

135 # 

136 psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*math.sqrt(2*math.log(2)))) 

137 

138 cnvImage = self.mi.Factory(self.mi.getBBox()) 

139 kernel = psf.getKernel() 

140 afwMath.convolve(cnvImage, self.mi, kernel, afwMath.ConvolutionControl()) 

141 

142 msk = cnvImage.getMask() 

143 msk |= savedMask # restore the saved bits 

144 del msk 

145 

146 threshold = afwDetection.Threshold(3, afwDetection.Threshold.STDEV) 

147 # 

148 # Only search the part of the frame that was PSF-smoothed 

149 # 

150 llc = lsst.geom.PointI(psf.getKernel().getWidth()//2, psf.getKernel().getHeight()//2) 

151 urc = lsst.geom.PointI(cnvImage.getWidth() - llc[0] - 1, cnvImage.getHeight() - llc[1] - 1) 

152 middle = cnvImage.Factory(cnvImage, lsst.geom.BoxI(llc, urc), afwImage.LOCAL) 

153 ds = afwDetection.FootprintSet(middle, threshold, "DETECTED") 

154 del middle 

155 # 

156 # Reinstate the saved (e.g. BAD) (and also the DETECTED | EDGE) bits in the unsmoothed image 

157 # 

158 savedMask[:] = cnvImage.getMask() 

159 msk = self.mi.getMask() 

160 msk |= savedMask 

161 del msk 

162 del savedMask 

163 

164 if display: 

165 disp = afwDisplay.Display(frame=2) 

166 disp.mtv(self.mi, title=self._testMethodName + ": image") 

167 afwDisplay.Display(frame=3).mtv(cnvImage, title=self._testMethodName + ": cnvImage") 

168 

169 # 

170 # Time to actually measure 

171 # 

172 schema = afwTable.SourceTable.makeMinimalSchema() 

173 sfm_config = measBase.SingleFrameMeasurementConfig() 

174 sfm_config.plugins = ["base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux", 

175 "base_SdssShape", "base_GaussianFlux", 

176 "base_PixelFlags"] 

177 sfm_config.slots.centroid = "base_SdssCentroid" 

178 sfm_config.slots.shape = "base_SdssShape" 

179 sfm_config.slots.psfFlux = "base_PsfFlux" 

180 sfm_config.slots.gaussianFlux = None 

181 sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0" 

182 sfm_config.slots.modelFlux = "base_GaussianFlux" 

183 sfm_config.slots.calibFlux = None 

184 sfm_config.plugins["base_SdssShape"].maxShift = 10.0 

185 sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0] 

186 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config) 

187 measCat = afwTable.SourceCatalog(schema) 

188 # detect the sources and run with the measurement task 

189 ds.makeSources(measCat) 

190 self.exposure.setPsf(self.psf) 

191 task.run(measCat, self.exposure) 

192 

193 self.assertGreater(len(measCat), 0) 

194 for source in measCat: 

195 if source.get("base_PixelFlags_flag_edge"): 

196 continue 

197 

198 if display: 

199 disp.dot("+", source.getX(), source.getY()) 

200 

201 

202class GaussianPsfTestCase(lsst.utils.tests.TestCase): 

203 """A test case detecting and measuring Gaussian PSFs.""" 

204 

205 def setUp(self): 

206 FWHM = 5 

207 psf = algorithms.DoubleGaussianPsf(15, 15, FWHM/(2*math.sqrt(2*math.log(2)))) 

208 mi = afwImage.MaskedImageF(lsst.geom.ExtentI(100, 100)) 

209 

210 self.xc, self.yc, self.instFlux = 45, 55, 1000.0 

211 mi.image[self.xc, self.yc, afwImage.LOCAL] = self.instFlux 

212 

213 cnvImage = mi.Factory(mi.getDimensions()) 

214 afwMath.convolve(cnvImage, mi, psf.getKernel(), afwMath.ConvolutionControl()) 

215 

216 self.exp = afwImage.makeExposure(cnvImage) 

217 self.exp.setPsf(psf) 

218 

219 if display and False: 

220 afwDisplay.Display(frame=0).mtv(self.exp, title=self._testMethodName + ": image") 

221 

222 def tearDown(self): 

223 del self.exp 

224 

225 def testPsfFlux(self): 

226 """Test that fluxes are measured correctly.""" 

227 # 

228 # Total flux in image 

229 # 

230 flux = afwMath.makeStatistics(self.exp.getMaskedImage(), afwMath.SUM).getValue() 

231 self.assertAlmostEqual(flux/self.instFlux, 1.0) 

232 

233 # 

234 # Various algorithms 

235 # 

236 rad = 10.0 

237 

238 schema = afwTable.SourceTable.makeMinimalSchema() 

239 schema.addField("centroid_x", type=float) 

240 schema.addField("centroid_y", type=float) 

241 schema.addField("centroid_flag", type='Flag') 

242 with warnings.catch_warnings(): 

243 warnings.filterwarnings("ignore", message="ignoreSlotPluginChecks", category=FutureWarning) 

244 sfm_config = measBase.SingleFrameMeasurementConfig(ignoreSlotPluginChecks=True) 

245 sfm_config.doReplaceWithNoise = False 

246 sfm_config.plugins = ["base_CircularApertureFlux", "base_PsfFlux"] 

247 sfm_config.slots.centroid = "centroid" 

248 sfm_config.slots.shape = None 

249 sfm_config.slots.psfFlux = None 

250 sfm_config.slots.gaussianFlux = None 

251 sfm_config.slots.apFlux = None 

252 sfm_config.slots.modelFlux = None 

253 sfm_config.slots.calibFlux = None 

254 sfm_config.plugins["base_SdssShape"].maxShift = 10.0 

255 sfm_config.plugins["base_CircularApertureFlux"].radii = [rad] 

256 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config) 

257 measCat = afwTable.SourceCatalog(schema) 

258 source = measCat.addNew() 

259 source.set("centroid_x", self.xc) 

260 source.set("centroid_y", self.yc) 

261 task.run(measCat, self.exp) 

262 for algName in ["base_CircularApertureFlux_10_0", "base_PsfFlux"]: 

263 instFlux = source.get(algName + "_instFlux") 

264 flag = source.get(algName + "_flag") 

265 self.assertEqual(flag, False) 

266 self.assertAlmostEqual(instFlux/self.instFlux, 1.0, 4, "Measuring with %s: %g v. %g" % 

267 (algName, instFlux, self.instFlux)) 

268 

269 

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

271 pass 

272 

273 

274def setup_module(module): 

275 lsst.utils.tests.init() 

276 

277 

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

279 lsst.utils.tests.init() 

280 unittest.main()