Coverage for tests/test_isrFunctions.py: 17%
167 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-16 10:23 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-16 10:23 +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.geom as geom
27import lsst.afw.image as afwImage
28import lsst.utils.tests
29import lsst.ip.isr as ipIsr
30import lsst.ip.isr.isrMock as isrMock
33def computeImageMedianAndStd(image):
34 """Function to calculate median and std of image data.
36 Parameters
37 ----------
38 image : `lsst.afw.image.Image`
39 Image to measure statistics on.
41 Returns
42 -------
43 median : `float`
44 Image median.
45 std : `float`
46 Image stddev.
47 """
48 median = np.nanmedian(image.getArray())
49 std = np.nanstd(image.getArray())
51 return (median, std)
54class IsrFunctionsCases(lsst.utils.tests.TestCase):
55 """Test that functions for ISR produce expected outputs.
56 """
57 def setUp(self):
58 self.inputExp = isrMock.TrimmedRawMock().run()
59 self.mi = self.inputExp.getMaskedImage()
61 def test_transposeMaskedImage(self):
62 """Expect height and width to be exchanged.
63 """
64 transposed = ipIsr.transposeMaskedImage(self.mi)
65 self.assertEqual(transposed.getImage().getBBox().getHeight(),
66 self.mi.getImage().getBBox().getWidth())
67 self.assertEqual(transposed.getImage().getBBox().getWidth(),
68 self.mi.getImage().getBBox().getHeight())
70 def test_interpolateDefectList(self):
71 """Expect number of interpolated pixels to be non-zero.
72 """
73 defectList = isrMock.DefectMock().run()
74 self.assertEqual(len(defectList), 1)
76 for fallbackValue in (None, -999.0):
77 for haveMask in (True, False):
78 with self.subTest(fallbackValue=fallbackValue, haveMask=haveMask):
79 if haveMask is False:
80 if 'INTRP' in self.mi.getMask().getMaskPlaneDict():
81 self.mi.getMask().removeAndClearMaskPlane('INTRP')
82 else:
83 if 'INTRP' not in self.mi.getMask().getMaskPlaneDict():
84 self.mi.getMask().addMaskPlane('INTRP')
85 numBit = ipIsr.countMaskedPixels(self.mi, "INTRP")
86 self.assertEqual(numBit, 0)
88 def test_transposeDefectList(self):
89 """Expect bbox dimension values to flip.
90 """
91 defectList = isrMock.DefectMock().run()
92 transposed = defectList.transpose()
94 for d, t in zip(defectList, transposed):
95 self.assertEqual(d.getBBox().getDimensions().getX(), t.getBBox().getDimensions().getY())
96 self.assertEqual(d.getBBox().getDimensions().getY(), t.getBBox().getDimensions().getX())
98 def test_makeThresholdMask(self):
99 """Expect list of defects to have elements.
100 """
101 defectList = ipIsr.makeThresholdMask(self.mi, 200,
102 growFootprints=2,
103 maskName='SAT')
105 self.assertEqual(len(defectList), 1)
107 def test_interpolateFromMask(self):
108 """Expect number of interpolated pixels to be non-zero.
109 """
110 ipIsr.makeThresholdMask(self.mi, 200, growFootprints=2,
111 maskName='SAT')
112 for growFootprints in range(0, 3):
113 interpMaskedImage = ipIsr.interpolateFromMask(self.mi, 2.0,
114 growSaturatedFootprints=growFootprints,
115 maskNameList=['SAT'])
116 numBit = ipIsr.countMaskedPixels(interpMaskedImage, "INTRP")
117 self.assertEqual(numBit, 40800, msg=f"interpolateFromMask with growFootprints={growFootprints}")
119 def test_saturationCorrectionInterpolate(self):
120 """Expect number of mask pixels with SAT marked to be non-zero.
121 """
122 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0,
123 growFootprints=2, interpolate=True,
124 maskName='SAT')
125 numBit = ipIsr.countMaskedPixels(corrMaskedImage, "SAT")
126 self.assertEqual(numBit, 40800)
128 def test_saturationCorrectionNoInterpolate(self):
129 """Expect number of mask pixels with SAT marked to be non-zero.
130 """
131 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0,
132 growFootprints=2, interpolate=False,
133 maskName='SAT')
134 numBit = ipIsr.countMaskedPixels(corrMaskedImage, "SAT")
135 self.assertEqual(numBit, 40800)
137 def test_trimToMatchCalibBBox(self):
138 """Expect bounding boxes to match.
139 """
140 darkExp = isrMock.DarkMock().run()
141 darkMi = darkExp.getMaskedImage()
143 nEdge = 2
144 darkMi = darkMi[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
145 newInput = ipIsr.trimToMatchCalibBBox(self.mi, darkMi)
147 self.assertEqual(newInput.getImage().getBBox(), darkMi.getImage().getBBox())
149 def test_darkCorrection(self):
150 """Expect round-trip application to be equal.
151 Expect RuntimeError if sizes are different.
152 """
153 darkExp = isrMock.DarkMock().run()
154 darkMi = darkExp.getMaskedImage()
156 mi = self.mi.clone()
158 # The `invert` parameter controls the direction of the
159 # application. This will apply, and un-apply the dark.
160 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True)
161 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True, invert=True)
163 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3)
165 darkMi = darkMi[1:-1, 1:-1, afwImage.LOCAL]
166 with self.assertRaises(RuntimeError):
167 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=False)
169 def test_biasCorrection(self):
170 """Expect smaller median image value after.
171 Expect RuntimeError if sizes are different.
172 """
173 biasExp = isrMock.BiasMock().run()
174 biasMi = biasExp.getMaskedImage()
176 mi = self.mi.clone()
177 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=True)
178 self.assertLess(computeImageMedianAndStd(self.mi.getImage())[0],
179 computeImageMedianAndStd(mi.getImage())[0])
181 biasMi = biasMi[1:-1, 1:-1, afwImage.LOCAL]
182 with self.assertRaises(RuntimeError):
183 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=False)
185 def test_flatCorrection(self):
186 """Expect round-trip application to be equal.
187 Expect RuntimeError if sizes are different.
188 """
189 flatExp = isrMock.FlatMock().run()
190 flatMi = flatExp.getMaskedImage()
192 mi = self.mi.clone()
193 for scaling in ('USER', 'MEAN', 'MEDIAN'):
194 # The `invert` parameter controls the direction of the
195 # application. This will apply, and un-apply the flat.
196 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0, trimToFit=True)
197 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0,
198 trimToFit=True, invert=True)
200 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3,
201 msg=f"flatCorrection with scaling {scaling}")
203 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL]
204 with self.assertRaises(RuntimeError):
205 ipIsr.flatCorrection(self.mi, flatMi, 'USER', userScale=1.0, trimToFit=False)
207 def test_flatCorrectionUnknown(self):
208 """Raise if an unknown scaling is used.
210 The `scaling` parameter must be a known type. If not, the
211 flat correction will raise a RuntimeError.
212 """
213 flatExp = isrMock.FlatMock().run()
214 flatMi = flatExp.getMaskedImage()
216 with self.assertRaises(RuntimeError):
217 ipIsr.flatCorrection(self.mi, flatMi, "UNKNOWN", userScale=1.0, trimToFit=True)
219 def test_illumCorrection(self):
220 """Expect larger median value after.
221 Expect RuntimeError if sizes are different.
222 """
223 flatExp = isrMock.FlatMock().run()
224 flatMi = flatExp.getMaskedImage()
226 mi = self.mi.clone()
227 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0)
228 self.assertGreater(computeImageMedianAndStd(self.mi.getImage())[0],
229 computeImageMedianAndStd(mi.getImage())[0])
231 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL]
232 with self.assertRaises(RuntimeError):
233 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0, trimToFit=False)
235 def test_brighterFatterCorrection(self):
236 """Expect smoother image/smaller std before.
237 """
238 bfKern = isrMock.BfKernelMock().run()
240 before = computeImageMedianAndStd(self.inputExp.getImage())
241 ipIsr.brighterFatterCorrection(self.inputExp, bfKern, 10, 1e-2, False)
242 after = computeImageMedianAndStd(self.inputExp.getImage())
244 self.assertLess(before[1], after[1])
246 def test_gainContext(self):
247 """Expect image to be unmodified before and after
248 """
249 mi = self.inputExp.getMaskedImage().clone()
250 with ipIsr.gainContext(self.inputExp, self.inputExp.getImage(), apply=True):
251 pass
253 self.assertIsNotNone(mi)
254 self.assertMaskedImagesAlmostEqual(self.inputExp.getMaskedImage(), mi)
256 def test_widenSaturationTrails(self):
257 """Expect more mask pixels with SAT set after.
258 """
259 numBitBefore = ipIsr.countMaskedPixels(self.mi, "SAT")
261 ipIsr.widenSaturationTrails(self.mi.getMask())
262 numBitAfter = ipIsr.countMaskedPixels(self.mi, "SAT")
264 self.assertGreaterEqual(numBitAfter, numBitBefore)
266 def test_setBadRegions(self):
267 """Expect RuntimeError if improper statistic given.
268 Expect a float value otherwise.
269 """
270 for badStatistic in ('MEDIAN', 'MEANCLIP', 'UNKNOWN'):
271 if badStatistic == 'UNKNOWN':
272 with self.assertRaises(RuntimeError,
273 msg=f"setBadRegions did not fail for stat {badStatistic}"):
274 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic)
275 else:
276 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic)
277 self.assertGreaterEqual(abs(value), 0.0,
278 msg=f"setBadRegions did not find valid value for stat {badStatistic}")
280 def test_attachTransmissionCurve(self):
281 """Expect no failure and non-None output from attachTransmissionCurve.
282 """
283 curve = isrMock.TransmissionMock().run()
284 combined = ipIsr.attachTransmissionCurve(self.inputExp,
285 opticsTransmission=curve,
286 filterTransmission=curve,
287 sensorTransmission=curve,
288 atmosphereTransmission=curve)
289 # DM-19707: ip_isr functionality not fully tested by unit tests
290 self.assertIsNotNone(combined)
292 def test_attachTransmissionCurve_None(self):
293 """Expect no failure and non-None output from attachTransmissionCurve.
294 """
295 curve = None
296 combined = ipIsr.attachTransmissionCurve(self.inputExp,
297 opticsTransmission=curve,
298 filterTransmission=curve,
299 sensorTransmission=curve,
300 atmosphereTransmission=curve)
301 # DM-19707: ip_isr functionality not fully tested by unit tests
302 self.assertIsNotNone(combined)
304 def test_countMaskedPixels(self):
305 mockImageConfig = isrMock.IsrMock.ConfigClass()
307 # flatDrop is not really relevant as we replace the data
308 # but good to note it in case we change how this image is made
309 mockImageConfig.flatDrop = 0.99999
310 mockImageConfig.isTrimmed = True
312 flatExp = isrMock.FlatMock(config=mockImageConfig).run()
313 (shapeY, shapeX) = flatExp.getDimensions()
315 rng = np.random.RandomState(0)
316 flatMean = 1000
317 flatWidth = np.sqrt(flatMean)
318 flatData = rng.normal(flatMean, flatWidth, (shapeX, shapeY))
319 flatExp.image.array[:] = flatData
321 exp = flatExp.clone()
322 mi = exp.maskedImage
323 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), 0)
324 self.assertEqual(ipIsr.countMaskedPixels(mi, 'BAD'), 0)
326 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA")
327 noDataBox = geom.Box2I(geom.Point2I(31, 49), geom.Extent2I(3, 6))
328 mi.mask[noDataBox] |= NODATABIT
330 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), noDataBox.getArea())
331 self.assertEqual(ipIsr.countMaskedPixels(mi, 'BAD'), 0)
333 mi.mask[noDataBox] ^= NODATABIT # XOR to reset what we did
334 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), 0)
337class MemoryTester(lsst.utils.tests.MemoryTestCase):
338 pass
341def setup_module(module):
342 lsst.utils.tests.init()
345if __name__ == "__main__": 345 ↛ 346line 345 didn't jump to line 346, because the condition on line 345 was never true
346 lsst.utils.tests.init()
347 unittest.main()