Coverage for tests/test_interp.py : 13%

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/>.
22import os
23import unittest
24import math
25import numpy as np
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
33try:
34 type(display)
35except NameError:
36 display = False
37else:
38 import lsst.afw.display as afwDisplay
39 afwDisplay.setDefaultMaskTransparency(75)
41# Determine if we have afwdata
42try:
43 afwdataDir = lsst.utils.getPackageDir('afwdata')
44except Exception:
45 afwdataDir = None
47TESTDIR = os.path.abspath(os.path.dirname(__file__))
50class DefectsTestCase(lsst.utils.tests.TestCase):
51 """Tests for collections of Defect."""
53 def assertMetadata(self, first, second):
54 """Compare the metadata associated with Defects"""
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]
64 self.assertEqual(meta1, meta2)
65 meta1["NEW"] = "additional header"
66 self.assertNotEqual(first.getMetadata(), second.getMetadata())
67 del meta1["NEW"]
69 def test_defects(self):
70 defects = algorithms.Defects()
72 defects.append(algorithms.Defect(lsst.geom.Box2I(lsst.geom.Point2I(5, 6),
73 lsst.geom.Point2I(41, 50))))
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)
82 for d in defects:
83 self.assertIsInstance(d, algorithms.Defect)
85 # Transposition
86 transposed = defects.transpose()
87 self.assertEqual(len(transposed), len(defects))
89 # Check that an individual defect is found properly transposed within
90 # the outputs.
91 found = False
92 for defect in transposed:
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)
98 # Serialization round trip
99 meta = PropertyList()
100 meta["TESTHDR"] = "testing"
101 defects.setMetadata(meta)
103 table = defects.toFitsRegionTable()
104 defects2 = algorithms.Defects.fromTable(table)
105 self.assertEqual(defects2, defects)
107 # via FITS
108 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
109 defects.writeFits(tmpFile)
110 defects2 = algorithms.Defects.readFits(tmpFile)
112 # Equality tests the bounding boxes so metadata is tested separately.
113 self.assertEqual(defects2, defects)
114 self.assertMetadata(defects2, defects)
116 # via text file
117 with lsst.utils.tests.getTempFilePath(".ecsv") as tmpFile:
118 defects.writeText(tmpFile)
119 defects2 = algorithms.Defects.readText(tmpFile)
121 # Equality tests the bounding boxes so metadata is tested separately.
122 self.assertEqual(defects2, defects)
123 self.assertMetadata(defects2, defects)
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")
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.
142 with self.assertLogs():
143 defects = algorithms.Defects.readFits(os.path.join(TESTDIR, "data", "fits_region.fits"))
145 self.assertEqual(len(defects), 2)
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)
164 defects = algorithms.Defects.readLsstDefectsFile(tmpFile)
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)
171 def test_normalize_defects(self):
172 """A test for the lsst.meas.algorithms.Defect.normalize() method.
173 """
174 defects = algorithms.Defects()
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)))
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)
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)
193 # Normalization applied on exiting bulk mode.
194 self.assertEqual(len(defects), 2)
196 boxesMeasured = []
197 for defect in defects:
198 boxesMeasured.append(defect.getBBox())
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))]
207 self.assertEqual(len(expectedDefects), len(boxesMeasured))
208 for expDef, measDef in zip(expectedDefects, boxesMeasured):
209 self.assertEqual(expDef, measDef)
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)))
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)))
228 self.assertEqual(defects, defects2)
230 boxesMeasured, boxesMeasured2 = [], []
231 for defect, defect2 in zip(defects, defects2):
232 boxesMeasured.append(defect.getBBox())
233 boxesMeasured2.append(defect2.getBBox())
235 expectedDefects = [lsst.geom.Box2I(corner=lsst.geom.Point2I(25, 1),
236 dimensions=lsst.geom.Extent2I(1, 8))]
238 self.assertEqual(len(expectedDefects), len(boxesMeasured))
239 for expDef, measDef in zip(expectedDefects, boxesMeasured):
240 self.assertEqual(expDef, measDef)
242 self.assertEqual(len(expectedDefects), len(boxesMeasured2))
243 for expDef, measDef in zip(expectedDefects, boxesMeasured2):
244 self.assertEqual(expDef, measDef)
247class InterpolationTestCase(lsst.utils.tests.TestCase):
248 """A test case for interpolation."""
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")
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")
260 measAlgorithmsDir = lsst.utils.getPackageDir('meas_algorithms')
261 self.badPixels = algorithms.Defects.readText(os.path.join(measAlgorithmsDir,
262 "policy", "BadPixels.ecsv"))
264 def tearDown(self):
265 del self.mi
266 del self.psf
267 del self.badPixels
269 @unittest.skipUnless(afwdataDir, "afwdata not available")
270 def testDetection(self):
271 """Test Interp algorithms."""
273 if display:
274 frame = 0
275 afwDisplay.Display(frame=frame).mtv(self.mi, title=self._testMethodName + ": Original")
277 algorithms.interpolateOverDefects(self.mi, self.psf, self.badPixels)
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")
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"""
290 badPixels = []
291 defects = [((82, 663), 6, 8),
292 ((83, 659), 9, 6),
293 ((85, 660), 10, 11),
294 ((87, 669), 3, 3),
295 ]
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))
302 mi = afwImage.MaskedImageF(517, 800)
304 algorithms.interpolateOverDefects(mi, self.psf, badPixels)
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
319 mi /= flat
321 if display:
322 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + ": Raw")
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))
334 psf = algorithms.DoubleGaussianPsf(15, 15, 1./(2*math.sqrt(2*math.log(2))))
335 algorithms.interpolateOverDefects(mi, psf, defectList, 50.)
337 if display:
338 afwDisplay.Display(frame=1).mtv(mi, title=self._testMethodName + ": Interpolated")
340 self.assertTrue(np.isfinite(mi.image[56, 51, afwImage.LOCAL]))
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)
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))
354 np.random.seed(666)
355 ima[:] = np.random.uniform(-1, 1, ima.shape)
357 defects = []
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)))
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)))
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)))
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)))
403 ima[-1:, :] = 100
404 defects.append(lsst.geom.BoxI(lsst.geom.PointI(0, mi.getHeight() - 1),
405 lsst.geom.ExtentI(mi.getWidth(), 1)))
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)))
413 #
414 # Build list of defects to interpolate over
415 #
416 defectList = algorithms.Defects()
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")
426 psf = algorithms.DoubleGaussianPsf(15, 15, 1./(2*math.sqrt(2*math.log(2))))
427 algorithms.interpolateOverDefects(mi, psf, defectList, 0, True)
429 if display:
430 afwDisplay.Display(frame=3).mtv(mi, title=self._testMethodName + ": image")
432 self.assertGreater(np.min(ima), -2)
433 self.assertGreater(2, np.max(ima))
436class TestMemory(lsst.utils.tests.MemoryTestCase):
437 pass
440def setup_module(module):
441 lsst.utils.tests.init()
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()