Coverage for tests/test_isrFunctions.py: 20%
149 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 03:03 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 03:03 -0700
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.utils.tests
28import lsst.ip.isr as ipIsr
29import lsst.ip.isr.isrMock as isrMock
32def computeImageMedianAndStd(image):
33 """Function to calculate median and std of image data.
35 Parameters
36 ----------
37 image : `lsst.afw.image.Image`
38 Image to measure statistics on.
40 Returns
41 -------
42 median : `float`
43 Image median.
44 std : `float`
45 Image stddev.
46 """
47 median = np.nanmedian(image.getArray())
48 std = np.nanstd(image.getArray())
50 return (median, std)
53def countMaskedPixels(maskedImage, maskPlane):
54 """Function to count the number of masked pixels of a given type.
56 Parameters
57 ----------
58 maskedImage : `lsst.afw.image.MaskedImage`
59 Image to measure the mask on.
60 maskPlane : `str`
61 Name of the mask plane to count
63 Returns
64 -------
65 nMask : `int`
66 Number of masked pixels.
67 """
68 bitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
69 isBit = maskedImage.getMask().getArray() & bitMask > 0
70 numBit = np.sum(isBit)
72 return numBit
75class IsrFunctionsCases(lsst.utils.tests.TestCase):
76 """Test that functions for ISR produce expected outputs.
77 """
78 def setUp(self):
79 self.inputExp = isrMock.TrimmedRawMock().run()
80 self.mi = self.inputExp.getMaskedImage()
82 def test_transposeMaskedImage(self):
83 """Expect height and width to be exchanged.
84 """
85 transposed = ipIsr.transposeMaskedImage(self.mi)
86 self.assertEqual(transposed.getImage().getBBox().getHeight(),
87 self.mi.getImage().getBBox().getWidth())
88 self.assertEqual(transposed.getImage().getBBox().getWidth(),
89 self.mi.getImage().getBBox().getHeight())
91 def test_interpolateDefectList(self):
92 """Expect number of interpolated pixels to be non-zero.
93 """
94 defectList = isrMock.DefectMock().run()
95 self.assertEqual(len(defectList), 1)
97 for fallbackValue in (None, -999.0):
98 for haveMask in (True, False):
99 with self.subTest(fallbackValue=fallbackValue, haveMask=haveMask):
100 if haveMask is False:
101 if 'INTRP' in self.mi.getMask().getMaskPlaneDict():
102 self.mi.getMask().removeAndClearMaskPlane('INTRP')
103 else:
104 if 'INTRP' not in self.mi.getMask().getMaskPlaneDict():
105 self.mi.getMask().addMaskPlane('INTRP')
106 numBit = countMaskedPixels(self.mi, "INTRP")
107 self.assertEqual(numBit, 0)
109 def test_transposeDefectList(self):
110 """Expect bbox dimension values to flip.
111 """
112 defectList = isrMock.DefectMock().run()
113 transposed = defectList.transpose()
115 for d, t in zip(defectList, transposed):
116 self.assertEqual(d.getBBox().getDimensions().getX(), t.getBBox().getDimensions().getY())
117 self.assertEqual(d.getBBox().getDimensions().getY(), t.getBBox().getDimensions().getX())
119 def test_makeThresholdMask(self):
120 """Expect list of defects to have elements.
121 """
122 defectList = ipIsr.makeThresholdMask(self.mi, 200,
123 growFootprints=2,
124 maskName='SAT')
126 self.assertEqual(len(defectList), 1)
128 def test_interpolateFromMask(self):
129 """Expect number of interpolated pixels to be non-zero.
130 """
131 ipIsr.makeThresholdMask(self.mi, 200, growFootprints=2,
132 maskName='SAT')
133 for growFootprints in range(0, 3):
134 interpMaskedImage = ipIsr.interpolateFromMask(self.mi, 2.0,
135 growSaturatedFootprints=growFootprints,
136 maskNameList=['SAT'])
137 numBit = countMaskedPixels(interpMaskedImage, "INTRP")
138 self.assertEqual(numBit, 40800, msg=f"interpolateFromMask with growFootprints={growFootprints}")
140 def test_saturationCorrectionInterpolate(self):
141 """Expect number of mask pixels with SAT marked to be non-zero.
142 """
143 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0,
144 growFootprints=2, interpolate=True,
145 maskName='SAT')
146 numBit = countMaskedPixels(corrMaskedImage, "SAT")
147 self.assertEqual(numBit, 40800)
149 def test_saturationCorrectionNoInterpolate(self):
150 """Expect number of mask pixels with SAT marked to be non-zero.
151 """
152 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0,
153 growFootprints=2, interpolate=False,
154 maskName='SAT')
155 numBit = countMaskedPixels(corrMaskedImage, "SAT")
156 self.assertEqual(numBit, 40800)
158 def test_trimToMatchCalibBBox(self):
159 """Expect bounding boxes to match.
160 """
161 darkExp = isrMock.DarkMock().run()
162 darkMi = darkExp.getMaskedImage()
164 nEdge = 2
165 darkMi = darkMi[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
166 newInput = ipIsr.trimToMatchCalibBBox(self.mi, darkMi)
168 self.assertEqual(newInput.getImage().getBBox(), darkMi.getImage().getBBox())
170 def test_darkCorrection(self):
171 """Expect round-trip application to be equal.
172 Expect RuntimeError if sizes are different.
173 """
174 darkExp = isrMock.DarkMock().run()
175 darkMi = darkExp.getMaskedImage()
177 mi = self.mi.clone()
179 # The `invert` parameter controls the direction of the
180 # application. This will apply, and un-apply the dark.
181 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True)
182 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True, invert=True)
184 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3)
186 darkMi = darkMi[1:-1, 1:-1, afwImage.LOCAL]
187 with self.assertRaises(RuntimeError):
188 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=False)
190 def test_biasCorrection(self):
191 """Expect smaller median image value after.
192 Expect RuntimeError if sizes are different.
193 """
194 biasExp = isrMock.BiasMock().run()
195 biasMi = biasExp.getMaskedImage()
197 mi = self.mi.clone()
198 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=True)
199 self.assertLess(computeImageMedianAndStd(self.mi.getImage())[0],
200 computeImageMedianAndStd(mi.getImage())[0])
202 biasMi = biasMi[1:-1, 1:-1, afwImage.LOCAL]
203 with self.assertRaises(RuntimeError):
204 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=False)
206 def test_flatCorrection(self):
207 """Expect round-trip application to be equal.
208 Expect RuntimeError if sizes are different.
209 """
210 flatExp = isrMock.FlatMock().run()
211 flatMi = flatExp.getMaskedImage()
213 mi = self.mi.clone()
214 for scaling in ('USER', 'MEAN', 'MEDIAN'):
215 # The `invert` parameter controls the direction of the
216 # application. This will apply, and un-apply the flat.
217 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0, trimToFit=True)
218 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0,
219 trimToFit=True, invert=True)
221 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3,
222 msg=f"flatCorrection with scaling {scaling}")
224 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL]
225 with self.assertRaises(RuntimeError):
226 ipIsr.flatCorrection(self.mi, flatMi, 'USER', userScale=1.0, trimToFit=False)
228 def test_flatCorrectionUnknown(self):
229 """Raise if an unknown scaling is used.
231 The `scaling` parameter must be a known type. If not, the
232 flat correction will raise a RuntimeError.
233 """
234 flatExp = isrMock.FlatMock().run()
235 flatMi = flatExp.getMaskedImage()
237 with self.assertRaises(RuntimeError):
238 ipIsr.flatCorrection(self.mi, flatMi, "UNKNOWN", userScale=1.0, trimToFit=True)
240 def test_illumCorrection(self):
241 """Expect larger median value after.
242 Expect RuntimeError if sizes are different.
243 """
244 flatExp = isrMock.FlatMock().run()
245 flatMi = flatExp.getMaskedImage()
247 mi = self.mi.clone()
248 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0)
249 self.assertGreater(computeImageMedianAndStd(self.mi.getImage())[0],
250 computeImageMedianAndStd(mi.getImage())[0])
252 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL]
253 with self.assertRaises(RuntimeError):
254 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0, trimToFit=False)
256 def test_brighterFatterCorrection(self):
257 """Expect smoother image/smaller std before.
258 """
259 bfKern = isrMock.BfKernelMock().run()
261 before = computeImageMedianAndStd(self.inputExp.getImage())
262 ipIsr.brighterFatterCorrection(self.inputExp, bfKern, 10, 1e-2, False)
263 after = computeImageMedianAndStd(self.inputExp.getImage())
265 self.assertLess(before[1], after[1])
267 def test_gainContext(self):
268 """Expect image to be unmodified before and after
269 """
270 mi = self.inputExp.getMaskedImage().clone()
271 with ipIsr.gainContext(self.inputExp, self.inputExp.getImage(), apply=True):
272 pass
274 self.assertIsNotNone(mi)
275 self.assertMaskedImagesAlmostEqual(self.inputExp.getMaskedImage(), mi)
277 def test_widenSaturationTrails(self):
278 """Expect more mask pixels with SAT set after.
279 """
280 numBitBefore = countMaskedPixels(self.mi, "SAT")
282 ipIsr.widenSaturationTrails(self.mi.getMask())
283 numBitAfter = countMaskedPixels(self.mi, "SAT")
285 self.assertGreaterEqual(numBitAfter, numBitBefore)
287 def test_setBadRegions(self):
288 """Expect RuntimeError if improper statistic given.
289 Expect a float value otherwise.
290 """
291 for badStatistic in ('MEDIAN', 'MEANCLIP', 'UNKNOWN'):
292 if badStatistic == 'UNKNOWN':
293 with self.assertRaises(RuntimeError,
294 msg=f"setBadRegions did not fail for stat {badStatistic}"):
295 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic)
296 else:
297 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic)
298 self.assertGreaterEqual(abs(value), 0.0,
299 msg=f"setBadRegions did not find valid value for stat {badStatistic}")
301 def test_attachTransmissionCurve(self):
302 """Expect no failure and non-None output from attachTransmissionCurve.
303 """
304 curve = isrMock.TransmissionMock().run()
305 combined = ipIsr.attachTransmissionCurve(self.inputExp,
306 opticsTransmission=curve,
307 filterTransmission=curve,
308 sensorTransmission=curve,
309 atmosphereTransmission=curve)
310 # DM-19707: ip_isr functionality not fully tested by unit tests
311 self.assertIsNotNone(combined)
313 def test_attachTransmissionCurve_None(self):
314 """Expect no failure and non-None output from attachTransmissionCurve.
315 """
316 curve = None
317 combined = ipIsr.attachTransmissionCurve(self.inputExp,
318 opticsTransmission=curve,
319 filterTransmission=curve,
320 sensorTransmission=curve,
321 atmosphereTransmission=curve)
322 # DM-19707: ip_isr functionality not fully tested by unit tests
323 self.assertIsNotNone(combined)
326class MemoryTester(lsst.utils.tests.MemoryTestCase):
327 pass
330def setup_module(module):
331 lsst.utils.tests.init()
334if __name__ == "__main__": 334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true
335 lsst.utils.tests.init()
336 unittest.main()