Hide keyboard shortcuts

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 

24 

25import numpy as np 

26 

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 

35 

36 

37def refLinearize(image, detector, table): 

38 """!Basic implementation of lookup table based non-linearity correction 

39 

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 

45 

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 

58 

59 

60class LinearizeLookupTableTestCase(lsst.utils.tests.TestCase): 

61 """!Unit tests for LinearizeLookupTable""" 

62 

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() 

72 

73 def tearDown(self): 

74 # destroy LSST objects so memory test passes 

75 self.bbox = None 

76 self.detector = None 

77 

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) 

84 

85 measImage = inImage.Factory(inImage, True) 

86 llt = LinearizeLookupTable(table=table, detector=self.detector) 

87 linRes = llt(measImage, self.detector) 

88 

89 refImage = inImage.Factory(inImage, True) 

90 refNumOutOfRange = refLinearize(image=refImage, detector=self.detector, table=table) 

91 

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) 

96 

97 # make sure logging is accepted 

98 log = Log.getLogger("ip.isr.LinearizeLookupTable") 

99 linRes = llt(image=measImage, detector=self.detector, log=log) 

100 

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) 

107 

108 # bad name 

109 detBadName = self.makeDetector(detName="bad_detector_name") 

110 with self.assertRaises(RuntimeError): 

111 llt(image, detBadName) 

112 

113 # bad serial 

114 detBadSerial = self.makeDetector(detSerial="bad_detector_serial") 

115 with self.assertRaises(RuntimeError): 

116 llt(image, detBadSerial) 

117 

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) 

123 

124 # bad linearity type 

125 detBadLinType = self.makeDetector(linearityType="bad_linearity_type") 

126 with self.assertRaises(RuntimeError): 

127 llt(image, detBadLinType) 

128 

129 # wrong dimension 

130 badTable = table[..., np.newaxis] 

131 with self.assertRaises(RuntimeError): 

132 LinearizeLookupTable(table=badTable, detector=self.detector) 

133 

134 # wrong size 

135 badTable = np.resize(table, (2, 8)) 

136 with self.assertRaises(RuntimeError): 

137 LinearizeLookupTable(table=badTable, detector=self.detector) 

138 

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) 

151 

152 def castAndReshape(arr): 

153 arr = np.array(arr, dtype=float) 

154 arr.shape = numAmps 

155 return arr 

156 

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() 

161 

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) 

164 

165 llt = LinearizeLookupTable(table=table, detector=detector) 

166 

167 lltRes = llt(image=im, detector=detector) 

168 self.assertEqual(lltRes.numOutOfRange, 2) 

169 

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)) 

173 

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)) 

177 

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)) 

181 

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)) 

185 

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) 

192 

193 refImage = inImage.Factory(inImage, True) 

194 refNumOutOfRange = llt(refImage, self.detector) 

195 

196 pickledStr = pickle.dumps(llt) 

197 restoredLlt = pickle.loads(pickledStr) 

198 

199 measImage = inImage.Factory(inImage, True) 

200 measNumOutOfRange = restoredLlt(measImage, self.detector) 

201 

202 self.assertEqual(refNumOutOfRange, measNumOutOfRange) 

203 self.assertImagesAlmostEqual(refImage, measImage) 

204 

205 def makeDetector(self, bbox=None, numAmps=None, rowInds=None, colIndOffsets=None, 

206 detName="det_a", detSerial="123", linearityType="LookupTable"): 

207 """!Make a detector 

208 

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) 

216 

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 

223 

224 detId = 1 

225 orientation = cameraGeom.Orientation() 

226 pixelSize = lsst.geom.Extent2D(1, 1) 

227 

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) 

234 

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) 

245 

246 return detBuilder 

247 

248 def makeTable(self, image, numCols=None, numRows=2500, sigma=55): 

249 """!Make a 2D lookup table 

250 

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) 

260 

261 

262class MemoryTester(lsst.utils.tests.MemoryTestCase): 

263 pass 

264 

265 

266def setup_module(module): 

267 lsst.utils.tests.init() 

268 

269 

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()