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 2016 AURA/LSST. 

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 abc 

23 

24import numpy as np 

25 

26from lsst.pipe.base import Struct 

27from .applyLookupTable import applyLookupTable 

28 

29__all__ = ["LinearizeBase", "LinearizeLookupTable", "LinearizeSquared"] 

30 

31 

32def getLinearityTypeByName(linearityTypeName): 

33 """Determine the linearity class to use if the type is known. 

34 

35 Parameters 

36 ---------- 

37 linearityTypeName : str 

38 String name of the linearity type that is needed. 

39 

40 Returns 

41 ------- 

42 linearityType : `~lsst.ip.isr.linearize.LinearizeSquared` 

43 The appropriate linearity class to use. If no matching class 

44 is found, `None` is returned. 

45 """ 

46 for t in [LinearizeLookupTable, LinearizeSquared]: 

47 if t.LinearityType == linearityTypeName: 

48 return t 

49 return None 

50 

51 

52class LinearizeBase(metaclass=abc.ABCMeta): 

53 """Abstract base class functor for correcting non-linearity 

54 

55 Subclasses must define __call__ and set class variable LinearityType to a string 

56 that will be used for linearity type in AmpInfoCatalog 

57 """ 

58 LinearityType = None # linearity type, a string used for AmpInfoCatalogs 

59 

60 @abc.abstractmethod 

61 def __call__(self, image, detector, log=None): 

62 """Correct non-linearity 

63 

64 @param[in] image image to be corrected (an lsst.afw.image.Image) 

65 @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector) 

66 @param[in] log logger (an lsst.log.Log), or None to disable logging; 

67 a warning is logged if amplifiers are skipped or other worrisome events occur 

68 

69 @return an lsst.pipe.base.Struct containing at least the following fields: 

70 - numAmps number of amplifiers found 

71 - numLinearized number of amplifiers linearized 

72 

73 @throw RuntimeError if the linearity type is wrong 

74 @throw a subclass of Exception if linearization fails for any other reason 

75 """ 

76 pass 

77 

78 def checkLinearityType(self, detector): 

79 """Verify that the linearity type is correct for this detector 

80 

81 @warning only checks the first record of the amp info catalog 

82 

83 @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector) 

84 

85 @throw RuntimeError if anything doesn't match 

86 """ 

87 ampInfoType = detector.getAmplifiers()[0].getLinearityType() 

88 if self.LinearityType != ampInfoType: 

89 raise RuntimeError("Linearity types don't match: %s != %s" % (self.LinearityType, ampInfoType)) 

90 

91 

92class LinearizeLookupTable(LinearizeBase): 

93 """Correct non-linearity with a persisted lookup table 

94 

95 for each i,j of image: 

96 rowInd = int(c0) 

97 colInd = int(c1 + uncorrImage[i,j]) 

98 corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd] 

99 

100 where c0, c1 are collimation coefficients from the AmpInfoTable of the detector: 

101 - c0: row index; used to identify which row of the table to use (typically one per amplifier, 

102 though one can have multiple amplifiers use the same table) 

103 - c1: column index offset; added to the uncorrected image value before truncation; 

104 this supports tables that can handle negative image values; also, if the c1 ends with .5 

105 then the nearest index is used instead of truncating to the next smaller index 

106 

107 In order to keep related data together, the coefficients are persisted along with the table. 

108 """ 

109 LinearityType = "LookupTable" 

110 

111 def __init__(self, table, detector): 

112 """Construct a LinearizeLookupTable 

113 

114 @param[in] table lookup table; a 2-dimensional array of floats: 

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

116 - one column for each image value 

117 To avoid copying the table the last index should vary fastest (numpy default "C" order) 

118 @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector); 

119 the name, serial, and amplifier linearization type and coefficients are saved 

120 

121 @throw RuntimeError if table is not 2-dimensional, 

122 table has fewer columns than rows (indicating that the indices are swapped), 

123 or if any row index (linearity coefficient 0) is out of range 

124 """ 

125 LinearizeBase.__init__(self) 

126 

127 self._table = np.array(table, order="C") 

128 if len(table.shape) != 2: 

129 raise RuntimeError("table shape = %s; must have two dimensions" % (table.shape,)) 

130 if table.shape[1] < table.shape[0]: 

131 raise RuntimeError("table shape = %s; indices are switched" % (table.shape,)) 

132 

133 self._detectorName = detector.getName() 

134 self._detectorSerial = detector.getSerial() 

135 self.checkLinearityType(detector) 

136 ampInfoCat = detector.getAmplifiers() 

137 rowIndList = [] 

138 colIndOffsetList = [] 

139 numTableRows = table.shape[0] 

140 for ampInfo in ampInfoCat: 

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

142 rowInd = int(rowInd) 

143 if rowInd < 0 or rowInd >= numTableRows: 

