Coverage for tests/test_linearizeLookupTable.py: 17%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# LSST Data Management System
3# Copyright 2017 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#
22import unittest
23import pickle
24import logging
25import numpy as np
27import lsst.utils.tests
28import lsst.utils
29import lsst.afw.image as afwImage
30import lsst.afw.cameraGeom as cameraGeom
31from lsst.afw.geom.testUtils import BoxGrid
32from lsst.afw.image.testUtils import makeRampImage
33from lsst.ip.isr import applyLookupTable, Linearizer
36def refLinearize(image, detector, table):
37 """!Basic implementation of lookup table based non-linearity correction
39 @param[in,out] image image to correct in place (an lsst.afw.image.Image of some type)
40 @param[in] detector detector info (an lsst.afw.cameraGeom.Detector)
41 @param[in] table lookup table: a 2D array of values of the same type as image;
42 - one row for each row index (value of coef[0] in the amp info catalog)
43 - one column for each image value
45 @return the number of pixels whose values were out of range of the lookup table
46 """
47 ampInfoCat = detector.getAmplifiers()
48 numOutOfRange = 0
49 for ampInfo in ampInfoCat:
50 bbox = ampInfo.getBBox()
51 rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2]
52 rowInd = int(rowInd)
53 tableRow = table[rowInd, :]
54 imView = image.Factory(image, bbox)
55 numOutOfRange += applyLookupTable(imView, tableRow, colIndOffset)
56 return numOutOfRange
59class LinearizeLookupTableTestCase(lsst.utils.tests.TestCase):
60 """!Unit tests for LinearizeLookupTable"""
62 def setUp(self):
63 # the following values are all arbitrary, but sane and varied
64 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-31, 22), lsst.geom.Extent2I(100, 85))
65 self.numAmps = (2, 3)
66 self.colIndOffsets = np.array([[0, -50, 2.5], [37, 1, -3]], dtype=float)
67 self.rowInds = np.array([[0, 1, 4], [3, 5, 2]])
68 numCols = self.numAmps[0]*self.numAmps[1]
69 self.assertLess(np.max(self.rowInds), numCols, "error in test conditions; invalid row index")
70 self.detector = self.makeDetector()
72 def tearDown(self):
73 # destroy LSST objects so memory test passes
74 self.bbox = None
75 self.detector = None
77 def testBasics(self):
78 """!Test basic functionality of LinearizeLookupTable
79 """
80 for imageClass in (afwImage.ImageF, afwImage.ImageD):
81 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=250, imageClass=imageClass)
82 table = self.makeTable(inImage)
84 log = logging.getLogger("lsst.ip.isr.LinearizeLookupTable")
86 measImage = inImage.Factory(inImage, True)
87 llt = Linearizer(table=table, detector=self.detector)
88 linRes = llt.applyLinearity(measImage, detector=self.detector, log=log)
90 refImage = inImage.Factory(inImage, True)
91 refNumOutOfRange = refLinearize(image=refImage, detector=self.detector, table=table)
93 self.assertEqual(linRes.numAmps, len(self.detector.getAmplifiers()))
94 self.assertEqual(linRes.numAmps, linRes.numLinearized)
95 self.assertEqual(linRes.numOutOfRange, refNumOutOfRange)
96 self.assertImagesAlmostEqual(refImage, measImage)
98 # make sure logging is accepted
99 log = logging.getLogger("lsst.ip.isr.LinearizeLookupTable")
100 linRes = llt.applyLinearity(image=measImage, detector=self.detector, log=log)
102 def testErrorHandling(self):
103 """!Test error handling in LinearizeLookupTable
104 """
105 image = makeRampImage(bbox=self.bbox, start=-5, stop=250)
106 table = self.makeTable(image)
107 llt = Linearizer(table=table, detector=self.detector)
109 # bad name
110 detBadName = self.makeDetector(detName="bad_detector_name")
111 with self.assertRaises(RuntimeError):
112 llt.applyLinearity(image, detBadName)
114 # bad serial
115 detBadSerial = self.makeDetector(detSerial="bad_detector_serial")
116 with self.assertRaises(RuntimeError):
117 llt.applyLinearity(image, detBadSerial)
119 # bad number of amplifiers
120 badNumAmps = (self.numAmps[0]-1, self.numAmps[1])
121 detBadNumMaps = self.makeDetector(numAmps=badNumAmps)
122 with self.assertRaises(RuntimeError):
123 llt.applyLinearity(image, detBadNumMaps)
125 # bad linearity type
126 detBadLinType = self.makeDetector(linearityType="bad_linearity_type")
127 with self.assertRaises(RuntimeError):
128 llt.applyLinearity(image, detBadLinType)
130 # wrong dimension
131 badTable = table[..., np.newaxis]
132 with self.assertRaises(RuntimeError):
133 Linearizer(table=badTable, detector=self.detector)
135 # wrong size
136 badTable = np.transpose(table)
137 with self.assertRaises(RuntimeError):
138 Linearizer(table=badTable, detector=self.detector)
140 def testKnown(self):
141 """!Test a few known values
142 """
143 numAmps = (2, 2)
144 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(4, 4))
145 # make a 4x4 image with 4 identical 2x2 subregions that flatten to -1, 0, 1, 2
146 im = afwImage.ImageF(bbox)
147 imArr = im.getArray()
148 imArr[:, :] = np.array(((-1, 0, -1, 0),
149 (1, 2, 1, 2),
150 (-1, 0, -1, 0),
151 (1, 2, 1, 2)), dtype=imArr.dtype)
153 def castAndReshape(arr):
154 arr = np.array(arr, dtype=float)
155 arr.shape = numAmps
156 return arr
158 rowInds = castAndReshape((3, 2, 1, 0)) # avoid the trivial mapping to exercise more of the code
159 colIndOffsets = castAndReshape((0, 0, 1, 1))
160 detector = self.makeDetector(bbox=bbox, numAmps=numAmps, rowInds=rowInds, colIndOffsets=colIndOffsets)
161 ampInfoCat = detector.getAmplifiers()
163 # note: table rows are reversed relative to amplifier order because rowInds is a descending ramp
164 table = np.array(((7, 6, 5, 4), (1, 1, 1, 1), (5, 4, 3, 2), (0, 0, 0, 0)), dtype=imArr.dtype)
166 llt = Linearizer(table=table, detector=detector)
168 lltRes = llt.applyLinearity(image=im, detector=detector)
169 self.assertEqual(lltRes.numOutOfRange, 2)
171 # amp 0 is a constant correction of 0; one image value is out of range, but it doesn't matter
172 imArr0 = im.Factory(im, ampInfoCat[0].getBBox()).getArray()
173 self.assertFloatsAlmostEqual(imArr0.flatten(), (-1, 0, 1, 2))
175 # amp 1 is a correction of (5, 4, 3, 2), but the first image value is under range
176 imArr1 = im.Factory(im, ampInfoCat[1].getBBox()).getArray()
177 self.assertFloatsAlmostEqual(imArr1.flatten(), (4, 5, 5, 5))
179 # amp 2 is a constant correction of +1; all image values are in range, but it doesn't matter
180 imArr2 = im.Factory(im, ampInfoCat[2].getBBox()).getArray()
181 self.assertFloatsAlmostEqual(imArr2.flatten(), (0, 1, 2, 3))
183 # amp 3 is a correction of (7, 6, 5, 4); all image values in range
184 imArr1 = im.Factory(im, ampInfoCat[3].getBBox()).getArray()
185 self.assertFloatsAlmostEqual(imArr1.flatten(), (6, 6, 6, 6))
187 def testPickle(self):
188 """!Test that a LinearizeLookupTable can be pickled and unpickled
189 """
190 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=2500)
191 table = self.makeTable(inImage)
192 llt = Linearizer(table=table, detector=self.detector)
194 refImage = inImage.Factory(inImage, True)
195 refNumOutOfRange = llt.applyLinearity(refImage, self.detector)
197 pickledStr = pickle.dumps(llt)
198 restoredLlt = pickle.loads(pickledStr)
200 measImage = inImage.Factory(inImage, True)
201 measNumOutOfRange = restoredLlt.applyLinearity(measImage, self.detector)
203 self.assertEqual(refNumOutOfRange, measNumOutOfRange)
204 self.assertImagesAlmostEqual(refImage, measImage)
206 def makeDetector(self, bbox=None, numAmps=None, rowInds=None, colIndOffsets=None,
207 detName="det_a", detSerial="123", linearityType="LookupTable"):
208 """!Make a detector
210 @param[in] bbox bounding box for image
211 @param[n] numAmps x,y number of amplifiers (pair of int)
212 @param[in] rowInds index of lookup table for each amplifier (array of shape numAmps)
213 @param[in] colIndOffsets column index offset for each amplifier (array of shape numAmps)
214 @param[in] detName detector name (a str)
215 @param[in] detSerial detector serial numbe (a str)
216 @param[in] linearityType name of linearity type (a str)
218 @return a detector (an lsst.afw.cameraGeom.Detector)
219 """
220 bbox = bbox if bbox is not None else self.bbox
221 numAmps = numAmps if numAmps is not None else self.numAmps
222 rowInds = rowInds if rowInds is not None else self.rowInds
223 colIndOffsets = colIndOffsets if colIndOffsets is not None else self.colIndOffsets
225 detId = 1
226 orientation = cameraGeom.Orientation()
227 pixelSize = lsst.geom.Extent2D(1, 1)
229 camBuilder = cameraGeom.Camera.Builder("fakeCam")
230 detBuilder = camBuilder.add(detName, detId)
231 detBuilder.setSerial(detSerial)
232 detBuilder.setBBox(bbox)
233 detBuilder.setOrientation(orientation)
234 detBuilder.setPixelSize(pixelSize)
236 boxArr = BoxGrid(box=bbox, numColRow=numAmps)
237 for i in range(numAmps[0]):
238 for j in range(numAmps[1]):
239 ampInfo = cameraGeom.Amplifier.Builder()
240 ampInfo.setName("amp %d_%d" % (i + 1, j + 1))
241 ampInfo.setBBox(boxArr[i, j])
242 ampInfo.setLinearityType(linearityType)
243 # setLinearityCoeffs is picky about getting a mixed int/float list.
244 ampInfo.setLinearityCoeffs(np.array([rowInds[i, j], colIndOffsets[i, j], 0, 0], dtype=float))
245 detBuilder.append(ampInfo)
247 return detBuilder
249 def makeTable(self, image, numCols=None, numRows=2500, sigma=55):
250 """!Make a 2D lookup table
252 @param[in] image image whose type is used for the table
253 @param[in] numCols number of columns for table; defaults to self.numCols
254 @param[in] numRows number of rows for the table
255 @param[in] sigma standard deviation of normal distribution
256 """
257 numCols = numCols or self.numAmps[0]*self.numAmps[1]
258 dtype = image.getArray().dtype
259 table = np.random.normal(scale=sigma, size=(numCols, numRows))
260 return np.array(table, dtype=dtype)
263class MemoryTester(lsst.utils.tests.MemoryTestCase):
264 pass
267def setup_module(module):
268 lsst.utils.tests.init()
271if __name__ == "__main__": 271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true
272 lsst.utils.tests.init()
273 unittest.main()