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, Linearizer
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 log = Log.getLogger("ip.isr.LinearizeLookupTable")
87 measImage = inImage.Factory(inImage, True)
88 llt = Linearizer(table=table, detector=self.detector)
89 linRes = llt.applyLinearity(measImage, detector=self.detector, log=log)
91 refImage = inImage.Factory(inImage, True)
92 refNumOutOfRange = refLinearize(image=refImage, detector=self.detector, table=table)
94 self.assertEqual(linRes.numAmps, len(self.detector.getAmplifiers()))
95 self.assertEqual(linRes.numAmps, linRes.numLinearized)
96 self.assertEqual(linRes.numOutOfRange, refNumOutOfRange)
97 self.assertImagesAlmostEqual(refImage, measImage)
99 # make sure logging is accepted
100 log = Log.getLogger("ip.isr.LinearizeLookupTable")
101 linRes = llt.applyLinearity(image=measImage, detector=self.detector, log=log)
103 def testErrorHandling(self):
104 """!Test error handling in LinearizeLookupTable
105 """
106 image = makeRampImage(bbox=self.bbox, start=-5, stop=250)
107 table = self.makeTable(image)
108 llt = Linearizer(table=table, detector=self.detector)
110 # bad name
111 detBadName = self.makeDetector(detName="bad_detector_name")
112 with self.assertRaises(RuntimeError):
113 llt.applyLinearity(image, detBadName)
115 # bad serial
116 detBadSerial = self.makeDetector(detSerial="bad_detector_serial")
117 with self.assertRaises(RuntimeError):
118 llt.applyLinearity(image, detBadSerial)
120 # bad number of amplifiers
121 badNumAmps = (self.numAmps[0]-1, self.numAmps[1])
122 detBadNumMaps = self.makeDetector(numAmps=badNumAmps)
123 with self.assertRaises(RuntimeError):
124 llt.applyLinearity(image, detBadNumMaps)
126 # bad linearity type
127 detBadLinType = self.makeDetector(linearityType="bad_linearity_type")
128 with self.assertRaises(RuntimeError):
129 llt.applyLinearity(image, detBadLinType)
131 # wrong dimension
132 badTable = table[..., np.newaxis]
133 with self.assertRaises(RuntimeError):
134 Linearizer(table=badTable, detector=self.detector)
136 # wrong size
137 badTable = np.transpose(table)
138 with self.assertRaises(RuntimeError):
139 Linearizer(table=badTable, detector=self.detector)
141 def testKnown(self):
142 """!Test a few known values
143 """
144 numAmps = (2, 2)
145 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(4, 4))
146 # make a 4x4 image with 4 identical 2x2 subregions that flatten to -1, 0, 1, 2
147 im = afwImage.ImageF(bbox)
148 imArr = im.getArray()
149 imArr[:, :] = np.array(((-1, 0, -1, 0),
150 (1, 2, 1, 2),
151 (-1, 0, -1, 0),
152 (1, 2, 1, 2)), dtype=imArr.dtype)
154 def castAndReshape(arr):
155 arr = np.array(arr, dtype=float)
156 arr.shape = numAmps
157 return arr
159 rowInds = castAndReshape((3, 2, 1, 0)) # avoid the trivial mapping to exercise more of the code
160 colIndOffsets = castAndReshape((0, 0, 1, 1))
161 detector = self.makeDetector(bbox=bbox, numAmps=numAmps, rowInds=rowInds, colIndOffsets=colIndOffsets)
162 ampInfoCat = detector.getAmplifiers()
164 # note: table rows are reversed relative to amplifier order because rowInds is a descending ramp
165 table = np.array(((7, 6, 5, 4), (1, 1, 1, 1), (5, 4, 3, 2), (0, 0, 0, 0)), dtype=imArr.dtype)
167 llt = Linearizer(table=table, detector=detector)
169 lltRes = llt.applyLinearity(image=im, detector=detector)
170 self.assertEqual(lltRes.numOutOfRange, 2)
172 # amp 0 is a constant correction of 0; one image value is out of range, but it doesn't matter
173 imArr0 = im.Factory(im, ampInfoCat[0].getBBox()).getArray()
174 self.assertFloatsAlmostEqual(imArr0.flatten(), (-1, 0, 1, 2))
176 # amp 1 is a correction of (5, 4, 3, 2), but the first image value is under range
177 imArr1 = im.Factory(im, ampInfoCat[1].getBBox()).getArray()
178 self.assertFloatsAlmostEqual(imArr1.flatten(), (4, 5, 5, 5))
180 # amp 2 is a constant correction of +1; all image values are in range, but it doesn't matter
181 imArr2 = im.Factory(im, ampInfoCat[2].getBBox()).getArray()
182 self.assertFloatsAlmostEqual(imArr2.flatten(), (0, 1, 2, 3))
184 # amp 3 is a correction of (7, 6, 5, 4); all image values in range
185 imArr1 = im.Factory(im, ampInfoCat[3].getBBox()).getArray()
186 self.assertFloatsAlmostEqual(imArr1.flatten(), (6, 6, 6, 6))
188 def testPickle(self):
189 """!Test that a LinearizeLookupTable can be pickled and unpickled
190 """
191 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=2500)
192 table = self.makeTable(inImage)
193 llt = Linearizer(table=table, detector=self.detector)
195 refImage = inImage.Factory(inImage, True)
196 refNumOutOfRange = llt.applyLinearity(refImage, self.detector)
198 pickledStr = pickle.dumps(llt)
199 restoredLlt = pickle.loads(pickledStr)
201 measImage = inImage.Factory(inImage, True)
202 measNumOutOfRange = restoredLlt.applyLinearity(measImage, self.detector)
204 self.assertEqual(refNumOutOfRange, measNumOutOfRange)
205 self.assertImagesAlmostEqual(refImage, measImage)
207 def makeDetector(self, bbox=None, numAmps=None, rowInds=None, colIndOffsets=None,
208 detName="det_a", detSerial="123", linearityType="LookupTable"):
209 """!Make a detector
211 @param[in] bbox bounding box for image
212 @param[n] numAmps x,y number of amplifiers (pair of int)
213 @param[in] rowInds index of lookup table for each amplifier (array of shape numAmps)
214 @param[in] colIndOffsets column index offset for each amplifier (array of shape numAmps)
215 @param[in] detName detector name (a str)
216 @param[in] detSerial detector serial numbe (a str)
217 @param[in] linearityType name of linearity type (a str)
219 @return a detector (an lsst.afw.cameraGeom.Detector)
220 """
221 bbox = bbox if bbox is not None else self.bbox
222 numAmps = numAmps if numAmps is not None else self.numAmps
223 rowInds = rowInds if rowInds is not None else self.rowInds
224 colIndOffsets = colIndOffsets if colIndOffsets is not None else self.colIndOffsets
226 detId = 1
227 orientation = cameraGeom.Orientation()
228 pixelSize = lsst.geom.Extent2D(1, 1)
230 camBuilder = cameraGeom.Camera.Builder("fakeCam")
231 detBuilder = camBuilder.add(detName, detId)
232 detBuilder.setSerial(detSerial)
233 detBuilder.setBBox(bbox)
234 detBuilder.setOrientation(orientation)
235 detBuilder.setPixelSize(pixelSize)
237 boxArr = BoxGrid(box=bbox, numColRow=numAmps)
238 for i in range(numAmps[0]):
239 for j in range(numAmps[1]):
240 ampInfo = cameraGeom.Amplifier.Builder()
241 ampInfo.setName("amp %d_%d" % (i + 1, j + 1))
242 ampInfo.setBBox(boxArr[i, j])
243 ampInfo.setLinearityType(linearityType)
244 # setLinearityCoeffs is picky about getting a mixed int/float list.
245 ampInfo.setLinearityCoeffs(np.array([rowInds[i, j], colIndOffsets[i, j], 0, 0], dtype=float))
246 detBuilder.append(ampInfo)
248 return detBuilder
250 def makeTable(self, image, numCols=None, numRows=2500, sigma=55):
251 """!Make a 2D lookup table
253 @param[in] image image whose type is used for the table
254 @param[in] numCols number of columns for table; defaults to self.numCols
255 @param[in] numRows number of rows for the table
256 @param[in] sigma standard deviation of normal distribution
257 """
258 numCols = numCols or self.numAmps[0]*self.numAmps[1]
259 dtype = image.getArray().dtype
260 table = np.random.normal(scale=sigma, size=(numCols, numRows))
261 return np.array(table, dtype=dtype)
264class MemoryTester(lsst.utils.tests.MemoryTestCase):
265 pass
268def setup_module(module):
269 lsst.utils.tests.init()
272if __name__ == "__main__": 272 ↛ 273line 272 didn't jump to line 273, because the condition on line 272 was never true
273 lsst.utils.tests.init()
274 unittest.main()