Coverage for tests/test_overscanCorrection.py: 11%
218 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-22 02:24 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-22 02:24 -0700
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
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 <http://www.lsstcorp.org/LegalNotices/>.
21#
23import unittest
24import numpy as np
26import lsst.utils.tests
27import lsst.geom
28import lsst.afw.image as afwImage
29import lsst.afw.cameraGeom as cameraGeom
30import lsst.ip.isr as ipIsr
31import lsst.pipe.base as pipeBase
34def computeImageMedianAndStd(image):
35 """Function to calculate median and std of image data.
37 Parameters
38 ----------
39 image : `lsst.afw.image.Image`
40 Image to measure statistics on.
42 Returns
43 -------
44 median : `float`
45 Image median.
46 std : `float`
47 Image stddev.
48 """
49 median = np.nanmedian(image.getArray())
50 std = np.nanstd(image.getArray())
52 return (median, std)
55class IsrTestCases(lsst.utils.tests.TestCase):
57 def updateConfigFromKwargs(self, config, **kwargs):
58 """Common config from keywords.
59 """
60 fitType = kwargs.get('fitType', None)
61 if fitType:
62 config.overscan.fitType = fitType
64 order = kwargs.get('order', None)
65 if order:
66 config.overscan.order = order
68 def makeExposure(self, isTransposed=False):
69 # Define the camera geometry we'll use.
70 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera")
71 detectorBuilder = cameraBuilder.add("Fake amp", 0)
73 ampBuilder = cameraGeom.Amplifier.Builder()
75 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
76 lsst.geom.Extent2I(10, 10))
78 if isTransposed is True:
79 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
80 lsst.geom.Point2I(9, 12))
81 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 10),
82 lsst.geom.Point2I(9, 12))
83 else:
84 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
85 lsst.geom.Point2I(12, 9))
87 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 0),
88 lsst.geom.Point2I(12, 9))
90 ampBuilder.setRawBBox(fullBBox)
91 ampBuilder.setRawSerialOverscanBBox(overscanBBox)
92 ampBuilder.setRawDataBBox(dataBBox)
94 detectorBuilder.append(ampBuilder)
95 camera = cameraBuilder.finish()
96 detector = camera[0]
98 # Define image data.
99 maskedImage = afwImage.MaskedImageF(fullBBox)
100 maskedImage.set(10, 0x0, 1)
102 overscan = afwImage.MaskedImageF(maskedImage, overscanBBox)
103 overscan.set(2, 0x0, 1)
105 exposure = afwImage.ExposureF(maskedImage, None)
106 exposure.setDetector(detector)
107 return exposure
109 def checkOverscanCorrectionY(self, **kwargs):
110 exposure = self.makeExposure(isTransposed=True)
111 detector = exposure.getDetector()
113 # These subimages are needed below.
114 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()]
115 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()]
117 config = ipIsr.IsrTask.ConfigClass()
118 self.updateConfigFromKwargs(config, **kwargs)
120 if kwargs['fitType'] == "MEDIAN_PER_ROW":
121 # Add a bad point to test outlier rejection.
122 overscan.getImage().getArray()[0, 0] = 12345
124 # Shrink the sigma clipping limit to handle the fact that the
125 # bad point is not be rejected at higher thresholds (2/0.74).
126 config.overscan.numSigmaClip = 2.7
128 isrTask = ipIsr.IsrTask(config=config)
129 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True)
131 height = maskedImage.getHeight()
132 width = maskedImage.getWidth()
133 for j in range(height):
134 for i in range(width):
135 if j == 10 and i == 0 and kwargs['fitType'] == "MEDIAN_PER_ROW":
136 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 12343)
137 elif j >= 10:
138 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0)
139 else:
140 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8)
142 def checkOverscanCorrectionX(self, **kwargs):
143 exposure = self.makeExposure(isTransposed=False)
144 detector = exposure.getDetector()
146 # These subimages are needed below.
147 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()]
149 config = ipIsr.IsrTask.ConfigClass()
150 self.updateConfigFromKwargs(config, **kwargs)
152 isrTask = ipIsr.IsrTask(config=config)
153 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False)
155 height = maskedImage.getHeight()
156 width = maskedImage.getWidth()
157 for j in range(height):
158 for i in range(width):
159 if i >= 10:
160 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0)
161 else:
162 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8)
164 def checkOverscanCorrectionSineWave(self, **kwargs):
165 """vertical sine wave along long direction"""
166 # Define the camera geometry we'll use.
167 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera")
168 detectorBuilder = cameraBuilder.add("Fake amp", 0)
170 ampBuilder = cameraGeom.Amplifier.Builder()
172 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
173 lsst.geom.Extent2I(70, 500))
175 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
176 lsst.geom.Extent2I(100, 500))
178 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(70, 0),
179 lsst.geom.Extent2I(30, 500))
181 ampBuilder.setRawBBox(fullBBox)
182 ampBuilder.setRawSerialOverscanBBox(overscanBBox)
183 ampBuilder.setRawDataBBox(dataBBox)
185 detectorBuilder.append(ampBuilder)
186 camera = cameraBuilder.finish()
187 detector = camera[0]
189 # Define image data.
190 maskedImage = afwImage.MaskedImageF(fullBBox)
191 maskedImage.set(50, 0x0, 1)
193 overscan = afwImage.MaskedImageF(maskedImage, overscanBBox)
194 overscan.set(0, 0x0, 1)
196 exposure = afwImage.ExposureF(maskedImage, None)
197 exposure.setDetector(detector)
199 # vertical sine wave along long direction
200 x = np.linspace(0, 2*3.14159, 500)
201 a, w = 15, 5*3.14159
202 sineWave = 20 + a*np.sin(w*x)
203 sineWave = sineWave.astype(int)
205 fullImage = np.repeat(sineWave, 100).reshape((500, 100))
206 maskedImage.image.array += fullImage
208 config = ipIsr.IsrTask.ConfigClass()
209 self.updateConfigFromKwargs(config, **kwargs)
211 isrTask = ipIsr.IsrTask(config=config)
212 isrTask.overscan.run(exposure, detector.getAmplifiers()[0])
214 height = maskedImage.getHeight()
215 width = maskedImage.getWidth()
217 for j in range(height):
218 for i in range(width):
219 if i >= 70:
220 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.0)
221 else:
222 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 50.0)
224 def test_MedianPerRowOverscanCorrection(self):
225 self.checkOverscanCorrectionY(fitType="MEDIAN_PER_ROW")
226 self.checkOverscanCorrectionX(fitType="MEDIAN_PER_ROW")
227 self.checkOverscanCorrectionSineWave(fitType="MEDIAN_PER_ROW")
229 def test_MedianOverscanCorrection(self):
230 self.checkOverscanCorrectionY(fitType="MEDIAN")
231 self.checkOverscanCorrectionX(fitType="MEDIAN")
233 def checkPolyOverscanCorrectionX(self, **kwargs):
234 exposure = self.makeExposure(isTransposed=False)
235 detector = exposure.getDetector()
237 # These subimages are needed below.
238 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()]
239 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()]
241 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox()
242 overscan.getMaskedImage().set(2, 0x0, 1)
243 for i in range(bbox.getDimensions()[1]):
244 for j, off in enumerate([-0.5, 0.0, 0.5]):
245 overscan.image[j, i, afwImage.LOCAL] = 2+i+off
247 config = ipIsr.IsrTask.ConfigClass()
248 self.updateConfigFromKwargs(config, **kwargs)
250 isrTask = ipIsr.IsrTask(config=config)
251 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False)
253 height = maskedImage.getHeight()
254 width = maskedImage.getWidth()
255 for j in range(height):
256 for i in range(width):
257 if i == 10:
258 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5)
259 elif i == 11:
260 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0)
261 elif i == 12:
262 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5)
263 else:
264 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - j)
266 def checkPolyOverscanCorrectionY(self, **kwargs):
267 exposure = self.makeExposure(isTransposed=True)
268 detector = exposure.getDetector()
270 # These subimages are needed below.
271 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()]
272 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()]
274 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox()
275 overscan.getMaskedImage().set(2, 0x0, 1)
276 for i in range(bbox.getDimensions()[0]):
277 for j, off in enumerate([-0.5, 0.0, 0.5]):
278 overscan.image[i, j, afwImage.LOCAL] = 2+i+off
279 # maskedImage.getMaskedImage().set(10, 0x0, 1)
281 config = ipIsr.IsrTask.ConfigClass()
282 self.updateConfigFromKwargs(config, **kwargs)
284 isrTask = ipIsr.IsrTask(config=config)
285 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True)
287 height = maskedImage.getHeight()
288 width = maskedImage.getWidth()
289 for j in range(height):
290 for i in range(width):
291 if j == 10:
292 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5)
293 elif j == 11:
294 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0)
295 elif j == 12:
296 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5)
297 else:
298 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - i)
300 def test_PolyOverscanCorrection(self):
301 for fitType in ("POLY", "CHEB", "LEG"):
302 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5)
303 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5)
305 def test_SplineOverscanCorrection(self):
306 for fitType in ("NATURAL_SPLINE", "CUBIC_SPLINE", "AKIMA_SPLINE"):
307 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5)
308 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5)
310 def test_overscanCorrection(self):
311 """Expect that this should reduce the image variance with a full fit.
312 The default fitType of MEDIAN will reduce the median value.
314 This needs to operate on a RawMock() to have overscan data to use.
316 The output types may be different when fitType != MEDIAN.
317 """
318 exposure = self.makeExposure(isTransposed=False)
319 detector = exposure.getDetector()
320 amp = detector.getAmplifiers()[0]
322 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()])
324 config = ipIsr.IsrTask.ConfigClass()
325 isrTask = ipIsr.IsrTask(config=config)
326 oscanResults = isrTask.overscan.run(exposure, amp)
328 self.assertIsInstance(oscanResults, pipeBase.Struct)
329 self.assertIsInstance(oscanResults.imageFit, float)
330 self.assertIsInstance(oscanResults.overscanFit, float)
331 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF)
333 statAfter = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()])
334 self.assertLess(statAfter[0], statBefore[0])
336 def test_overscanCorrection_isNotInt(self):
337 """Expect smaller median/smaller std after.
338 Expect exception if overscan fit type isn't known.
339 """
340 exposure = self.makeExposure(isTransposed=False)
341 detector = exposure.getDetector()
342 amp = detector.getAmplifiers()[0]
344 for fitType in ('MEAN', 'MEDIAN', 'MEDIAN_PER_ROW', 'MEANCLIP', 'POLY', 'CHEB',
345 'NATURAL_SPLINE', 'CUBIC_SPLINE'):
346 if fitType in ('NATURAL_SPLINE', 'CUBIC_SPLINE'):
347 order = 3
348 else:
349 order = 1
350 config = ipIsr.IsrTask.ConfigClass()
351 config.overscan.order = order
352 config.overscan.fitType = fitType
353 isrTask = ipIsr.IsrTask(config=config)
355 response = isrTask.overscan.run(exposure, amp)
357 self.assertIsInstance(response, pipeBase.Struct,
358 msg=f"overscanCorrection overscanIsNotInt Bad response: {fitType}")
359 self.assertIsNotNone(response.imageFit,
360 msg=f"overscanCorrection overscanIsNotInt Bad imageFit: {fitType}")
361 self.assertIsNotNone(response.overscanFit,
362 msg=f"overscanCorrection overscanIsNotInt Bad overscanFit: {fitType}")
363 self.assertIsInstance(response.overscanImage, afwImage.ExposureF,
364 msg=f"overscanCorrection overscanIsNotInt Bad overscanImage: {fitType}")
367class MemoryTester(lsst.utils.tests.MemoryTestCase):
368 pass
371def setup_module(module):
372 lsst.utils.tests.init()
375if __name__ == "__main__": 375 ↛ 376line 375 didn't jump to line 376, because the condition on line 375 was never true
376 lsst.utils.tests.init()
377 unittest.main()