144 raise RuntimeError("Amplifier %s has rowInd=%s not in range[0, %s)" % 

145 (ampInfo.getName(), rowInd, numTableRows)) 

146 rowIndList.append(int(rowInd)) 

147 colIndOffsetList.append(colIndOffset) 

148 self._rowIndArr = np.array(rowIndList, dtype=int) 

149 self._colIndOffsetArr = np.array(colIndOffsetList) 

150 

151 def __call__(self, image, detector, log=None): 

152 """Correct for non-linearity 

153 

154 @param[in] image image to be corrected (an lsst.afw.image.Image) 

155 @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector); 

156 the name, serial and number of amplifiers must match persisted data; 

157 the bbox from each amplifier is read; 

158 the linearization coefficients are ignored in favor of the persisted values 

159 @param[in] log logger (an lsst.log.Log), or None to disable logging; 

160 a warning is logged if any pixels are out of range of their lookup table 

161 

162 @return an lsst.pipe.base.Struct containing: 

163 - numAmps number of amplifiers found 

164 - numLinearized number of amplifiers linearized (always equal to numAmps for this linearizer) 

165 - numOutOfRange number of pixels out of range of their lookup table (summed across all amps) 

166 

167 @throw RuntimeError if the linearity type is wrong or if the detector name, serial 

168 or number of amplifiers does not match the saved data 

169 """ 

170 self.checkDetector(detector) 

171 ampInfoCat = detector.getAmplifiers() 

172 numOutOfRange = 0 

173 for ampInfo, rowInd, colIndOffset in zip(ampInfoCat, self._rowIndArr, self._colIndOffsetArr): 

174 bbox = ampInfo.getBBox() 

175 ampView = image.Factory(image, bbox) 

176 tableRow = self._table[rowInd, :] 

177 numOutOfRange += applyLookupTable(ampView, tableRow, colIndOffset) 

178 

179 if numOutOfRange > 0 and log is not None: 

180 log.warn("%s pixels of detector \"%s\" were out of range of the linearization table", 

181 numOutOfRange, detector.getName()) 

182 numAmps = len(ampInfoCat) 

183 return Struct( 

184 numAmps=numAmps, 

185 numLinearized=numAmps, 

186 numOutOfRange=numOutOfRange, 

187 ) 

188 

189 def checkDetector(self, detector): 

190 """Check detector name and serial number, ampInfo table length and linearity type 

191 

192 @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector); 

193 

194 @throw RuntimeError if anything doesn't match 

195 """ 

196 if self._detectorName != detector.getName(): 

197 raise RuntimeError("Detector names don't match: %s != %s" % 

198 (self._detectorName, detector.getName())) 

199 if self._detectorSerial != detector.getSerial(): 

200 raise RuntimeError("Detector serial numbers don't match: %s != %s" % 

201 (self._detectorSerial, detector.getSerial())) 

202 

203 numAmps = len(detector.getAmplifiers()) 

204 if numAmps != len(self._rowIndArr): 

205 raise RuntimeError("Detector number of amps = %s does not match saved value %s" % 

206 (numAmps, len(self._rowIndArr))) 

207 self.checkLinearityType(detector) 

208 

209 

210class LinearizeSquared(LinearizeBase): 

211 """Correct non-linearity with a squared model 

212 

213 corrImage = uncorrImage + c0*uncorrImage^2 

214 

215 where c0 is linearity coefficient 0 in the AmpInfoCatalog of the detector 

216 """ 

217 LinearityType = "Squared" 

218 

219 def __call__(self, image, detector, log=None): 

220 """Correct for non-linearity 

221 

222 @param[in] image image to be corrected (an lsst.afw.image.Image) 

223 @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector) 

224 @param[in] log logger (an lsst.log.Log), or None to disable logging; 

225 a warning is logged if any amplifiers are skipped because the square coefficient is 0 

226 

227 @return an lsst.pipe.base.Struct containing at least the following fields: 

228 - nAmps number of amplifiers found 

229 - nLinearized number of amplifiers linearized 

230 

231 @throw RuntimeError if the linearity type is wrong 

232 """ 

233 self.checkLinearityType(detector) 

234 ampInfoCat = detector.getAmplifiers() 

235 numLinearized = 0 

236 for ampInfo in ampInfoCat: 

237 sqCoeff = ampInfo.getLinearityCoeffs()[0] 

238 if sqCoeff != 0: 

239 bbox = ampInfo.getBBox() 

240 ampArr = image.Factory(image, bbox).getArray() 

241 ampArr *= (1 + sqCoeff*ampArr) 

242 numLinearized += 1 

243 

244 numAmps = len(ampInfoCat) 

245 if numAmps > numLinearized and log is not None: 

246 log.warn("%s of %s amps in detector \"%s\" were not linearized (coefficient = 0)", 

247 numAmps - numLinearized, numAmps, detector.getName()) 

248 return Struct( 

249 numAmps=numAmps, 

250 numLinearized=numLinearized, 

251 )