Coverage for tests/test_jointcal.py: 22%
283 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-10 01:57 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-10 01:57 -0700
1# This file is part of jointcal.
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 itertools
23import os.path
24import unittest
25from unittest import mock
27import numpy as np
28import pyarrow.parquet
29import astropy.time
31import lsst.log
32import lsst.utils
34import lsst.afw.table
35import lsst.daf.butler
36from lsst.daf.base import DateTime
37import lsst.geom
38from lsst.meas.algorithms import getRefFluxField, LoadIndexedReferenceObjectsTask, DatasetConfig
39import lsst.obs.base
40import lsst.pipe.base
41import lsst.jointcal
42from lsst.jointcal.jointcal import make_schema_table, extract_detector_catalog_from_visit_catalog
43from lsst.jointcal import MinimizeResult
44import lsst.jointcal.chi2
45import lsst.jointcal.testUtils
48# for MemoryTestCase
49def setup_module(module):
50 lsst.utils.tests.init()
53def make_fake_refcat(center, flux, filterName):
54 """Make a fake reference catalog."""
55 schema = LoadIndexedReferenceObjectsTask.makeMinimalSchema([filterName],
56 addProperMotion=True)
57 catalog = lsst.afw.table.SimpleCatalog(schema)
58 record = catalog.addNew()
59 record.setCoord(center)
60 record[filterName + '_flux'] = flux
61 record[filterName + '_fluxErr'] = flux*0.1
62 record['pm_ra'] = lsst.geom.Angle(1)
63 record['pm_dec'] = lsst.geom.Angle(2)
64 record['epoch'] = 65432.1
65 return catalog
68def make_fake_wcs():
69 """Return two simple SkyWcs objects, with slightly different sky positions.
71 Use the same pixel origins as the cfht_minimal data, but put the sky origin
72 at RA=0
73 """
74 crpix = lsst.geom.Point2D(931.517869, 2438.572109)
75 cd = np.array([[5.19513851e-05, -2.81124812e-07],
76 [-3.25186974e-07, -5.19112119e-05]])
77 crval1 = lsst.geom.SpherePoint(0.01, -0.01, lsst.geom.degrees)
78 crval2 = lsst.geom.SpherePoint(-0.01, 0.01, lsst.geom.degrees)
79 wcs1 = lsst.afw.geom.makeSkyWcs(crpix, crval1, cd)
80 wcs2 = lsst.afw.geom.makeSkyWcs(crpix, crval2, cd)
81 return wcs1, wcs2
84class TestJointcalVisitCatalog(lsst.utils.tests.TestCase):
85 """Tests of jointcal's sourceTable_visit parquet ->single detector afw
86 table catalog unrolling.
87 """
88 def setUp(self):
89 filename = os.path.join(os.path.dirname(__file__),
90 "data/subselected-sourceTable-0034690.parq")
91 file = pyarrow.parquet.ParquetFile(filename)
92 self.data = file.read(use_pandas_metadata=True).to_pandas()
93 self.config = lsst.jointcal.jointcal.JointcalConfig()
94 # NOTE: This parquet file is older and uses the earlier
95 # "capitalize first letter" naming convention for these fields.
96 self.config.sourceFluxType = "ApFlux_12_0"
97 # we don't actually need either fitter to run for these tests
98 self.config.doAstrometry = False
99 self.config.doPhotometry = False
100 self.jointcal = lsst.jointcal.JointcalTask(config=self.config)
102 def test_make_catalog_schema(self):
103 """Check that the slot fields required by CcdImage::loadCatalog are in
104 the schema returned by _make_catalog_schema().
105 """
106 table = make_schema_table()
107 self.assertTrue(table.getCentroidSlot().getMeasKey().isValid())
108 self.assertTrue(table.getCentroidSlot().getErrKey().isValid())
109 self.assertTrue(table.getShapeSlot().getMeasKey().isValid())
111 def test_extract_detector_catalog_from_visit_catalog(self):
112 """Spot check a value output by the script that generated the test
113 parquet catalog and check that the size of the returned catalog
114 is correct for each detectior.
115 """
116 detectorId = 56
117 table = make_schema_table()
118 catalog = extract_detector_catalog_from_visit_catalog(table,
119 self.data,
120 detectorId,
121 'ccd',
122 ['Ixx', 'Iyy', 'Ixy'],
123 self.config.sourceFluxType,
124 self.jointcal.log)
126 # The test catalog has a number of elements for each detector equal to the detector id.
127 self.assertEqual(len(catalog), detectorId)
128 self.assertIn(29798723617816629, catalog['id'])
129 matched = catalog[29798723617816629 == catalog['id']]
130 self.assertEqual(1715.734359473175, matched['slot_Centroid_x'])
131 self.assertEqual(89.06076509964362, matched['slot_Centroid_y'])
134class JointcalTestBase:
135 def setUp(self):
136 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages(100, 100)
137 self.ccdImageList = struct.ccdImageList
138 # so that countStars() returns nonzero results
139 for ccdImage in self.ccdImageList:
140 ccdImage.resetCatalogForFit()
142 self.goodChi2 = lsst.jointcal.chi2.Chi2Statistic()
143 # chi2/ndof == 2.0 should be non-bad
144 self.goodChi2.chi2 = 200.0
145 self.goodChi2.ndof = 100
147 self.badChi2 = lsst.jointcal.chi2.Chi2Statistic()
148 self.badChi2.chi2 = 600.0
149 self.badChi2.ndof = 100
151 self.nanChi2 = lsst.jointcal.chi2.Chi2Statistic()
152 self.nanChi2.chi2 = np.nan
153 self.nanChi2.ndof = 100
155 self.maxSteps = 20
156 self.name = "testing"
157 self.dataName = "fake"
158 self.whatToFit = "" # unneeded, since we're mocking the fitter
160 # Mock a Butler so the refObjLoaders have something to call `get()` on.
161 self.butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler)
162 self.butler.get.return_value.indexer = DatasetConfig().indexer
164 # Mock the association manager and give it access to the ccd list above.
165 self.associations = mock.Mock(spec=lsst.jointcal.Associations)
166 self.associations.getCcdImageList.return_value = self.ccdImageList
168 # a default config to be modified by individual tests
169 self.config = lsst.jointcal.jointcal.JointcalConfig()
172class TestJointcalIterateFit(JointcalTestBase, lsst.utils.tests.TestCase):
173 def setUp(self):
174 super().setUp()
175 # Mock the fitter and model, so we can force particular
176 # return values/exceptions. Default to "good" return values.
177 self.fitter = mock.Mock(spec=lsst.jointcal.PhotometryFit)
178 self.fitter.computeChi2.return_value = self.goodChi2
179 self.fitter.minimize.return_value = MinimizeResult.Converged
180 self.model = mock.Mock(spec=lsst.jointcal.SimpleFluxModel)
182 self.jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler)
184 def test_iterateFit_success(self):
185 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter,
186 self.maxSteps, self.name, self.whatToFit)
187 self.assertEqual(chi2, self.goodChi2)
188 # Once for the for loop, the second time for the rank update.
189 self.assertEqual(self.fitter.minimize.call_count, 2)
191 def test_iterateFit_writeChi2Outer(self):
192 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter,
193 self.maxSteps, self.name, self.whatToFit,
194 dataName=self.dataName)
195 self.assertEqual(chi2, self.goodChi2)
196 # Once for the for loop, the second time for the rank update.
197 self.assertEqual(self.fitter.minimize.call_count, 2)
198 # Default config should not call saveChi2Contributions
199 self.fitter.saveChi2Contributions.assert_not_called()
201 def test_iterateFit_failed(self):
202 self.fitter.minimize.return_value = MinimizeResult.Failed
204 with self.assertRaises(RuntimeError):
205 self.jointcal._iterate_fit(self.associations, self.fitter,
206 self.maxSteps, self.name, self.whatToFit)
207 self.assertEqual(self.fitter.minimize.call_count, 1)
209 def test_iterateFit_badFinalChi2(self):
210 log = mock.Mock(spec=lsst.log.Log)
211 self.jointcal.log = log
212 self.fitter.computeChi2.return_value = self.badChi2
214 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter,
215 self.maxSteps, self.name, self.whatToFit)
216 self.assertEqual(chi2, self.badChi2)
217 log.info.assert_called_with("%s %s", "Fit completed", self.badChi2)
218 log.error.assert_called_with("Potentially bad fit: High chi-squared/ndof.")
220 def test_iterateFit_exceedMaxSteps(self):
221 log = mock.Mock(spec=lsst.log.Log)
222 self.jointcal.log = log
223 self.fitter.minimize.return_value = MinimizeResult.Chi2Increased
224 maxSteps = 3
226 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter,
227 maxSteps, self.name, self.whatToFit)
228 self.assertEqual(chi2, self.goodChi2)
229 self.assertEqual(self.fitter.minimize.call_count, maxSteps)
230 log.error.assert_called_with("testing failed to converge after %s steps" % maxSteps)
232 def test_moderate_chi2_increase(self):
233 """DM-25159: warn, but don't fail, on moderate chi2 increases between
234 steps.
235 """
236 chi2_1 = lsst.jointcal.chi2.Chi2Statistic()
237 chi2_1.chi2 = 100.0
238 chi2_1.ndof = 100
239 chi2_2 = lsst.jointcal.chi2.Chi2Statistic()
240 chi2_2.chi2 = 300.0
241 chi2_2.ndof = 100
243 chi2s = [self.goodChi2, chi2_1, chi2_2, self.goodChi2, self.goodChi2]
244 self.fitter.computeChi2.side_effect = chi2s
245 self.fitter.minimize.side_effect = [MinimizeResult.Chi2Increased,
246 MinimizeResult.Chi2Increased,
247 MinimizeResult.Chi2Increased,
248 MinimizeResult.Converged,
249 MinimizeResult.Converged]
250 with lsst.log.UsePythonLogging(): # so that assertLogs works with lsst.log
251 with self.assertLogs("lsst.jointcal", level="WARNING") as logger:
252 self.jointcal._iterate_fit(self.associations, self.fitter,
253 self.maxSteps, self.name, self.whatToFit)
254 msg = "Significant chi2 increase by a factor of 300 / 100 = 3"
255 self.assertIn(msg, [rec.message for rec in logger.records])
257 def test_large_chi2_increase_fails(self):
258 """DM-25159: fail on large chi2 increases between steps."""
259 chi2_1 = lsst.jointcal.chi2.Chi2Statistic()
260 chi2_1.chi2 = 1e11
261 chi2_1.ndof = 100
262 chi2_2 = lsst.jointcal.chi2.Chi2Statistic()
263 chi2_2.chi2 = 1.123456e13 # to check floating point formatting
264 chi2_2.ndof = 100
266 chi2s = [chi2_1, chi2_1, chi2_2]
267 self.fitter.computeChi2.side_effect = chi2s
268 self.fitter.minimize.return_value = MinimizeResult.Chi2Increased
269 with lsst.log.UsePythonLogging(): # so that assertLogs works with lsst.log
270 with self.assertLogs("lsst.jointcal", level="WARNING") as logger:
271 with(self.assertRaisesRegex(RuntimeError, "Large chi2 increase")):
272 self.jointcal._iterate_fit(self.associations, self.fitter,
273 self.maxSteps, self.name, self.whatToFit)
274 msg = "Significant chi2 increase by a factor of 1.123e+13 / 1e+11 = 112.3"
275 self.assertIn(msg, [rec.message for rec in logger.records])
277 def test_invalid_model(self):
278 self.model.validate.return_value = False
279 with(self.assertRaises(ValueError)):
280 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "invalid")
282 def test_nonfinite_chi2(self):
283 self.fitter.computeChi2.return_value = self.nanChi2
284 with(self.assertRaises(FloatingPointError)):
285 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "nonfinite")
287 def test_writeChi2(self):
288 filename = "somefile"
289 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "writeCh2",
290 writeChi2Name=filename)
291 # logChi2AndValidate prepends `config.debugOutputPath` to the filename
292 self.fitter.saveChi2Contributions.assert_called_with("./"+filename+"{type}")
295class TestJointcalLoadRefCat(JointcalTestBase, lsst.utils.tests.TestCase):
297 def _make_fake_refcat(self):
298 """Mock a fake reference catalog and the bits necessary to use it."""
299 center = lsst.geom.SpherePoint(30, -30, lsst.geom.degrees)
300 flux = 10
301 radius = 1 * lsst.geom.degrees
302 filter = lsst.afw.image.FilterLabel(band='fake', physical="fake-filter")
304 fakeRefCat = make_fake_refcat(center, flux, filter.bandLabel)
305 fluxField = getRefFluxField(fakeRefCat.schema, filter.bandLabel)
306 returnStruct = lsst.pipe.base.Struct(refCat=fakeRefCat, fluxField=fluxField)
307 refObjLoader = mock.Mock(spec=LoadIndexedReferenceObjectsTask)
308 refObjLoader.loadSkyCircle.return_value = returnStruct
310 return refObjLoader, center, radius, filter, fakeRefCat
312 def test_load_reference_catalog(self):
313 refObjLoader, center, radius, filterLabel, fakeRefCat = self._make_fake_refcat()
315 config = lsst.jointcal.jointcal.JointcalConfig()
316 config.astrometryReferenceErr = 0.1 # our test refcats don't have coord errors
317 jointcal = lsst.jointcal.JointcalTask(config=config, butler=self.butler)
319 # NOTE: we cannot test application of proper motion here, because we
320 # mock the refObjLoader, so the real loader is never called.
321 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader,
322 jointcal.astrometryReferenceSelector,
323 center,
324 radius,
325 filterLabel)
326 # operator== isn't implemented for Catalogs, so we have to check like
327 # this, in case the records are copied during load.
328 self.assertEqual(len(refCat), len(fakeRefCat))
329 for r1, r2 in zip(refCat, fakeRefCat):
330 self.assertEqual(r1, r2)
332 def test_load_reference_catalog_subselect(self):
333 """Test that we can select out the one source in the fake refcat
334 with a ridiculous S/N cut.
335 """
336 refObjLoader, center, radius, filterLabel, fakeRefCat = self._make_fake_refcat()
338 config = lsst.jointcal.jointcal.JointcalConfig()
339 config.astrometryReferenceErr = 0.1 # our test refcats don't have coord errors
340 config.astrometryReferenceSelector.doSignalToNoise = True
341 config.astrometryReferenceSelector.signalToNoise.minimum = 1e10
342 config.astrometryReferenceSelector.signalToNoise.fluxField = "fake_flux"
343 config.astrometryReferenceSelector.signalToNoise.errField = "fake_fluxErr"
344 jointcal = lsst.jointcal.JointcalTask(config=config, butler=self.butler)
346 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader,
347 jointcal.astrometryReferenceSelector,
348 center,
349 radius,
350 filterLabel)
351 self.assertEqual(len(refCat), 0)
354class TestJointcalFitModel(JointcalTestBase, lsst.utils.tests.TestCase):
355 def test_fit_photometry_writeChi2(self):
356 """Test that we are calling saveChi2 with appropriate file prefixes."""
357 self.config.photometryModel = "constrainedFlux"
358 self.config.writeChi2FilesOuterLoop = True
359 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler)
360 jointcal.focalPlaneBBox = lsst.geom.Box2D()
362 # Mock the fitter, so we can pretend it found a good fit
363 with mock.patch("lsst.jointcal.PhotometryFit", autospec=True) as fitPatch:
364 fitPatch.return_value.computeChi2.return_value = self.goodChi2
365 fitPatch.return_value.minimize.return_value = MinimizeResult.Converged
367 # config.debugOutputPath is prepended to the filenames that go into saveChi2Contributions
368 expected = ["./photometry_init-ModelVisit_chi2", "./photometry_init-Model_chi2",
369 "./photometry_init-Fluxes_chi2", "./photometry_init-ModelFluxes_chi2"]
370 expected = [mock.call(x+"-fake{type}") for x in expected]
371 jointcal._fit_photometry(self.associations, dataName=self.dataName)
372 fitPatch.return_value.saveChi2Contributions.assert_has_calls(expected)
374 def test_fit_astrometry_writeChi2(self):
375 """Test that we are calling saveChi2 with appropriate file prefixes."""
376 self.config.astrometryModel = "constrained"
377 self.config.writeChi2FilesOuterLoop = True
378 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler)
379 jointcal.focalPlaneBBox = lsst.geom.Box2D()
381 # Mock the fitter, so we can pretend it found a good fit
382 fitPatch = mock.patch("lsst.jointcal.AstrometryFit")
383 # Mock the projection handler so we don't segfault due to not-fully initialized ccdImages
384 projectorPatch = mock.patch("lsst.jointcal.OneTPPerVisitHandler")
385 with fitPatch as fit, projectorPatch as projector:
386 fit.return_value.computeChi2.return_value = self.goodChi2
387 fit.return_value.minimize.return_value = MinimizeResult.Converged
388 # return a real ProjectionHandler to keep ConstrainedAstrometryModel() happy
389 projector.return_value = lsst.jointcal.IdentityProjectionHandler()
391 # config.debugOutputPath is prepended to the filenames that go into saveChi2Contributions
392 expected = ["./astrometry_init-DistortionsVisit_chi2", "./astrometry_init-Distortions_chi2",
393 "./astrometry_init-Positions_chi2", "./astrometry_init-DistortionsPositions_chi2"]
394 expected = [mock.call(x+"-fake{type}") for x in expected]
395 jointcal._fit_astrometry(self.associations, dataName=self.dataName)
396 fit.return_value.saveChi2Contributions.assert_has_calls(expected)
399class TestComputeBoundingCircle(lsst.utils.tests.TestCase):
400 """Tests of Associations.computeBoundingCircle()"""
401 def _checkPointsInCircle(self, points, center, radius):
402 """Check that all points are within the (center, radius) circle.
404 The test is whether the max(points - center) separation is equal to
405 (or slightly less than) radius.
406 """
407 maxSeparation = 0*lsst.geom.degrees
408 for point in points:
409 maxSeparation = max(maxSeparation, center.separation(point))
410 self.assertAnglesAlmostEqual(maxSeparation, radius, maxDiff=3*lsst.geom.arcseconds)
411 self.assertLess(maxSeparation, radius)
413 def _testPoints(self, ccdImage1, ccdImage2, skyWcs1, skyWcs2, bbox):
414 """Fill an Associations object and test that it computes the correct
415 bounding circle for the input data.
417 Parameters
418 ----------
419 ccdImage1, ccdImage2 : `lsst.jointcal.CcdImage`
420 The CcdImages to add to the Associations object.
421 skyWcs1, skyWcs2 : `lsst.afw.geom.SkyWcs`
422 The WCS of each of the above images.
423 bbox : `lsst.geom.Box2D`
424 The ccd bounding box of both images.
425 """
426 lsst.log.setLevel('jointcal', lsst.log.DEBUG)
427 associations = lsst.jointcal.Associations()
428 associations.addCcdImage(ccdImage1)
429 associations.addCcdImage(ccdImage2)
430 associations.computeCommonTangentPoint()
432 circle = associations.computeBoundingCircle()
433 center = lsst.geom.SpherePoint(circle.getCenter())
434 radius = lsst.geom.Angle(circle.getOpeningAngle().asRadians(), lsst.geom.radians)
435 points = [lsst.geom.SpherePoint(skyWcs1.pixelToSky(lsst.geom.Point2D(x)))
436 for x in bbox.getCorners()]
437 points.extend([lsst.geom.SpherePoint(skyWcs2.pixelToSky(lsst.geom.Point2D(x)))
438 for x in bbox.getCorners()])
439 self._checkPointsInCircle(points, center, radius)
441 def testPoints(self):
442 """Test for points in an "easy" area, far from RA=0 or the poles."""
443 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages()
444 self._testPoints(struct.ccdImageList[0], struct.ccdImageList[1],
445 struct.skyWcs[0], struct.skyWcs[1], struct.bbox)
447 def testPointsRA0(self):
448 """Test for CcdImages crossing RA=0; this demonstrates a fix for
449 the bug described in DM-19802.
450 """
451 wcs1, wcs2 = make_fake_wcs()
453 # Put the visit boresights at the WCS origin, for consistency
454 visitInfo1 = lsst.afw.image.VisitInfo(exposureId=30577512,
455 date=DateTime(date=65321.1),
456 boresightRaDec=wcs1.getSkyOrigin())
457 visitInfo2 = lsst.afw.image.VisitInfo(exposureId=30621144,
458 date=DateTime(date=65322.1),
459 boresightRaDec=wcs1.getSkyOrigin())
461 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2],
462 fakeVisitInfos=[visitInfo1, visitInfo2])
463 self._testPoints(struct.ccdImageList[0], struct.ccdImageList[1],
464 struct.skyWcs[0], struct.skyWcs[1], struct.bbox)
467class TestJointcalComputePMDate(JointcalTestBase, lsst.utils.tests.TestCase):
468 """Tests of jointcal._compute_proper_motion_epoch(), using fake dates."""
469 def test_compute_proper_motion_epoch(self):
470 mjds = np.array((65432.1, 66666.0, 55555.0, 44322.2))
472 wcs1, wcs2 = make_fake_wcs()
473 visitInfo1 = lsst.afw.image.VisitInfo(exposureId=30577512,
474 date=DateTime(date=mjds[0]),
475 boresightRaDec=wcs1.getSkyOrigin())
476 visitInfo2 = lsst.afw.image.VisitInfo(exposureId=30621144,
477 date=DateTime(date=mjds[1]),
478 boresightRaDec=wcs2.getSkyOrigin())
479 visitInfo3 = lsst.afw.image.VisitInfo(exposureId=30577513,
480 date=DateTime(date=mjds[2]),
481 boresightRaDec=wcs1.getSkyOrigin())
482 visitInfo4 = lsst.afw.image.VisitInfo(exposureId=30621145,
483 date=DateTime(date=mjds[3]),
484 boresightRaDec=wcs2.getSkyOrigin())
486 struct1 = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2],
487 fakeVisitInfos=[visitInfo1, visitInfo2])
488 struct2 = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2],
489 fakeVisitInfos=[visitInfo3, visitInfo4])
490 ccdImageList = list(itertools.chain(struct1.ccdImageList, struct2.ccdImageList))
491 associations = lsst.jointcal.Associations()
492 for ccdImage in ccdImageList:
493 associations.addCcdImage(ccdImage)
494 associations.computeCommonTangentPoint()
496 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler)
497 result = jointcal._compute_proper_motion_epoch(ccdImageList)
498 self.assertEqual(result.jyear, (astropy.time.Time(mjds, format="mjd", scale="tai").jyear).mean())
501class MemoryTester(lsst.utils.tests.MemoryTestCase):
502 pass
505if __name__ == "__main__": 505 ↛ 506line 505 didn't jump to line 506, because the condition on line 505 was never true
506 lsst.utils.tests.init()
507 unittest.main()