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# 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 numpy as np 

26 

27import lsst.geom 

28import lsst.afw.image as afwImage 

29import lsst.meas.algorithms as algorithms 

30import lsst.utils.tests 

31from lsst.daf.base import PropertyList 

32 

33try: 

34 type(display) 

35except NameError: 

36 display = False 

37else: 

38 import lsst.afw.display as afwDisplay 

39 afwDisplay.setDefaultMaskTransparency(75) 

40 

41# Determine if we have afwdata 

42try: 

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

44except Exception: 

45 afwdataDir = None 

46 

47TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

48 

49 

50class DefectsTestCase(lsst.utils.tests.TestCase): 

51 """Tests for collections of Defect.""" 

52 

53 def assertMetadata(self, first, second): 

54 """Compare the metadata associated with Defects""" 

55 

56 # Must strip out DATE metadata before comparison 

57 meta1 = first.getMetadata() 

58 meta2 = second.getMetadata() 

59 for d in (meta1, meta2): 

60 for k in ("DATE", "CALIB_CREATION_DATE", "CALIB_CREATION_TIME"): 

61 if k in d: 

62 del d[k] 

63 

64 self.assertEqual(meta1, meta2) 

65 meta1["NEW"] = "additional header" 

66 self.assertNotEqual(first.getMetadata(), second.getMetadata()) 

67 del meta1["NEW"] 

68 

69 def test_defects(self): 

70 defects = algorithms.Defects() 

71 

72 defects.append(algorithms.Defect(lsst.geom.Box2I(lsst.geom.Point2I(5, 6), 

73 lsst.geom.Point2I(41, 50)))) 

74 

75 defects.append(lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

76 lsst.geom.Point2I(4, 5))) 

77 defects.append(lsst.geom.Point2I(50, 50)) 

78 defects.append(afwImage.DefectBase(lsst.geom.Box2I(lsst.geom.Point2I(100, 200), 

79 lsst.geom.Extent2I(5, 5)))) 

80 self.assertEqual(len(defects), 4) 

81 

82 for d in defects: 

83 self.assertIsInstance(d, algorithms.Defect) 

84 

85 # Transposition 

86 transposed = defects.transpose() 

87 self.assertEqual(len(transposed), len(defects)) 

88 

89 # Check that an individual defect is found properly transposed within 

90 # the outputs. 

91 found = False 

92 for defect in transposed: 92 ↛ 96line 92 didn't jump to line 96, because the loop on line 92 didn't complete

93 if defect.getBBox() == lsst.geom.Box2I(lsst.geom.Point2I(6, 5), lsst.geom.Extent2I(45, 37)): 

94 found = True 

95 break 

96 self.assertTrue(found) 

97 

98 # Serialization round trip 

99 meta = PropertyList() 

100 meta["TESTHDR"] = "testing" 

101 defects.setMetadata(meta) 

102 

103 table = defects.toFitsRegionTable() 

104 defects2 = algorithms.Defects.fromTable(table) 

105 self.assertEqual(defects2, defects) 

106 

107 # via FITS 

108 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

109 defects.writeFits(tmpFile) 

110 defects2 = algorithms.Defects.readFits(tmpFile) 

111 

112 # Equality tests the bounding boxes so metadata is tested separately. 

113 self.assertEqual(defects2, defects) 

114 self.assertMetadata(defects2, defects) 

115 

116 # via text file 

117 with lsst.utils.tests.getTempFilePath(".ecsv") as tmpFile: 

118 defects.writeText(tmpFile) 

119 defects2 = algorithms.Defects.readText(tmpFile) 

120 

121 # Equality tests the bounding boxes so metadata is tested separately. 

122 self.assertEqual(defects2, defects) 

123 self.assertMetadata(defects2, defects) 

124 

125 # Check bad values 

126 with self.assertRaises(ValueError): 

127 defects.append(lsst.geom.Box2D(lsst.geom.Point2D(0., 0.), 

128 lsst.geom.Point2D(3.1, 3.1))) 

129 with self.assertRaises(ValueError): 

130 defects.append("defect") 

131 

132 def testAstropyRegion(self): 

133 """Read a FITS region file created by Astropy regions.""" 

134 # The file contains three regions: 

135 # 

136 # - Point2I(340, 344) 

137 # - Point2I(340, 344) 

138 # - Box2I(minimum=Point2I(5, -5), dimensions=Extent2I(10, 20)) 

139 # 

140 # The two coincident points are combined on read, so we end up with two defects. 

141 

142 with self.assertLogs(): 

143 defects = algorithms.Defects.readFits(os.path.join(TESTDIR, "data", "fits_region.fits")) 

