Coverage for tests / test_isrTask.py: 12%
373 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:58 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:58 +0000
1#
2# LSST Data Management System
3# Copyright 2008-2017 AURA/LSST.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
23import unittest
24import numpy as np
26import lsst.afw.image as afwImage
27import lsst.ip.isr.isrMock as isrMock
28import lsst.utils.tests
29from lsst.ip.isr.isrTask import (IsrTask, IsrTaskConfig)
30from lsst.ip.isr.isrQa import IsrQaConfig
31from lsst.pipe.base import Struct
32from lsst.ip.isr import PhotonTransferCurveDataset
35def countMaskedPixels(maskedImage, maskPlane):
36 """Function to count the number of masked pixels of a given type.
38 Parameters
39 ----------
40 maskedImage : `lsst.afw.image.MaskedImage`
41 Image to measure the mask on.
42 maskPlane : `str`
43 Name of the mask plane to count
45 Returns
46 -------
47 nMask : `int`
48 Number of masked pixels.
49 """
50 bitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
51 isBit = maskedImage.getMask().getArray() & bitMask > 0
52 numBit = np.sum(isBit)
53 return numBit
56def computeImageMedianAndStd(image):
57 """Function to calculate median and std of image data.
59 Parameters
60 ----------
61 image : `lsst.afw.image.Image`
62 Image to measure statistics on.
64 Returns
65 -------
66 median : `float`
67 Image median.
68 std : `float`
69 Image stddev.
70 """
71 median = np.nanmedian(image.getArray())
72 std = np.nanstd(image.getArray())
73 return (median, std)
76class IsrTaskTestCases(lsst.utils.tests.TestCase):
77 """Test IsrTask methods with trimmed raw data.
78 """
79 def setUp(self):
80 self.config = IsrTaskConfig()
81 self.config.overscan.doParallelOverscan = True
82 self.config.qa = IsrQaConfig()
83 self.task = IsrTask(config=self.config)
84 self.camera = isrMock.IsrMock().getCamera()
86 self.inputExp = isrMock.TrimmedRawMock().run()
87 self.amp = self.inputExp.getDetector()[0]
88 self.mi = self.inputExp.getMaskedImage()
90 def validateIsrData(self, results):
91 """results should be a struct with components that are
92 not None if included in the configuration file.
93 """
94 self.assertIsInstance(results, Struct)
95 if self.config.doBias is True:
96 self.assertIsNotNone(results.bias)
97 if self.config.doDark is True:
98 self.assertIsNotNone(results.dark)
99 if self.config.doFlat is True:
100 self.assertIsNotNone(results.flat)
101 if self.config.doFringe is True:
102 self.assertIsNotNone(results.fringes)
103 if self.config.doDefect is True:
104 self.assertIsNotNone(results.defects)
105 if self.config.doBrighterFatter is True:
106 self.assertIsNotNone(results.bfKernel)
107 if self.config.doAttachTransmissionCurve is True:
108 self.assertIsNotNone(results.opticsTransmission)
109 self.assertIsNotNone(results.filterTransmission)
110 self.assertIsNotNone(results.sensorTransmission)
111 self.assertIsNotNone(results.atmosphereTransmission)
113 def test_ensureExposure(self):
114 """Test that an exposure has a usable instance class.
115 """
116 self.assertIsInstance(self.task.ensureExposure(self.inputExp, self.camera, 0),
117 afwImage.Exposure)
119 def test_convertItoF(self):
120 """Test conversion from integer to floating point pixels.
121 """
122 result = self.task.convertIntToFloat(self.inputExp)
123 self.assertEqual(result.getImage().getArray().dtype, np.dtype("float32"))
124 self.assertEqual(result, self.inputExp)
126 def test_updateVariance(self):
127 """Expect The variance image should have a larger median value after
128 this operation.
129 """
130 ampName = self.amp.getName()
131 effectivePtc = PhotonTransferCurveDataset([ampName], "TEST_PTC", 1)
132 effectivePtc.gain[ampName] = self.amp.getGain()
133 effectivePtc.noise[ampName] = self.amp.getReadNoise()
134 statBefore = computeImageMedianAndStd(self.inputExp.variance[self.amp.getBBox()])
135 # effectivePtc will have noise and gain.
136 self.task.updateVariance(self.inputExp, self.amp, effectivePtc)
137 statAfter = computeImageMedianAndStd(self.inputExp.variance[self.amp.getBBox()])
138 self.assertGreater(statAfter[0], statBefore[0])
139 self.assertFloatsAlmostEqual(statBefore[0], 0.0, atol=1e-2)
140 self.assertFloatsAlmostEqual(statAfter[0], 8170.0195, atol=1e-2)
142 def test_defineEffectivePtc(self):
143 ampName = self.amp.getName()
144 inputPtc = PhotonTransferCurveDataset([ampName], "TEST_PTC", 1)
145 inputPtc.gain[ampName] = 1.2
146 inputPtc.noise[ampName] = 5
148 exposureMetadata = self.inputExp.getMetadata()
149 detector = self.inputExp.getDetector()
150 overscans = [self.task.overscanCorrection(self.inputExp, self.amp)]
151 bfGains = {ampName: 1.5}
153 # Defaults
154 # doEmpiricalReadNoise = False
155 # usePtcReadNoise = False
156 # usePtcGains = False
157 # doRaiseOnCalibMismatch= False
158 effectivePtc = self.task.defineEffectivePtc(inputPtc, detector, bfGains,
159 overscans,
160 exposureMetadata)
161 self.assertEqual(effectivePtc.gain[ampName], self.amp.getGain())
162 self.assertEqual(effectivePtc.noise[ampName], self.amp.getReadNoise())
164 # Use input PTC values
165 self.config.usePtcGains = True
166 self.config.usePtcReadNoise = True
167 effectivePtc = self.task.defineEffectivePtc(inputPtc, detector, bfGains,
168 overscans,
169 exposureMetadata)
170 self.assertEqual(effectivePtc.gain[ampName], inputPtc.gain[ampName])
171 self.assertEqual(effectivePtc.noise[ampName], inputPtc.noise[ampName])
173 # use empirical readout noise from overscan and PTC gain
174 self.config.usePtcGains = True
175 self.config.doEmpiricalReadNoise = True
176 effectivePtc = self.task.defineEffectivePtc(inputPtc, detector, bfGains,
177 overscans,
178 exposureMetadata)
179 self.assertFloatsAlmostEqual(
180 effectivePtc.noise[ampName],
181 39.8794613621691*effectivePtc.gain[ampName],
182 )
183 self.assertEqual(effectivePtc.gain[ampName], inputPtc.gain[ampName])
185 # Raise if the PTC and BFK gains don't match
186 results = None
187 with self.assertRaises(RuntimeError):
188 self.config.doRaiseOnCalibMismatch = True
189 results = self.task.defineEffectivePtc(inputPtc, detector, bfGains,
190 overscans,
191 exposureMetadata)
192 self.assertIsNone(results)
194 def test_darkCorrection(self):
195 """Expect the median image value should decrease after this operation.
196 """
197 darkIm = isrMock.DarkMock().run()
199 statBefore = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
200 self.task.darkCorrection(self.inputExp, darkIm)
201 statAfter = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
202 self.assertLess(statAfter[0], statBefore[0])
203 self.assertFloatsAlmostEqual(statBefore[0], 8070.0195, atol=1e-2)
204 self.assertFloatsAlmostEqual(statAfter[0], 8045.7773, atol=1e-2)
206 def test_darkCorrection_noVisitInfo(self):
207 """Expect the median image value should decrease after this operation.
208 """
209 darkIm = isrMock.DarkMock().run()
210 darkIm.getInfo().setVisitInfo(None)
212 statBefore = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
213 self.task.darkCorrection(self.inputExp, darkIm)
214 statAfter = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
215 self.assertLess(statAfter[0], statBefore[0])
216 self.assertFloatsAlmostEqual(statBefore[0], 8070.0195, atol=1e-2)
217 self.assertFloatsAlmostEqual(statAfter[0], 8045.7773, atol=1e-2)
219 def test_flatCorrection(self):
220 """Expect the image median should increase (divide by < 1).
221 """
222 flatIm = isrMock.FlatMock().run()
224 statBefore = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
225 self.task.flatCorrection(self.inputExp, flatIm)
226 statAfter = computeImageMedianAndStd(self.inputExp.image[self.amp.getBBox()])
227 self.assertGreater(statAfter[1], statBefore[1])
228 self.assertFloatsAlmostEqual(statAfter[1], 147407.02, atol=1e-2)
229 self.assertFloatsAlmostEqual(statBefore[1], 147.55304, atol=1e-2)
231 def test_saturationDetection(self):
232 """Expect the saturation level detection/masking to scale with
233 threshold.
234 """
235 ampB = self.amp.rebuild()
236 ampB.setSaturation(9000.0)
237 self.task.saturationDetection(self.inputExp, ampB.finish())
238 countBefore = countMaskedPixels(self.mi, "SAT")
240 ampB.setSaturation(8250.0)
241 self.task.saturationDetection(self.inputExp, ampB.finish())
242 countAfter = countMaskedPixels(self.mi, "SAT")
244 self.assertLessEqual(countBefore, countAfter)
245 self.assertEqual(countBefore, 43)
246 self.assertEqual(countAfter, 136)
248 def test_measureBackground(self):
249 """Expect the background measurement runs successfully and to save
250 metadata values.
251 """
252 self.config.qa.flatness.meshX = 20
253 self.config.qa.flatness.meshY = 20
254 self.task.measureBackground(self.inputExp, self.config.qa)
255 self.assertIsNotNone(self.inputExp.getMetadata().getScalar('SKYLEVEL'))
257 def test_flatContext(self):
258 """Expect the flat context manager runs successfully (applying both
259 flat and dark within the context), and results in the same
260 image data after completion.
261 """
262 darkExp = isrMock.DarkMock().run()
263 flatExp = isrMock.FlatMock().run()
265 mi = self.inputExp.getMaskedImage().clone()
266 with self.task.flatContext(self.inputExp, flatExp, darkExp):
267 contextStat = computeImageMedianAndStd(self.inputExp.getMaskedImage().getImage())
268 self.assertFloatsAlmostEqual(contextStat[0], 37165.594, atol=1e-2)
270 self.assertMaskedImagesAlmostEqual(mi, self.inputExp.getMaskedImage())
273class IsrTaskUnTrimmedTestCases(lsst.utils.tests.TestCase):
274 """Test IsrTask methods using untrimmed raw data.
275 """
276 def setUp(self):
277 self.config = IsrTaskConfig()
278 self.config.overscan.doParallelOverscan = True
279 self.config.qa = IsrQaConfig()
280 self.task = IsrTask(config=self.config)
282 self.mockConfig = isrMock.IsrMockConfig()
283 self.mockConfig.isTrimmed = False
284 self.doGenerateImage = True
285 self.dataContainer = isrMock.MockDataContainer(config=self.mockConfig)
286 self.camera = isrMock.IsrMock(config=self.mockConfig).getCamera()
288 self.inputExp = isrMock.RawMock(config=self.mockConfig).run()
289 self.amp = self.inputExp.getDetector()[0]
290 self.mi = self.inputExp.getMaskedImage()
292 def batchSetConfiguration(self, value):
293 """Set the configuration state to a consistent value.
295 Disable options we do not need as well.
297 Parameters
298 ----------
299 value : `bool`
300 Value to switch common ISR configuration options to.
301 """
302 self.config.qa.flatness.meshX = 20
303 self.config.qa.flatness.meshY = 20
304 self.config.doWrite = False
305 self.config.doLinearize = False
306 self.config.doCrosstalk = False
308 self.config.doConvertIntToFloat = value
309 self.config.doSaturation = value
310 self.config.doSuspect = value
311 self.config.doSetBadRegions = value
312 self.config.doOverscan = value
313 self.config.doBias = value
314 self.config.doVariance = value
315 self.config.doWidenSaturationTrails = value
316 self.config.doBrighterFatter = value
317 self.config.doDefect = value
318 self.config.doSaturationInterpolation = value
319 self.config.doDark = value
320 self.config.doStrayLight = value
321 self.config.doFlat = value
322 self.config.doFringe = value
323 self.config.doMeasureBackground = value
324 self.config.doVignette = value
325 self.config.doAttachTransmissionCurve = value
326 self.config.doUseOpticsTransmission = value
327 self.config.doUseFilterTransmission = value
328 self.config.doUseSensorTransmission = value
329 self.config.doUseAtmosphereTransmission = value
330 self.config.qa.saveStats = value
331 self.config.qa.doThumbnailOss = value
332 self.config.qa.doThumbnailFlattened = value
334 self.config.doApplyGains = not value
335 self.config.doCameraSpecificMasking = value
337 def validateIsrResults(self):
338 """results should be a struct with components that are
339 not None if included in the configuration file.
341 Returns
342 -------
343 results : `pipeBase.Struct`
344 Results struct generated from the current ISR configuration.
345 """
346 self.task = IsrTask(config=self.config)
347 results = self.task.run(self.inputExp,
348 camera=self.camera,
349 bias=self.dataContainer.get("bias"),
350 dark=self.dataContainer.get("dark"),
351 flat=self.dataContainer.get("flat"),
352 bfKernel=self.dataContainer.get("bfKernel"),
353 defects=self.dataContainer.get("defects"),
354 fringes=Struct(fringes=self.dataContainer.get("fringe"), seed=1234),
355 opticsTransmission=self.dataContainer.get("transmission_"),
356 filterTransmission=self.dataContainer.get("transmission_"),
357 sensorTransmission=self.dataContainer.get("transmission_"),
358 atmosphereTransmission=self.dataContainer.get("transmission_")
359 )
361 self.assertIsInstance(results, Struct)
362 self.assertIsInstance(results.exposure, afwImage.Exposure)
363 return results
365 def test_run_allTrue(self):
366 """Expect successful run with expected outputs when all non-exclusive
367 configuration options are on.
369 Output results should be tested more precisely by the
370 individual function tests.
372 """
373 self.batchSetConfiguration(True)
374 self.validateIsrResults()
376 def test_run_allFalse(self):
377 """Expect successful run with expected outputs when all non-exclusive
378 configuration options are off.
380 Output results should be tested more precisely by the
381 individual function tests.
383 """
384 self.batchSetConfiguration(False)
385 self.validateIsrResults()
387 def test_failCases(self):
388 """Expect failure with crosstalk enabled.
390 Output results should be tested more precisely by the
391 individual function tests.
392 """
393 self.batchSetConfiguration(True)
395 # This breaks it
396 self.config.doCrosstalk = True
398 with self.assertRaises(RuntimeError):
399 self.validateIsrResults()
401 def test_maskingCase_negativeVariance(self):
402 """Test masking cases of configuration parameters.
403 """
404 self.batchSetConfiguration(True)
405 self.config.overscan.doParallelOverscan = False
406 self.config.overscan.fitType = "POLY"
407 self.config.overscan.order = 1
409 self.config.doSaturation = False
410 self.config.doWidenSaturationTrails = False
411 self.config.doSaturationInterpolation = False
412 self.config.doSuspect = False
413 self.config.doSetBadRegions = False
414 self.config.doDefect = False
415 self.config.doBrighterFatter = False
417 self.config.maskNegativeVariance = True
418 self.config.doInterpolate = False
420 results = self.validateIsrResults()
422 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
423 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 0)
424 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
425 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 40800)
427 def test_maskingCase_noMasking(self):
428 """Test masking cases of configuration parameters.
429 """
430 self.batchSetConfiguration(True)
431 self.config.overscan.fitType = "POLY"
432 self.config.overscan.order = 1
434 self.config.doSaturation = False
435 self.config.doWidenSaturationTrails = False
436 self.config.doSaturationInterpolation = False
437 self.config.doSuspect = False
438 self.config.doSetBadRegions = False
439 self.config.doDefect = False
440 self.config.doBrighterFatter = False
442 self.config.maskNegativeVariance = False
443 self.config.doInterpolate = False
445 results = self.validateIsrResults()
447 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
448 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 0)
449 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
450 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 0)
452 def test_maskingCase_satMasking(self):
453 """Test masking cases of configuration parameters.
454 """
455 self.batchSetConfiguration(True)
456 self.config.overscan.fitType = "POLY"
457 self.config.overscan.order = 1
459 self.config.saturation = 20000.0
460 self.config.doSaturation = True
461 self.config.doWidenSaturationTrails = True
463 self.config.doSaturationInterpolation = False
464 self.config.doSuspect = False
465 self.config.doSetBadRegions = False
466 self.config.doDefect = False
467 self.config.doBrighterFatter = False
469 self.config.maskNegativeVariance = False # These are mock images.
471 results = self.validateIsrResults()
473 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
474 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 0)
475 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
476 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 0)
478 def test_maskingCase_satMaskingAndInterp(self):
479 """Test masking cases of configuration parameters.
480 """
481 self.batchSetConfiguration(True)
482 self.config.overscan.fitType = "POLY"
483 self.config.overscan.order = 1
485 self.config.saturation = 20000.0
486 self.config.doSaturation = True
487 self.config.doWidenSaturationTrails = True
488 self.config.doSaturationInterpolation = True
490 self.config.doSuspect = False
491 self.config.doSetBadRegions = False
492 self.config.doDefect = False
493 self.config.doBrighterFatter = False
495 self.config.maskNegativeVariance = False # These are mock images.
497 results = self.validateIsrResults()
499 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
500 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 0)
501 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
502 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 0)
504 def test_maskingCase_throughEdge(self):
505 """Test masking cases of configuration parameters.
506 """
507 self.batchSetConfiguration(True)
508 self.config.overscan.fitType = "POLY"
509 self.config.overscan.order = 1
511 self.config.saturation = 20000.0
512 self.config.doSaturation = True
513 self.config.doWidenSaturationTrails = True
514 self.config.doSaturationInterpolation = True
515 self.config.numEdgeSuspect = 5
516 self.config.doSuspect = True
518 self.config.doSetBadRegions = False
519 self.config.doDefect = False
520 self.config.doBrighterFatter = False
522 self.config.maskNegativeVariance = False # These are mock images.
524 results = self.validateIsrResults()
526 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
527 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 0)
528 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
529 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 0)
531 def test_maskingCase_throughDefects(self):
532 """Test masking cases of configuration parameters.
533 """
534 self.batchSetConfiguration(True)
535 self.config.overscan.fitType = "POLY"
536 self.config.overscan.order = 1
538 self.config.saturation = 20000.0
539 self.config.doSaturation = True
540 self.config.doWidenSaturationTrails = True
541 self.config.doSaturationInterpolation = True
542 self.config.numEdgeSuspect = 5
543 self.config.doSuspect = True
544 self.config.doDefect = True
546 self.config.doSetBadRegions = False
547 self.config.doBrighterFatter = False
549 self.config.maskNegativeVariance = False # These are mock images.
551 results = self.validateIsrResults()
553 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
554 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 2000)
555 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 3940)
556 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 2000)
558 def test_maskingCase_throughDefectsAmpEdges(self):
559 """Test masking cases of configuration parameters.
560 """
561 self.batchSetConfiguration(True)
562 self.config.overscan.fitType = "POLY"
563 self.config.overscan.order = 1
565 self.config.saturation = 20000.0
566 self.config.doSaturation = True
567 self.config.doWidenSaturationTrails = True
568 self.config.doSaturationInterpolation = True
569 self.config.numEdgeSuspect = 5
570 self.config.doSuspect = True
571 self.config.doDefect = True
572 self.config.edgeMaskLevel = 'AMP'
574 self.config.doSetBadRegions = False
575 self.config.doBrighterFatter = False
577 self.config.maskNegativeVariance = False # These are mock images.
579 results = self.validateIsrResults()
581 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
582 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 2000)
583 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 11280)
584 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 2000)
586 def test_maskingCase_throughBad(self):
587 """Test masking cases of configuration parameters.
588 """
589 self.batchSetConfiguration(True)
590 self.config.overscan.fitType = "POLY"
591 self.config.overscan.order = 1
593 self.config.saturation = 20000.0
594 self.config.doSaturation = True
595 self.config.doWidenSaturationTrails = True
596 self.config.doSaturationInterpolation = True
598 self.config.doSuspect = True
599 self.config.doDefect = True
600 self.config.doSetBadRegions = True
601 self.config.doBrighterFatter = False
603 self.config.maskNegativeVariance = False # These are mock images.
605 results = self.validateIsrResults()
607 self.assertEqual(countMaskedPixels(results.exposure, "SAT"), 0)
608 self.assertEqual(countMaskedPixels(results.exposure, "INTRP"), 2000)
609 self.assertEqual(countMaskedPixels(results.exposure, "SUSPECT"), 0)
610 self.assertEqual(countMaskedPixels(results.exposure, "BAD"), 2000)
612 def test_binnedExposures(self):
613 """Ensure that binned exposures have correct sizes."""
614 self.batchSetConfiguration(True)
615 self.config.doBinnedExposures = True
616 self.config.binFactor1 = 8
617 self.config.binFactor2 = 64
619 results = self.validateIsrResults()
621 original = results.exposure.image.array.shape
622 bin1 = results.outputBin1Exposure.image.array.shape
623 bin2 = results.outputBin2Exposure.image.array.shape
625 # Binning truncates, so check that the original and obtained
626 # binned images have the correct offset.
627 self.assertEqual(original[0] - bin1[0] * 8, 4)
628 self.assertEqual(original[1] - bin1[1] * 8, 0)
629 self.assertEqual(original[0] - bin2[0] * 64, 12)
630 self.assertEqual(original[1] - bin2[1] * 64, 8)
633class MemoryTester(lsst.utils.tests.MemoryTestCase):
634 pass
637def setup_module(module):
638 lsst.utils.tests.init()
641if __name__ == "__main__": 641 ↛ 642line 641 didn't jump to line 642 because the condition on line 641 was never true
642 lsst.utils.tests.init()
643 unittest.main(failfast=True)