Coverage for tests/test_linearizeLookupTable.py: 15%

147 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-15 02:55 -0700

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 

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 

34 

35 

36def refLinearize(image, detector, table): 

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

38 

39 @param[in,out] image image to correct in place (an lsst.afw.image.Image 

40 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 

43 image; 

44 - one row for each row index (value of coef[0] in the amp info 

45 catalog) 

46 - one column for each image value 

47 

48 @return the number of pixels whose values were out of range of the lookup 

49 table 

50 """ 

51 ampInfoCat = detector.getAmplifiers() 

52 numOutOfRange = 0 

53 for ampInfo in ampInfoCat: 

54 bbox = ampInfo.getBBox() 

55 rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2] 

56 rowInd = int(rowInd) 

57 tableRow = table[rowInd, :] 

58 imView = image.Factory(image, bbox) 

59 numOutOfRange += applyLookupTable(imView, tableRow, colIndOffset) 

60 return numOutOfRange 

61 

62 

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

64 """!Unit tests for LinearizeLookupTable""" 

65 

66 def setUp(self): 

67 # the following values are all arbitrary, but sane and varied 

68 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-31, 22), lsst.geom.Extent2I(100, 85)) 

69 self.numAmps = (2, 3) 

70 self.colIndOffsets = np.array([[0, -50, 2.5], [37, 1, -3]], dtype=float) 

71 self.rowInds = np.array([[0, 1, 4], [3, 5, 2]]) 

72 numCols = self.numAmps[0]*self.numAmps[1] 

73 self.assertLess(np.max(self.rowInds), numCols, "error in test conditions; invalid row index") 

74 self.detector = self.makeDetector() 

75 

76 def tearDown(self): 

77 # destroy LSST objects so memory test passes 

78 self.bbox = None 

79 self.detector = None 

80 

81 def testBasics(self): 

82 """!Test basic functionality of LinearizeLookupTable 

83 """ 

84 for imageClass in (afwImage.ImageF, afwImage.ImageD): 

85 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=250, imageClass=imageClass) 

86 table = self.makeTable(inImage) 

87 

88 log = logging.getLogger("lsst.ip.isr.LinearizeLookupTable") 

89 

90 measImage = inImage.Factory(inImage, True) 

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

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

93 

94 refImage = inImage.Factory(inImage, True) 

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

96 

97 self.assertEqual(linRes.numAmps, len(self.detector.getAmplifiers())) 

98 self.assertEqual(linRes.numAmps, linRes.numLinearized) 

99 self.assertEqual(linRes.numOutOfRange, refNumOutOfRange) 

100 self.assertImagesAlmostEqual(refImage, measImage) 

101 

102 # make sure logging is accepted 

103 log = logging.getLogger("lsst.ip.isr.LinearizeLookupTable") 

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

105 

106 def testErrorHandling(self): 

107 """!Test error handling in LinearizeLookupTable 

108 """ 

109 image = makeRampImage(bbox=self.bbox, start=-5, stop=250) 

110 table = self.makeTable(image) 

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

112 

113 # bad name 

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

115 with self.assertRaises(RuntimeError): 

116 llt.applyLinearity(image, detBadName) 

117 

118 # bad serial 

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

120 with self.assertRaises(RuntimeError): 

121 llt.applyLinearity(image, detBadSerial) 

122 

123 # bad number of amplifiers 

124 badNumAmps = (self.numAmps[0]-1, self.numAmps[1]) 

125 detBadNumMaps = self.makeDetector(numAmps=badNumAmps) 

126 with self.assertRaises(RuntimeError): 

127 llt.applyLinearity(image, detBadNumMaps) 

128 

129 # bad linearity type 

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

131 with self.assertRaises(RuntimeError): 

132 llt.applyLinearity(image, detBadLinType) 

133 

134 # wrong dimension 

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

136 with self.assertRaises(RuntimeError): 

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

138 

139 # wrong size 

140 badTable = np.transpose(table) 

141 with self.assertRaises(RuntimeError): 

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

143 

144 def testKnown(self): 

145 """!Test a few known values 

146 """ 

147 numAmps = (2, 2) 

148 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(4, 4)) 

149 # make a 4x4 image with 4 identical 2x2 subregions that flatten 

150 # to -1, 0, 1, 2 

151 im = afwImage.ImageF(bbox) 

152 imArr = im.getArray() 

153 imArr[:, :] = np.array(((-1, 0, -1, 0), 

154 (1, 2, 1, 2), 

155 (-1, 0, -1, 0), 

156 (1, 2, 1, 2)), dtype=imArr.dtype) 

157 

158 def castAndReshape(arr): 

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

160 arr.shape = numAmps 

161 return arr 

162 

163 rowInds = castAndReshape((3, 2, 1, 0)) # avoid the trivial mapping to exercise more of the code 

164 colIndOffsets = castAndReshape((0, 0, 1, 1)) 

165 detector = self.makeDetector(bbox=bbox, numAmps=numAmps, rowInds=rowInds, colIndOffsets=colIndOffsets) 

166 ampInfoCat = detector.getAmplifiers() 

167 

168 # note: table rows are reversed relative to amplifier order because 

169 # rowInds is a descending ramp 

170 table = np.array(((7, 6, 5, 4), (1, 1, 1, 1), (5, 4, 3, 2), (0, 0, 0, 0)), dtype=imArr.dtype) 

171 

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

173 

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

175 self.assertEqual(lltRes.numOutOfRange, 2) 

176 

177 # amp 0 is a constant correction of 0; one image value is out of range, 

178 # but it doesn't matter 

179 imArr0 = im.Factory(im, ampInfoCat[0].getBBox()).getArray() 

180 self.assertFloatsAlmostEqual(imArr0.flatten(), (-1, 0, 1, 2)) 

181 

182 # amp 1 is a correction of (5, 4, 3, 2), but the first image value is 

183 # under range 

184 imArr1 = im.Factory(im, ampInfoCat[1].getBBox()).getArray() 

185 self.assertFloatsAlmostEqual(imArr1.flatten(), (4, 5, 5, 5)) 

186 

187 # amp 2 is a constant correction of +1; all image values are in range, 

188 # but it doesn't matter 

189 imArr2 = im.Factory(im, ampInfoCat[2].getBBox()).getArray() 

190 self.assertFloatsAlmostEqual(imArr2.flatten(), (0, 1, 2, 3)) 

191 

192 # amp 3 is a correction of (7, 6, 5, 4); all image values in range 

193 imArr1 = im.Factory(im, ampInfoCat[3].getBBox()).getArray() 

194 self.assertFloatsAlmostEqual(imArr1.flatten(), (6, 6, 6, 6)) 

195 

196 def testPickle(self): 

197 """!Test that a LinearizeLookupTable can be pickled and unpickled 

198 """ 

199 inImage = makeRampImage(bbox=self.bbox, start=-5, stop=2500) 

200 table = self.makeTable(inImage) 

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

202 

203 refImage = inImage.Factory(inImage, True) 

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

205 

206 pickledStr = pickle.dumps(llt) 

207 restoredLlt = pickle.loads(pickledStr) 

208 

209 measImage = inImage.Factory(inImage, True) 

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

211 

212 self.assertEqual(refNumOutOfRange, measNumOutOfRange) 

213 self.assertImagesAlmostEqual(refImage, measImage) 

214 

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

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

217 """!Make a detector 

218 

219 @param[in] bbox bounding box for image 

220 @param[n] numAmps x,y number of amplifiers (pair of int) 

221 @param[in] rowInds index of lookup table for each amplifier (array of 

222 shape numAmps) 

223 @param[in] colIndOffsets column index offset for each amplifier 

224 (array of shape numAmps) 

225 @param[in] detName detector name (a str) 

226 @param[in] detSerial detector serial numbe (a str) 

227 @param[in] linearityType name of linearity type (a str) 

228 

229 @return a detector (an lsst.afw.cameraGeom.Detector) 

230 """ 

231 bbox = bbox if bbox is not None else self.bbox 

232 numAmps = numAmps if numAmps is not None else self.numAmps 

233 rowInds = rowInds if rowInds is not None else self.rowInds 

234 colIndOffsets = colIndOffsets if colIndOffsets is not None else self.colIndOffsets 

235 

236 detId = 1 

237 orientation = cameraGeom.Orientation() 

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

239 

240 camBuilder = cameraGeom.Camera.Builder("fakeCam") 

241 detBuilder = camBuilder.add(detName, detId) 

242 detBuilder.setSerial(detSerial) 

243 detBuilder.setBBox(bbox) 

244 detBuilder.setOrientation(orientation) 

245 detBuilder.setPixelSize(pixelSize) 

246 

247 boxArr = BoxGrid(box=bbox, numColRow=numAmps) 

248 for i in range(numAmps[0]): 

249 for j in range(numAmps[1]): 

250 ampInfo = cameraGeom.Amplifier.Builder() 

251 ampInfo.setName("amp %d_%d" % (i + 1, j + 1)) 

252 ampInfo.setBBox(boxArr[i, j]) 

253 ampInfo.setLinearityType(linearityType) 

254 # setLinearityCoeffs is picky about getting a mixed int/float 

255 # list. 

256 ampInfo.setLinearityCoeffs(np.array([rowInds[i, j], colIndOffsets[i, j], 0, 0], dtype=float)) 

257 detBuilder.append(ampInfo) 

258 

259 return detBuilder 

260 

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

262 """!Make a 2D lookup table 

263 

264 @param[in] image image whose type is used for the table 

265 @param[in] numCols number of columns for table; defaults to 

266 self.numCols 

267 @param[in] numRows number of rows for the table 

268 @param[in] sigma standard deviation of normal distribution 

269 """ 

270 numCols = numCols or self.numAmps[0]*self.numAmps[1] 

271 dtype = image.getArray().dtype 

272 table = np.random.normal(scale=sigma, size=(numCols, numRows)) 

273 return np.array(table, dtype=dtype) 

274 

275 

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

277 pass 

278 

279 

280def setup_module(module): 

281 lsst.utils.tests.init() 

282 

283 

284if __name__ == "__main__": 284 ↛ 285line 284 didn't jump to line 285, because the condition on line 284 was never true

285 lsst.utils.tests.init() 

286 unittest.main()