Coverage for tests/test_linearizeLookupTable.py : 17%

Hot-keys 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
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, LinearizeLookupTable
34from lsst.log import Log
37def refLinearize(image, detector, table):
38 """!Basic implementation of lookup table based non-linearity correction
40 @param[in,out] image image to correct in place (an lsst.afw.image.Image of some type)
41 @param[in] detector detector info (an lsst.afw.cameraGeom.Detector)
42 @param[in] table lookup table: a 2D array of values of the same type as image;
43 - one row for each row index (value of coef[0] in the amp info catalog)
44 - one column for each image value
46 @return the number of pixels whose values were out of range of the lookup table
47 """
48 ampInfoCat = detector.getAmplifiers()
49 numOutOfRange = 0
50 for ampInfo in ampInfoCat:
51 bbox = ampInfo.getBBox()
52 rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2]
53 rowInd = int(rowInd)
54 tableRow = table[rowInd, :]
55 imView = image.Factory(image, bbox)
56 numOutOfRange += applyLookupTable(imView, tableRow, colIndOffset)
57 return numOutOfRange
60class LinearizeLookupTableTestCase(lsst.utils.tests.TestCase):
61 """!Unit tests for LinearizeLookupTable"""
63 def setUp(self):
64 # the following values are all arbitrary, but sane and varied
65 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-31, 22), lsst.geom.Extent2I(100, 85))
66 self.numAmps = (2, 3)
67 self.colIndOffsets = np.array([[0, -50, 2.5], [37, 1, -3]], dtype=float)
68 self.rowInds = np.array([[0, 1, 4], [3, 5, 2]])
69 numCols = self.numAmps[0]*self.numAmps[1]
70 self.assertLess(np.max(self.rowInds), numCols, "error in test conditions; invalid row index")
71 self.detector = self.makeDetector()
73 def tearDown(self):
74 # destroy LSST objects so memory test passes
75 self.bbox = None
76 self.detector = None
78 def testBasics(self):
79 """!Test basic functionality of LinearizeLookupTable
80 """
81 for imageClass in (afwImage.ImageF, afwImage.ImageD):
82 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=250, imageClass=imageClass)
83 table = self.makeTable(inImage)
85 measImage = inImage.Factory(inImage, True)
86 llt = LinearizeLookupTable(table=table, detector=self.detector)
87 linRes = llt(measImage, self.detector)
89 refImage = inImage.Factory(inImage, True)
90 refNumOutOfRange = refLinearize(image=refImage, detector=self.detector, table=table)
92 self.assertEqual(linRes.numAmps, len(self.detector.getAmplifiers()))
93 self.assertEqual(linRes.numAmps, linRes.numLinearized)
94 self.assertEqual(linRes.numOutOfRange, refNumOutOfRange)
95 self.assertImagesAlmostEqual(refImage, measImage)
97 # make sure logging is accepted
98 log = Log.getLogger("ip.isr.LinearizeLookupTable")
99 linRes = llt(image=measImage, detector=self.detector, log=log)
101 def testErrorHandling(self):
102 """!Test error handling in LinearizeLookupTable
103 """
104 image = makeRampImage(bbox=self.bbox, start=-5, stop=250)
105 table = self.makeTable(image)
106 llt = LinearizeLookupTable(table=table, detector=self.detector)
108 # bad name
109 detBadName = self.makeDetector(detName="bad_detector_name")
110 with self.assertRaises(RuntimeError):
111 llt(image, detBadName)
113 # bad serial
114 detBadSerial = self.makeDetector(detSerial="bad_detector_serial")
115 with self.assertRaises(RuntimeError):
116 llt(image, detBadSerial)
118 # bad number of amplifiers
119 badNumAmps = (self.numAmps[0]-1, self.numAmps[1])
120 detBadNumMaps = self.makeDetector(numAmps=badNumAmps)
121 with self.assertRaises(RuntimeError):
122 llt(image, detBadNumMaps)
124 # bad linearity type
125 detBadLinType = self.makeDetector(linearityType="bad_linearity_type")
126 with self.assertRaises(RuntimeError):
127 llt(image, detBadLinType)
129 # wrong dimension
130 badTable = table[..., np.newaxis]
131 with self.assertRaises(RuntimeError):
132 LinearizeLookupTable(table=badTable, detector=self.detector)
134 # wrong size
135 badTable = np.resize(table, (2, 8))
136 with self.assertRaises(RuntimeError):
137 LinearizeLookupTable(table=badTable, detector=self.detector)
139 def testKnown(self):
140 """!Test a few known values
141 """
142 numAmps = (2, 2)
143 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(4, 4))
144 # make a 4x4 image with 4 identical 2x2 subregions that flatten to -1, 0, 1, 2
145 im = afwImage.ImageF(bbox)
146 imArr = im.getArray()
147 imArr[:, :] = np.array(((-1, 0, -1, 0),
148 (1, 2, 1, 2),
149 (-1, 0, -1, 0),
150 (1, 2, 1, 2)), dtype=imArr.dtype)
152 def castAndReshape(arr):
153 arr = np.array(arr, dtype=float)
154 arr.shape = numAmps
155 return arr
157 rowInds = castAndReshape((3, 2, 1, 0)) # avoid the trivial mapping to exercise more of the code
158 colIndOffsets = castAndReshape((0, 0, 1, 1))
159 detector = self.makeDetector(bbox=bbox, numAmps=numAmps, rowInds=rowInds, colIndOffsets=colIndOffsets)
160 ampInfoCat = detector.getAmplifiers()
162 # note: table rows are reversed relative to amplifier order because rowInds is a descending ramp
163 table = np.array(((7, 6, 5, 4), (1, 1, 1, 1), (5, 4, 3, 2), (0, 0, 0, 0)), dtype=imArr.dtype)
165 llt = LinearizeLookupTable(table=table, detector=detector)
167 lltRes = llt(image=im, detector=detector)
168 self.assertEqual(lltRes.numOutOfRange, 2)
170 # amp 0 is a constant correction of 0; one image value is out of range, but it doesn't matter
171 imArr0 = im.Factory(im, ampInfoCat[0].getBBox()).getArray()
172 self.assertFloatsAlmostEqual(imArr0.flatten(), (-1, 0, 1, 2))
174 # amp 1 is a correction of (5, 4, 3, 2), but the first image value is under range
175 imArr1 = im.Factory(im, ampInfoCat[1].getBBox()).getArray()
176 self.assertFloatsAlmostEqual(imArr1.flatten(), (4, 5, 5, 5))
178 # amp 2 is a constant correction of +1; all image values are in range, but it doesn't matter
179 imArr2 = im.Factory(im, ampInfoCat[2].getBBox()).getArray()
180 self.assertFloatsAlmostEqual(imArr2.flatten(), (0, 1, 2, 3))
182 # amp 3 is a correction of (7, 6, 5, 4); all image values in range
183 imArr1 = im.Factory(im, ampInfoCat[3].getBBox()).getArray()
184 self.assertFloatsAlmostEqual(imArr1.flatten(), (6, 6, 6, 6))
186 def testPickle(self):
187 """!Test that a LinearizeLookupTable can be pickled and unpickled
188 """
189 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=2500)
190 table = self.makeTable(inImage)
191 llt = LinearizeLookupTable(table=table, detector=self.detector)
193 refImage = inImage.Factory(inImage, True)
194 refNumOutOfRange = llt(refImage, self.detector)
196 pickledStr = pickle.dumps(llt)
197 restoredLlt = pickle.loads(pickledStr)
199 measImage = inImage.Factory(inImage, True)
200 measNumOutOfRange = restoredLlt(measImage, self.detector)
202 self.assertEqual(refNumOutOfRange, measNumOutOfRange)
203 self.assertImagesAlmostEqual(refImage, measImage)
205 def makeDetector(self, bbox=None, numAmps=None, rowInds=None, colIndOffsets=None,
206 detName="det_a", detSerial="123", linearityType="LookupTable"):
207 """!Make a detector
209 @param[in] bbox bounding box for image
210 @param[n] numAmps x,y number of amplifiers (pair of int)
211 @param[in] rowInds index of lookup table for each amplifier (array of shape numAmps)
212 @param[in] colIndOffsets column index offset for each amplifier (array of shape numAmps)
213 @param[in] detName detector name (a str)
214 @param[in] detSerial detector serial numbe (a str)
215 @param[in] linearityType name of linearity type (a str)
217 @return a detector (an lsst.afw.cameraGeom.Detector)
218 """
219 bbox = bbox if bbox is not None else self.bbox
220 numAmps = numAmps if numAmps is not None else self.numAmps
221 rowInds = rowInds if rowInds is not None else self.rowInds
222 colIndOffsets = colIndOffsets if colIndOffsets is not None else self.colIndOffsets
224 detId = 1
225 orientation = cameraGeom.Orientation()
226 pixelSize = lsst.geom.Extent2D(1, 1)
228 camBuilder = cameraGeom.Camera.Builder("fakeCam")
229 detBuilder = camBuilder.add(detName, detId)
230 detBuilder.setSerial(detSerial)
231 detBuilder.setBBox(bbox)
232 detBuilder.setOrientation(orientation)
233 detBuilder.setPixelSize(pixelSize)
235 boxArr = BoxGrid(box=bbox, numColRow=numAmps)
236 for i in range(numAmps[0]):
237 for j in range(numAmps[1]):
238 ampInfo = cameraGeom.Amplifier.Builder()
239 ampInfo.setName("amp %d_%d" % (i + 1, j + 1))
240 ampInfo.setBBox(boxArr[i, j])
241 ampInfo.setLinearityType(linearityType)
242 # setLinearityCoeffs is picky about getting a mixed int/float list.
243 ampInfo.setLinearityCoeffs(np.array([rowInds[i, j], colIndOffsets[i, j], 0, 0], dtype=float))
244 detBuilder.append(ampInfo)
246 return detBuilder
248 def makeTable(self, image, numCols=None, numRows=2500, sigma=55):
249 """!Make a 2D lookup table
251 @param[in] image image whose type is used for the table
252 @param[in] numCols number of columns for table; defaults to self.numCols
253 @param[in] numRows number of rows for the table
254 @param[in] sigma standard deviation of normal distribution
255 """
256 numCols = numCols or self.numAmps[0]*self.numAmps[1]
257 dtype = image.getArray().dtype
258 table = np.random.normal(scale=sigma, size=(numCols, numRows))
259 return np.array(table, dtype=dtype)
262class MemoryTester(lsst.utils.tests.MemoryTestCase):
263 pass
266def setup_module(module):
267 lsst.utils.tests.init()
270if __name__ == "__main__": 270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true
271 lsst.utils.tests.init()
272 unittest.main()