144 

145 self.assertEqual(len(defects), 2) 

146 

147 def testLsstTextfile(self): 

148 """Read legacy LSST text file format""" 

149 with lsst.utils.tests.getTempFilePath(".txt") as tmpFile: 

150 with open(tmpFile, "w") as fh: 

151 print("""# X0 Y0 width height 

152 996 0 56 24 

153 0 4156 2048 20 

154 0 0 17 4176 

155 1998 4035 50 141 

156 1023 0 2 4176 

157 2027 0 21 4176 

158 0 4047 37 129 

159# Some rows without fixed column widths 

16014 20 2000 50 

16110 10 10 10 

162""", file=fh) 

163 

164 defects = algorithms.Defects.readLsstDefectsFile(tmpFile) 

165 

166 # Although there are 9 defects listed above, we record 11 after 

167 # normalization. This is due to non-optimal behaviour in 

168 # Defects.fromMask; see DM-24781. 

169 self.assertEqual(len(defects), 11) 

170 

171 def test_normalize_defects(self): 

172 """A test for the lsst.meas.algorithms.Defect.normalize() method. 

173 """ 

174 defects = algorithms.Defects() 

175 

176 # First series of 1-pixel contiguous defects 

177 for yPix in range(1, 6): 

178 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(15, yPix), 

179 dimensions=lsst.geom.Extent2I(1, 1))) 

180 

181 # Defects are normalized as they are added; check that the above have 

182 # been merged into a single bounding box. 

183 self.assertEqual(len(defects), 1) 

184 

185 # Second series of 1-pixel contiguous defects in bulk mode 

186 with defects.bulk_update(): 

187 for yPix in range(11, 16): 

188 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(20, yPix), 

189 dimensions=lsst.geom.Extent2I(1, 1))) 

190 # In bulk mode, defects are not normalized. 

191 self.assertEqual(len(defects), 6) 

192 

193 # Normalization applied on exiting bulk mode. 

194 self.assertEqual(len(defects), 2) 

195 

196 boxesMeasured = [] 

197 for defect in defects: 

198 boxesMeasured.append(defect.getBBox()) 

199 

200 # The normalizing function should have created the following two boxes out 

201 # of the individual 1-pixel defects from above 

202 expectedDefects = [lsst.geom.Box2I(corner=lsst.geom.Point2I(15, 1), 

203 dimensions=lsst.geom.Extent2I(1, 5)), 

204 lsst.geom.Box2I(corner=lsst.geom.Point2I(20, 11), 

205 dimensions=lsst.geom.Extent2I(1, 5))] 

206 

207 self.assertEqual(len(expectedDefects), len(boxesMeasured)) 

208 for expDef, measDef in zip(expectedDefects, boxesMeasured): 

209 self.assertEqual(expDef, measDef) 

210 

211 # Normalize two distinct sets of Defects and ensure they compare to the same thing 

212 defects = algorithms.Defects() 

213 # Set 1 

214 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 1), dimensions=lsst.geom.Extent2I(1, 1))) 

215 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 2), dimensions=lsst.geom.Extent2I(1, 1))) 

216 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 3), dimensions=lsst.geom.Extent2I(1, 1))) 

217 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 4), dimensions=lsst.geom.Extent2I(1, 1))) 

218 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 5), dimensions=lsst.geom.Extent2I(1, 1))) 

219 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 6), dimensions=lsst.geom.Extent2I(1, 1))) 

220 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 7), dimensions=lsst.geom.Extent2I(1, 1))) 

221 defects.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 8), dimensions=lsst.geom.Extent2I(1, 1))) 

222 

223 # Set 2 

224 defects2 = algorithms.Defects() 

225 defects2.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 1), dimensions=lsst.geom.Extent2I(1, 5))) 

226 defects2.append(lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 5), dimensions=lsst.geom.Extent2I(1, 4))) 

227 

228 self.assertEqual(defects, defects2) 

229 

230 boxesMeasured, boxesMeasured2 = [], [] 

231 for defect, defect2 in zip(defects, defects2): 

232 boxesMeasured.append(defect.getBBox()) 

233 boxesMeasured2.append(defect2.getBBox()) 

234 

235 expectedDefects = [lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 1), 

236 dimensions=lsst.geom.Extent2I(1, 8))] 

237 

238 self.assertEqual(len(expectedDefects), len(boxesMeasured)) 

239 for expDef, measDef in zip(expectedDefects, boxesMeasured): 

240 self.assertEqual(expDef, measDef) 

241 

242 self.assertEqual(len(expectedDefects), len(boxesMeasured2)) 

