Coverage for tests/test_mask.py: 15%
391 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-14 02:44 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-14 02:44 -0700
1# This file is part of afw.
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/>.
22"""
23Tests for Masks
25Run with:
26 python test_mask.py
27or
28 pytest test_mask.py
29"""
30import os.path
31import unittest
33import numpy as np
35import lsst.utils
36import lsst.utils.tests as utilsTests
37import lsst.pex.exceptions as pexExcept
38import lsst.daf.base
39import lsst.geom
40import lsst.afw.image as afwImage
41import lsst.afw.display as afwDisplay
42import lsst.afw.display.ds9 as ds9 # noqa: F401 for some reason images don't display without both imports
44try:
45 type(display)
46except NameError:
47 display = False
49try:
50 afwdataDir = lsst.utils.getPackageDir("afwdata")
51except LookupError:
52 afwdataDir = None
54afwDisplay.setDefaultMaskTransparency(75)
57def showMaskDict(d=None, msg=None):
58 if not d:
59 d = afwImage.Mask(0, 0)
60 if not msg:
61 msg = "default"
63 try:
64 d = d.getMaskPlaneDict()
65 except AttributeError:
66 pass
68 if msg:
69 print("%-15s" % msg, end=' ')
70 print(sorted([(d[p], p) for p in d]))
73class MaskTestCase(utilsTests.TestCase):
74 """A test case for Mask"""
76 def setUp(self):
77 np.random.seed(1)
78 self.Mask = afwImage.Mask[afwImage.MaskPixel]
80 # Store the default mask planes for later use
81 maskPlaneDict = self.Mask().getMaskPlaneDict()
82 self.defaultMaskPlanes = sorted(
83 maskPlaneDict, key=maskPlaneDict.__getitem__)
85 # reset so tests will be deterministic
86 self.Mask.clearMaskPlaneDict()
87 for p in ("BAD", "SAT", "INTRP", "CR", "EDGE"):
88 self.Mask.addMaskPlane(p)
90 self.BAD = self.Mask.getPlaneBitMask("BAD")
91 self.CR = self.Mask.getPlaneBitMask("CR")
92 self.EDGE = self.Mask.getPlaneBitMask("EDGE")
94 self.val1 = self.BAD | self.CR
95 self.val2 = self.val1 | self.EDGE
97 self.mask1 = afwImage.Mask(100, 200)
98 self.mask1.set(self.val1)
99 self.mask2 = afwImage.Mask(self.mask1.getDimensions())
100 self.mask2.set(self.val2)
102 if afwdataDir is not None:
103 self.maskFile = os.path.join(afwdataDir, "data", "small_MI.fits")
104 # Below: what to expect from the mask plane in the above data.
105 # For some tests, it is left-shifted by some number of mask planes.
106 self.expect = np.zeros((256, 256), dtype='i8')
107 self.expect[:, 0] = 1
109 def tearDown(self):
110 del self.mask1
111 del self.mask2
112 # Reset the mask plane to the default
113 self.Mask.clearMaskPlaneDict()
114 for p in self.defaultMaskPlanes:
115 self.Mask.addMaskPlane(p)
117 def testArrays(self):
118 # could use Mask(5, 6) but check extent(5, 6) form too
119 image1 = afwImage.Mask(lsst.geom.ExtentI(5, 6))
120 array1 = image1.getArray()
121 self.assertEqual(array1.shape[0], image1.getHeight())
122 self.assertEqual(array1.shape[1], image1.getWidth())
123 image2 = afwImage.Mask(array1, False)
124 self.assertEqual(array1.shape[0], image2.getHeight())
125 self.assertEqual(array1.shape[1], image2.getWidth())
126 image3 = afwImage.makeMaskFromArray(array1)
127 self.assertEqual(array1.shape[0], image2.getHeight())
128 self.assertEqual(array1.shape[1], image2.getWidth())
129 self.assertEqual(type(image3), afwImage.Mask[afwImage.MaskPixel])
130 array1[:, :] = np.random.uniform(low=0, high=10, size=array1.shape)
131 self.assertMasksEqual(image1, array1)
132 array2 = image1.array
133 np.testing.assert_array_equal(image1.array, array2)
134 array3 = np.random.uniform(low=0, high=10,
135 size=array1.shape).astype(array1.dtype)
136 image1.array = array3
137 np.testing.assert_array_equal(array1, array3)
139 def testInitializeMasks(self):
140 val = 0x1234
141 msk = afwImage.Mask(lsst.geom.ExtentI(10, 10), val)
142 self.assertEqual(msk[0, 0], val)
144 def testSetGetMasks(self):
145 self.assertEqual(self.mask1[0, 0], self.val1)
147 def testOrMasks(self):
148 self.mask2 |= self.mask1
149 self.mask1 |= self.val2
151 self.assertMasksEqual(self.mask1, self.mask2)
152 expect = np.empty_like(self.mask1.getArray())
153 expect[:] = self.val2 | self.val1
154 self.assertMasksEqual(self.mask1, expect)
156 def testAndMasks(self):
157 self.mask2 &= self.mask1
158 self.mask1 &= self.val2
160 self.assertMasksEqual(self.mask1, self.mask2)
161 expect = np.empty_like(self.mask1.getArray())
162 expect[:] = self.val1 & self.val2
163 self.assertMasksEqual(self.mask1, expect)
165 def testXorMasks(self):
166 self.mask2 ^= self.mask1
167 self.mask1 ^= self.val2
169 self.assertMasksEqual(self.mask1, self.mask2)
170 expect = np.empty_like(self.mask1.getArray())
171 expect[:] = self.val1 ^ self.val2
172 self.assertMasksEqual(self.mask1, expect)
174 def testLogicalMasksMismatch(self):
175 "Test logical operations on Masks of different sizes"
176 i1 = afwImage.Mask(lsst.geom.ExtentI(100, 100))
177 i1.set(100)
178 i2 = afwImage.Mask(lsst.geom.ExtentI(10, 10))
179 i2.set(10)
181 with self.assertRaises(lsst.pex.exceptions.LengthError):
182 i1 |= i2
184 with self.assertRaises(lsst.pex.exceptions.LengthError):
185 i1 &= i2
187 with self.assertRaises(lsst.pex.exceptions.LengthError):
188 i1 ^= i2
190 def testMaskPlanes(self):
191 planes = self.Mask().getMaskPlaneDict()
192 self.assertEqual(len(planes), self.Mask.getNumPlanesUsed())
194 for k in sorted(planes.keys()):
195 self.assertEqual(planes[k], self.Mask.getMaskPlane(k))
197 def testCopyConstructors(self):
198 dmask = afwImage.Mask(self.mask1, True) # deep copy
199 smask = afwImage.Mask(self.mask1) # shallow copy
201 self.mask1 |= 32767 # should only change smask
202 temp = np.zeros_like(self.mask1.getArray()) | self.val1
203 self.assertMasksEqual(dmask, temp)
204 self.assertMasksEqual(smask, self.mask1)
205 self.assertMasksEqual(smask, temp | 32767)
207 def testSubmasks(self):
208 smask = afwImage.Mask(self.mask1,
209 lsst.geom.Box2I(lsst.geom.Point2I(1, 1),
210 lsst.geom.ExtentI(3, 2)),
211 afwImage.LOCAL)
212 mask2 = afwImage.Mask(smask.getDimensions())
214 mask2.set(666)
215 smask[:] = mask2
217 del smask
218 del mask2
220 self.assertEqual(self.mask1[0, 0, afwImage.LOCAL], self.val1)
221 self.assertEqual(self.mask1[1, 1, afwImage.LOCAL], 666)
222 self.assertEqual(self.mask1[4, 1, afwImage.LOCAL], self.val1)
223 self.assertEqual(self.mask1[1, 2, afwImage.LOCAL], 666)
224 self.assertEqual(self.mask1[4, 2, afwImage.LOCAL], self.val1)
225 self.assertEqual(self.mask1[1, 3, afwImage.LOCAL], self.val1)
227 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
228 def testReadFits(self):
229 nMaskPlanes0 = self.Mask.getNumPlanesUsed()
230 # will shift any unrecognised mask planes into unused slots
231 mask = self.Mask(self.maskFile, hdu=2)
233 self.assertMasksEqual(mask, self.expect << nMaskPlanes0)
235 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
236 def testReadFitsConform(self):
237 hdu = 2
238 mask = afwImage.Mask(self.maskFile, hdu, None,
239 lsst.geom.Box2I(), afwImage.LOCAL, True)
241 self.assertMasksEqual(mask, self.expect)
243 @unittest.skipIf(afwdataDir is None, "afwdata not setup")
244 def testWriteFits(self):
245 nMaskPlanes0 = self.Mask.getNumPlanesUsed()
246 mask = self.Mask(self.maskFile, hdu=2)
248 self.assertMasksEqual(mask, self.expect << nMaskPlanes0)
250 mask.clearMaskPlaneDict()
252 with utilsTests.getTempFilePath(".fits") as tmpFile:
253 mask.writeFits(tmpFile)
255 # Read it back
256 md = lsst.daf.base.PropertySet()
257 rmask = self.Mask(tmpFile, 0, md)
259 self.assertMasksEqual(mask, rmask)
261 # Check that we wrote (and read) the metadata successfully
262 mp_ = "MP_" if True else self.Mask.maskPlanePrefix() # currently private
263 for (k, v) in self.Mask().getMaskPlaneDict().items():
264 self.assertEqual(md.getArray(mp_ + k), v)
266 def testReadWriteXY0(self):
267 """Test that we read and write (X0, Y0) correctly"""
268 mask = afwImage.Mask(lsst.geom.ExtentI(10, 20))
270 x0, y0 = 1, 2
271 mask.setXY0(x0, y0)
272 with utilsTests.getTempFilePath(".fits") as tmpFile:
273 mask.writeFits(tmpFile)
275 mask2 = mask.Factory(tmpFile)
277 self.assertEqual(mask2.getX0(), x0)
278 self.assertEqual(mask2.getY0(), y0)
280 def testMaskInitialisation(self):
281 dims = self.mask1.getDimensions()
282 factory = self.mask1.Factory
284 self.mask1.set(666)
286 del self.mask1 # tempt C++ to reuse the memory
287 self.mask1 = factory(dims)
288 self.assertEqual(self.mask1[10, 10], 0)
290 del self.mask1
291 self.mask1 = factory(lsst.geom.ExtentI(20, 20))
292 self.assertEqual(self.mask1[10, 10], 0)
294 def testCtorWithPlaneDefs(self):
295 """Test that we can create a Mask with a given MaskPlaneDict"""
296 FOO, val = "FOO", 2
297 mask = afwImage.Mask(100, 200, {FOO: val})
298 mpd = mask.getMaskPlaneDict()
299 self.assertIn(FOO, mpd.keys())
300 self.assertEqual(mpd[FOO], val)
302 def testImageSlices(self):
303 """Test image slicing, which generate sub-images using Box2I under the covers"""
304 mask = afwImage.Mask(10, 20)
305 mask[-3:, -2:, afwImage.LOCAL] = 0x4
306 mask[4, 10] = 0x2
307 smask = mask[1:4, 6:10]
308 smask[:] = 0x8
309 mask[0:4, 0:4] = mask[2:6, 8:12]
311 if display:
312 afwDisplay.Display(frame=0).mtv(mask, title="testImageSlices")
314 self.assertEqual(mask[0, 6], 0)
315 self.assertEqual(mask[6, 17], 0)
316 self.assertEqual(mask[7, 18], 0x4)
317 self.assertEqual(mask[9, 19], 0x4)
318 self.assertEqual(mask[1, 6], 0x8)
319 self.assertEqual(mask[3, 9], 0x8)
320 self.assertEqual(mask[4, 10], 0x2)
321 self.assertEqual(mask[4, 9], 0)
322 self.assertEqual(mask[2, 2], 0x2)
323 self.assertEqual(mask[0, 0], 0x8)
325 def testInterpret(self):
326 """Interpretation of Mask values"""
327 planes = self.Mask().getMaskPlaneDict()
328 im = self.Mask(len(planes), 1)
330 allBits = 0
331 for i, p in enumerate(planes):
332 bitmask = self.Mask.getPlaneBitMask(p)
333 allBits |= bitmask
334 self.assertEqual(im.interpret(bitmask), p)
335 im.getArray()[0, i] = bitmask
336 self.assertEqual(im.getAsString(i, 0), p)
337 self.assertEqual(self.Mask.interpret(allBits),
338 ",".join(sorted(planes.keys())))
340 def testString(self):
341 mask = afwImage.Mask(100, 100)
342 self.assertIn(str(np.zeros((100, 100), dtype=mask.dtype)), str(mask))
343 self.assertIn("bbox=%s"%str(mask.getBBox()), str(mask))
344 self.assertIn("maskPlaneDict=%s"%str(mask.getMaskPlaneDict()), str(mask))
346 smallMask = afwImage.Mask(2, 2)
347 self.assertIn(str(np.zeros((2, 2), dtype=mask.dtype)), str(smallMask))
349 self.assertIn("MaskX=", repr(mask))
352class OldMaskTestCase(unittest.TestCase):
353 """A test case for Mask (based on Mask_1.cc); these are taken over from the DC2 fw tests
354 and modified to run with the new (DC3) APIs"""
356 def setUp(self):
357 self.Mask = afwImage.Mask[afwImage.MaskPixel] # the class
359 self.testMask = self.Mask(lsst.geom.Extent2I(300, 400), 0)
361 # Store the default mask planes for later use
362 maskPlaneDict = self.Mask().getMaskPlaneDict()
363 self.defaultMaskPlanes = sorted(
364 maskPlaneDict, key=maskPlaneDict.__getitem__)
366 # reset so tests will be deterministic
367 self.Mask.clearMaskPlaneDict()
368 for p in ("CR", "BP"):
369 self.Mask.addMaskPlane(p)
371 self.region = lsst.geom.Box2I(lsst.geom.Point2I(100, 300),
372 lsst.geom.Extent2I(10, 40))
373 self.subTestMask = self.Mask(
374 self.testMask, self.region, afwImage.LOCAL)
376 if False:
377 self.pixelList = afwImage.listPixelCoord()
378 for x in range(0, 300):
379 for y in range(300, 400, 20):
380 self.pixelList.push_back(afwImage.PixelCoord(x, y))
382 def tearDown(self):
383 del self.testMask
384 del self.subTestMask
385 del self.region
386 # Reset the mask plane to the default
387 self.Mask.clearMaskPlaneDict()
388 for p in self.defaultMaskPlanes:
389 self.Mask.addMaskPlane(p)
391 def testPlaneAddition(self):
392 """Test mask plane addition"""
394 nplane = self.testMask.getNumPlanesUsed()
395 for p in ("XCR", "XBP"):
396 self.assertEqual(self.Mask.addMaskPlane(p),
397 nplane, f"Assigning plane {p}")
398 nplane += 1
400 def pname(p):
401 return f"P{p}"
403 nextra = 8
404 for p in range(0, nextra):
405 self.Mask.addMaskPlane(pname(p))
407 for p in range(0, nextra):
408 self.testMask.removeAndClearMaskPlane(pname(p))
410 self.assertEqual(
411 nplane + nextra, self.Mask.getNumPlanesUsed(), "Adding and removing planes")
413 for p in range(0, nextra):
414 self.Mask.removeMaskPlane(pname(p))
416 self.assertEqual(nplane, self.testMask.getNumPlanesUsed(),
417 "Adding and removing planes")
419 def testMetadata(self):
420 """Test mask plane metadata interchange with MaskPlaneDict"""
421 #
422 # Demonstrate that we can extract a MaskPlaneDict into metadata
423 #
424 metadata = lsst.daf.base.PropertySet()
426 self.Mask.addMaskPlanesToMetadata(metadata)
427 for (k, v) in self.Mask().getMaskPlaneDict().items():
428 self.assertEqual(metadata.getInt(f"MP_{k}"), v)
429 #
430 # Now add another plane to metadata and make it appear in the mask Dict, albeit
431 # in general at another location (hence the getNumPlanesUsed call)
432 #
433 metadata.addInt("MP_" + "Whatever", self.Mask.getNumPlanesUsed())
435 self.testMask.conformMaskPlanes(
436 self.Mask.parseMaskPlaneMetadata(metadata))
437 for (k, v) in self.Mask().getMaskPlaneDict().items():
438 self.assertEqual(metadata.getInt(f"MP_{k}"), v)
440 def testPlaneOperations(self):
441 """Test mask plane operations"""
443 planes = self.Mask().getMaskPlaneDict()
444 self.testMask.clearMaskPlane(planes['CR'])
446 if False:
447 for p in planes.keys():
448 self.testMask.setMaskPlaneValues(planes[p], self.pixelList)
450 # print "\nClearing mask"
451 self.testMask.clearMaskPlane(planes['CR'])
453 def testPlaneRemoval(self):
454 """Test mask plane removal"""
456 def checkPlaneBP():
457 self.Mask.getMaskPlane("BP")
459 testMask2 = self.Mask(self.testMask.getDimensions())
460 self.testMask = self.Mask(self.testMask.getDimensions())
461 self.testMask.removeAndClearMaskPlane("BP")
463 testMask2.getMaskPlaneDict()
465 # still present in default mask
466 checkPlaneBP()
467 # should still be in testMask2
468 self.assertIn("BP", testMask2.getMaskPlaneDict())
470 self.Mask.removeMaskPlane("BP") # remove from default mask too
472 self.assertRaises(pexExcept.InvalidParameterError, checkPlaneBP)
474 self.assertRaises(pexExcept.InvalidParameterError,
475 lambda: self.Mask.removeMaskPlane("BP")) # Plane is already removed
476 self.assertRaises(pexExcept.InvalidParameterError,
477 lambda: self.testMask.removeMaskPlane("RHL gets names right"))
478 #
479 self.Mask.clearMaskPlaneDict()
480 self.Mask.addMaskPlane("P0")
481 self.Mask.addMaskPlane("P1")
482 # a no-op -- readding a plane has no effect
483 self.Mask.addMaskPlane("P1")
484 #
485 # Check that removing default mask planes doesn't affect pre-existing planes
486 #
487 msk = self.Mask()
488 nmask = len(msk.getMaskPlaneDict())
489 self.Mask.removeMaskPlane("P0")
490 self.Mask.removeMaskPlane("P1")
491 self.assertEqual(len(msk.getMaskPlaneDict()), nmask)
492 del msk
493 #
494 # Check that removeAndClearMaskPlane can clear the default too
495 #
496 self.Mask.addMaskPlane("BP")
497 self.testMask.removeAndClearMaskPlane("BP", True)
499 self.assertRaises(pexExcept.InvalidParameterError, checkPlaneBP)
501 def testInvalidPlaneOperations(self):
502 """Test mask plane operations invalidated by Mask changes"""
504 testMask3 = self.Mask(self.testMask.getDimensions())
506 name = "Great Timothy"
507 self.Mask.addMaskPlane(name)
508 testMask3.removeAndClearMaskPlane(name)
510 self.Mask.getMaskPlane(name) # should be fine
511 self.assertRaises(KeyError, lambda: testMask3.getMaskPlaneDict()[name])
513 def tst():
514 self.testMask |= testMask3
516 self.assertRaises(pexExcept.RuntimeError, tst)
518 # The dictionary should be back to the same state, so ...
519 self.Mask.addMaskPlane(name)
520 tst # ... assertion should not fail
522 self.testMask.removeAndClearMaskPlane(name, True)
523 self.Mask.addMaskPlane("Mario") # takes name's slot
524 self.Mask.addMaskPlane(name)
526 self.assertRaises(pexExcept.RuntimeError, tst)
528 def testInvalidPlaneOperations2(self):
529 """Test mask plane operations invalidated by Mask changes"""
531 testMask3 = self.Mask(self.testMask.getDimensions())
533 name = "Great Timothy"
534 name2 = "Our Boss"
535 self.Mask.addMaskPlane(name)
536 self.Mask.addMaskPlane(name2)
537 # a description of the Mask's current dictionary
538 oldDict = testMask3.getMaskPlaneDict()
540 for n in (name, name2):
541 self.testMask.removeAndClearMaskPlane(n, True)
543 # added in opposite order to the planes in testMask3
544 self.Mask.addMaskPlane(name2)
545 self.Mask.addMaskPlane(name)
547 self.assertNotEqual(self.testMask.getMaskPlaneDict()[
548 name], oldDict[name])
550 def tst():
551 self.testMask |= testMask3
553 self.testMask.removeAndClearMaskPlane("BP")
555 self.assertRaises(pexExcept.RuntimeError, tst)
556 #
557 # OK, that failed as it should. Fixup the dictionaries and try again
558 #
559 self.Mask.addMaskPlane("BP")
560 # convert testMask3 from oldDict to current default
561 testMask3.conformMaskPlanes(oldDict)
563 self.testMask |= testMask3 # shouldn't throw
565 def testConformMaskPlanes(self):
566 """Test conformMaskPlanes() when the two planes are actually the same"""
568 testMask3 = self.Mask(self.testMask.getDimensions())
570 name = "XXX"
571 self.Mask.addMaskPlane(name)
572 oldDict = testMask3.getMaskPlaneDict()
573 # invalidates dictionary version
574 testMask3.removeAndClearMaskPlane(name)
576 testMask3.conformMaskPlanes(oldDict)
578 self.testMask |= testMask3
580 def testConformMaskPlanes2(self):
581 """Test conformMaskPlanes() when the two planes are different"""
583 testMask3 = afwImage.Mask(self.testMask.getDimensions())
585 name1 = "Great Timothy"
586 name2 = "Our Boss"
587 p1 = self.Mask.addMaskPlane(name1)
588 p2 = self.Mask.addMaskPlane(name2)
589 oldDict = self.testMask.getMaskPlaneDict()
591 testMask3.setMaskPlaneValues(p1, 0, 5, 0)
592 testMask3.setMaskPlaneValues(p2, 0, 5, 1)
594 if display:
595 afwDisplay.Display(frame=1).mtv(testMask3, title="testConformMaskPlanes2")
597 self.assertEqual(testMask3[0, 0], testMask3.getPlaneBitMask(name1))
598 self.assertEqual(testMask3[0, 1], testMask3.getPlaneBitMask(name2))
600 self.testMask.removeAndClearMaskPlane(name1, True)
601 self.testMask.removeAndClearMaskPlane(name2, True)
602 self.Mask.addMaskPlane(name2) # added in opposite order to testMask3
603 self.Mask.addMaskPlane(name1)
605 self.assertEqual(self.testMask[0, 0], 0)
607 if display:
608 afwDisplay.Display(frame=2).mtv(testMask3, title="testConformMaskPlanes2 (cleared and re-added)")
610 self.assertNotEqual(testMask3[0, 0],
611 testMask3.getPlaneBitMask(name1))
612 self.assertNotEqual(testMask3[0, 1],
613 testMask3.getPlaneBitMask(name2))
615 testMask3.conformMaskPlanes(oldDict)
617 self.assertEqual(testMask3[0, 0], testMask3.getPlaneBitMask(name1))
618 self.assertEqual(testMask3[0, 1], testMask3.getPlaneBitMask(name2))
620 if display:
621 afwDisplay.Display(frame=3).mtv(testMask3, title="testConformMaskPlanes2 (conform with oldDict)")
623 self.testMask |= testMask3
626def printMaskPlane(mask, plane,
627 xrange=list(range(250, 300, 10)), yrange=list(range(300, 400, 20))):
628 """Print parts of the specified plane of the mask"""
630 xrange = list(range(min(xrange), max(xrange), 25))
631 yrange = list(range(min(yrange), max(yrange), 25))
633 for x in xrange:
634 for y in yrange:
635 if False: # mask(x,y) confuses swig
636 print(x, y, mask(x, y), mask(x, y, plane))
637 else:
638 print(x, y, mask(x, y, plane))
641class TestMemory(lsst.utils.tests.MemoryTestCase):
642 pass
645def setup_module(module):
646 lsst.utils.tests.init()
649if __name__ == "__main__": 649 ↛ 650line 649 didn't jump to line 650, because the condition on line 649 was never true
650 lsst.utils.tests.init()
651 unittest.main()