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, Linearizer 

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 log = Log.getLogger("ip.isr.LinearizeLookupTable") 

86 

87 measImage = inImage.Factory(inImage, True) 

88 llt = Linearizer(table=table, detector=self.detector) 

89 linRes = llt.applyLinearity(measImage, detector=self.detector, log=log) 

90 

91 refImage = inImage.Factory(inImage, True) 

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

93 

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) 

98 

99 # make sure logging is accepted 

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

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

102 

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) 

109 

110 # bad name 

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

112 with self.assertRaises(RuntimeError): 

113 llt.applyLinearity(image, detBadName) 

114 

115 # bad serial 

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

117 with self.assertRaises(RuntimeError): 

118 llt.applyLinearity(image, detBadSerial) 

119 

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) 

125 

126 # bad linearity type 

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

128 with self.assertRaises(RuntimeError): 

129 llt.applyLinearity(image, detBadLinType) 

130 

131 # wrong dimension 

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

133 with self.assertRaises(RuntimeError): 

134 Linearizer(table=badTable, detector=self.detector) 

135 

136 # wrong size 

137 badTable = np.transpose(table) 

138 with self.assertRaises(RuntimeError): 

139 Linearizer(table=badTable, detector=self.detector) 

140 

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) 

153 

154 def castAndReshape(arr): 

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

156 arr.shape = numAmps 

157 return arr 

158 

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

163 

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) 

166 

167 llt = Linearizer(table=table, detector=detector) 

168 

169 lltRes = llt.applyLinearity(image=im, detector=detector) 

170 self.assertEqual(lltRes.numOutOfRange, 2) 

171 

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

175 

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

179 

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

183 

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

187 

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) 

194 

195 refImage = inImage.Factory(inImage, True) 

196 refNumOutOfRange = llt.applyLinearity(refImage, self.detector) 

197 

198 pickledStr = pickle.dumps(llt) 

199 restoredLlt = pickle.loads(pickledStr) 

200 

201 measImage = inImage.Factory(inImage, True) 

202 measNumOutOfRange = restoredLlt.applyLinearity(measImage, self.detector) 

203 

204 self.assertEqual(refNumOutOfRange, measNumOutOfRange) 

205 self.assertImagesAlmostEqual(refImage, measImage) 

206 

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

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

209 """!Make a detector 

210 

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) 

218 

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 

225 

226 detId = 1 

227 orientation = cameraGeom.Orientation() 

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

229 

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) 

236 

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) 

247 

248 return detBuilder 

249 

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

251 """!Make a 2D lookup table 

252 

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) 

262 

263 

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

265 pass 

266 

267 

268def setup_module(module): 

269 lsst.utils.tests.init() 

270 

271 

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