243 for expDef, measDef in zip(expectedDefects, boxesMeasured2): 

244 self.assertEqual(expDef, measDef) 

245 

246 

247class InterpolationTestCase(lsst.utils.tests.TestCase): 

248 """A test case for interpolation.""" 

249 

250 def setUp(self): 

251 self.FWHM = 5 

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

253 maskedImageFile = os.path.join(afwdataDir, "CFHT", "D4", "cal-53535-i-797722_1.fits") 

254 

255 self.mi = afwImage.MaskedImageF(maskedImageFile) 

256 if False: # use sub-image? 

257 self.mi = self.mi.Factory(self.mi, afwImage.BBox(afwImage.PointI(760, 20), 256, 256)) 

258 self.mi.getMask().addMaskPlane("INTERP") 

259 

260 measAlgorithmsDir = lsst.utils.getPackageDir('meas_algorithms') 

261 self.badPixels = algorithms.Defects.readText(os.path.join(measAlgorithmsDir, 

262 "policy", "BadPixels.ecsv")) 

263 

264 def tearDown(self): 

265 del self.mi 

266 del self.psf 

267 del self.badPixels 

268 

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

270 def testDetection(self): 

271 """Test Interp algorithms.""" 

272 

273 if display: 

274 frame = 0 

275 afwDisplay.Display(frame=frame).mtv(self.mi, title=self._testMethodName + ": Original") 

276 

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

278 

279 if display: 

280 frame += 1 

281 afwDisplay.Display(frame=frame).mtv(self.mi, title=self._testMethodName + ": Interpolated") 

282 frame += 1 

283 afwDisplay.Display(frame=frame).mtv(self.mi.getVariance(), 

284 title=self._testMethodName + ": Variance") 

285 

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

287 def test818(self): 

288 """A test case for #818; the full test is in /lsst/DC3root/ticketFiles/818""" 

289 

290 badPixels = [] 

291 defects = [((82, 663), 6, 8), 

292 ((83, 659), 9, 6), 

293 ((85, 660), 10, 11), 

294 ((87, 669), 3, 3), 

295 ] 

296 

297 for xy0, width, height in defects: 

298 x0, y0 = xy0 

299 bbox = lsst.geom.BoxI(lsst.geom.PointI(x0, y0), lsst.geom.ExtentI(width, height)) 

300 badPixels.append(algorithms.Defect(bbox)) 

301 

302 mi = afwImage.MaskedImageF(517, 800) 

303 

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

305 

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

307 def test1295(self): 

308 """A test case for #1295 (failure to interpolate over groups of defects.""" 

309 im = afwImage.ImageF(lsst.geom.ExtentI(100, 100)) 

310 mi = afwImage.makeMaskedImage(im) 

311 mi.set(100) 

312 flat = afwImage.ImageF(im.getDimensions()) 

313 flat.set(1) 

314 flat[50:51, :, afwImage.LOCAL] = 0.0 

315 flat[55:56, :, afwImage.LOCAL] = 0.0 

316 flat[58:59, :, afwImage.LOCAL] = 0.0 

317 flat[51:60, 51:, afwImage.LOCAL] = 0.0 

318 

319 mi /= flat 

320 

321 if display: 

322 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + ": Raw") 

323 

324 defectList = algorithms.Defects() 

325 bbox = lsst.geom.BoxI(lsst.geom.PointI(50, 0), lsst.geom.ExtentI(1, 100)) 

326 defectList.append(algorithms.Defect(bbox)) 

327 bbox = lsst.geom.BoxI(lsst.geom.PointI(55, 0), lsst.geom.ExtentI(1, 100)) 

328 defectList.append(algorithms.Defect(bbox)) 

329 bbox = lsst.geom.BoxI(lsst.geom.PointI(58, 0), lsst.geom.ExtentI(1, 100)) 

330 defectList.append(algorithms.Defect(bbox)) 

331 bbox = lsst.geom.BoxI(lsst.geom.PointI(51, 51), lsst.geom.ExtentI(9, 49)) 

332 defectList.append(algorithms.Defect(bbox)) 

333 

334 psf = algorithms.DoubleGaussianPsf(15, 15, 1./(2*math.sqrt(2*math.log(2)))) 

335 algorithms.interpolateOverDefects(mi, psf, defectList, 50.) 

336 

337 if display: 

338 afwDisplay.Display(frame=1).mtv(mi, title=self._testMethodName + ": Interpolated") 

339 

340 self.assertTrue(np.isfinite(mi.image[56, 51, afwImage.LOCAL])) 

341 

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

343 def testEdge(self): 

344 """Test that we can interpolate to the edge""" 

345 mi = afwImage.MaskedImageF(80, 30) 

346 

347 ima = mi.getImage().getArray() 

348 # 

349 # Loop over number of bad columns at left or right edge of image 

350 # 

351 for nBadCol in range(0, 20): 

352 mi.set((0, 0x0, 0)) 

353 

354 np.random.seed(666) 

355 ima[:] = np.random.uniform(-1, 1, ima.shape) 

356 

357 defects = [] 

358 

359 if nBadCol > 0: 

360 # 

361 # Bad left edge 

362 # 

363 ima[:, 0:nBadCol] = 10 

364 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, 0), 

365 lsst.geom.ExtentI(nBadCol, mi.getHeight()))) 

366 # 

367 # With another bad set of columns next to bad left edge 

368 # 

369 ima[:, -nBadCol:] = 10 

370 defects.append(lsst.geom.BoxI(lsst.geom.PointI(mi.getWidth() - nBadCol, 0), 

371 lsst.geom.ExtentI(nBadCol, mi.getHeight()))) 

372 # 

373 # Bad right edge 

374 # 

375 ima[0:10, nBadCol+1:nBadCol+4] = 100 

376 defects.append(lsst.geom.BoxI(lsst.geom.PointI(nBadCol+1, 0), 

377 lsst.geom.ExtentI(3, 10))) 

378 # 

379 # With another bad set of columns next to bad right edge 

380 # 

381 ima[0:10, -nBadCol-4:-nBadCol-1] = 100 

382 defects.append((lsst.geom.BoxI(lsst.geom.PointI(mi.getWidth() - nBadCol - 4, 0), 

383 lsst.geom.ExtentI(3, 10)))) 

384 # 

385 # Test cases that left and right bad patches nearly (or do) coalesce 

386 # 

387 ima[-3:, 0:mi.getWidth()//2-1] = 100 

388 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, mi.getHeight() - 3), 

389 lsst.geom.ExtentI(mi.getWidth()//2-1, 1))) 

390 

391 ima[-3:, mi.getWidth()//2+1:] = 100 

392 defects.append(lsst.geom.BoxI(lsst.geom.PointI(mi.getWidth()//2 + 1, mi.getHeight() - 3), 

393 lsst.geom.ExtentI(mi.getWidth()//2 - 1, 1))) 

394 

395 ima[-2:, 0:mi.getWidth()//2] = 100 

396 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, mi.getHeight() - 2), 

397 lsst.geom.ExtentI(mi.getWidth()//2, 1))) 

398 

399 ima[-2:, mi.getWidth()//2+1:] = 100 

400 defects.append(lsst.geom.BoxI(lsst.geom.PointI(mi.getWidth()//2 + 1, mi.getHeight() - 2), 

401 lsst.geom.ExtentI(mi.getWidth()//2 - 1, 1))) 

402 

403 ima[-1:, :] = 100 

404 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, mi.getHeight() - 1), 

405 lsst.geom.ExtentI(mi.getWidth(), 1))) 

406 

407 # Test fix for HSC-978: long defect stops one pixel shy of the edge (when nBadCol == 0) 

408 ima[13, :-1] = 100 

409 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, 13), lsst.geom.ExtentI(mi.getWidth() - 1, 1))) 

410 ima[14, 1:] = 100 

411 defects.append(lsst.geom.BoxI(lsst.geom.PointI(1, 14), lsst.geom.ExtentI(mi.getWidth() - 1, 1))) 

412 

413 # 

414 # Build list of defects to interpolate over 

415 # 

416 defectList = algorithms.Defects() 

417 

418 for bbox in defects: 

419 defectList.append(algorithms.Defect(bbox)) 

420 # 

421 # Guess a PSF and do the work 

422 # 

423 if display: 

424 afwDisplay.Display(frame=2).mtv(mi, title=self._testMethodName + ": image") 

425 

426 psf = algorithms.DoubleGaussianPsf(15, 15, 1./(2*math.sqrt(2*math.log(2)))) 

427 algorithms.interpolateOverDefects(mi, psf, defectList, 0, True) 

428 

429 if display: 

430 afwDisplay.Display(frame=3).mtv(mi, title=self._testMethodName + ": image") 

431 

432 self.assertGreater(np.min(ima), -2) 

433 self.assertGreater(2, np.max(ima)) 

434 

435 

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

437 pass 

438 

439 

440def setup_module(module): 

441 lsst.utils.tests.init() 

442 

443 

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

445 lsst.utils.tests.init() 

446 unittest.main()