28 from lsstDebug
import getDebugFrame
31 from .utils
import (fitLeastSq, funcPolynomial)
34 __all__ = [
"LinearitySolveTask",
"LinearitySolveConfig"]
38 dimensions=(
"instrument",
"detector")):
42 doc=
"Input PTC dataset.",
43 storageClass=
"StructuredDataDict",
44 dimensions=(
"instrument",
"detector"),
49 doc=
"Camera Geometry definition.",
50 storageClass=
"Camera",
51 dimensions=(
"instrument", ),
53 outputLinearizer = cT.Output(
55 doc=
"Output linearity measurements.",
56 storageClass=
"Linearizer",
57 dimensions=(
"instrument",
"detector"),
63 pipelineConnections=LinearitySolveConnections):
64 """Configuration for solving the linearity from PTC dataset.
66 linearityType = pexConfig.ChoiceField(
68 doc=
"Type of linearizer to construct.",
71 "LookupTable":
"Create a lookup table solution.",
72 "Polynomial":
"Create an arbitrary polynomial solution.",
73 "Squared":
"Create a single order squared solution.",
74 "None":
"Create a dummy solution.",
77 polynomialOrder = pexConfig.Field(
79 doc=
"Degree of polynomial to fit.",
82 maxLookupTableAdu = pexConfig.Field(
84 doc=
"Maximum DN value for a LookupTable linearizer.",
90 """Fit the linearity from the PTC dataset.
92 ConfigClass = LinearitySolveConfig
93 _DefaultName =
'cpLinearitySolve'
96 """Ensure that the input and output dimensions are passed along.
100 butlerQC : `lsst.daf.butler.butlerQuantumContext.ButlerQuantumContext`
101 Butler to operate on.
102 inputRefs : `lsst.pipe.base.connections.InputQuantizedConnection`
103 Input data refs to load.
104 ouptutRefs : `lsst.pipe.base.connections.OutputQuantizedConnection`
105 Output data refs to persist.
107 inputs = butlerQC.get(inputRefs)
110 inputs[
'inputDims'] = [exp.dataId.byName()
for exp
in inputRefs.inputPtc]
112 outputs = self.
run(**inputs)
113 butlerQC.put(outputs, outputRefs)
115 def run(self, inputPtc, camera=None, inputDims=None):
116 """Fit non-linearity to PTC data, returning the correct Linearizer
121 inputPtc : `lsst.cp.pipe.PtcDataset`
122 Pre-measured PTC dataset.
123 camera : `lsst.afw.cameraGeom.Camera`, optional
125 inputDims : `lsst.daf.butler.DataCoordinate` or `dict`, optional
126 DataIds to use to populate the output calibration.
130 results : `lsst.pipe.base.Struct`
131 The results struct containing:
133 ``outputLinearizer`` : `lsst.ip.isr.Linearizer`
134 Final linearizer calibration.
135 ``outputProvenance`` : `lsst.ip.isr.IsrProvenance`
136 Provenance data for the new calibration.
140 This task currently fits only polynomial-defined corrections,
141 where the correction coefficients are defined such that:
142 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
143 These `c_i` are defined in terms of the direct polynomial fit:
144 meanVector ~ P(x=timeVector) = sum_j k_j x^j
145 such that c_(j-2) = -k_j/(k_1^j) in units of DN^(1-j) (c.f.,
146 Eq. 37 of 2003.05978). The `config.polynomialOrder` defines
147 the maximum order of x^j to fit. As k_0 and k_1 are
148 degenerate with bias level and gain, they are not included in
149 the non-linearity correction.
153 detector = camera[inputDims[
'detector']]
155 if self.config.linearityType ==
'LookupTable':
156 table = np.zeros((len(detector), self.config.maxLookupTableAdu), dtype=np.float32)
163 linearizer = Linearizer(detector=detector, table=table, log=self.log)
165 for i, amp
in enumerate(detector):
166 ampName = amp.getName()
167 if (len(inputPtc.expIdMask[ampName]) == 0):
168 self.log.warn(f
"Mask not found for {ampName} in non-linearity fit. Using all points.")
169 mask = np.repeat(
True, len(inputPtc.rawExpTimes[ampName]))
171 mask = inputPtc.expIdMask[ampName]
173 timeVector = np.array(inputPtc.rawExpTimes[ampName])[mask]
174 meanVector = np.array(inputPtc.rawMeans[ampName])[mask]
176 if self.config.linearityType
in [
'Polynomial',
'Squared',
'LookupTable']:
177 polyFit = np.zeros(self.config.polynomialOrder + 1)
179 polyFit, polyFitErr, chiSq =
fitLeastSq(polyFit, timeVector, meanVector, funcPolynomial)
183 linearityFit = [-coeff/(k1**order)
for order, coeff
in enumerate(polyFit)]
184 significant = np.where(np.abs(linearityFit) > 1e-10,
True,
False)
185 self.log.info(f
"Significant polynomial fits: {significant}")
187 if self.config.linearityType ==
'Squared':
188 linearityFit = [linearityFit[2]]
189 elif self.config.linearityType ==
'LookupTable':
191 tMax = (self.config.maxLookupTableAdu - polyFit[0])/polyFit[1]
192 timeRange = np.linspace(0, tMax, self.config.maxLookupTableAdu)
193 signalIdeal = polyFit[0] + polyFit[1]*timeRange
195 lookupTableRow = signalIdeal - signalUncorrected
197 linearizer.tableData[tableIndex, :] = lookupTableRow
198 linearityFit = [tableIndex, 0]
206 linearizer.linearityType[ampName] =
'self.config.linearityType'
207 linearizer.linearityCoeffs[ampName] = linearityFit
208 linearizer.linearityBBox[ampName] = amp.getBBox()
209 linearizer.fitParams[ampName] = polyFit
210 linearizer.fitParamsErr[ampName] = polyFitErr
211 linearizer.fitChiSq[ampName] = chiSq
212 self.
debugFit(
'solution', timeVector, meanVector, linearizer, ampName)
214 linearizer.validate()
215 linearizer.updateMetadata(setDate=
True)
216 provenance = IsrProvenance(calibType=
'linearizer')
218 return pipeBase.Struct(
219 outputLinearizer=linearizer,
220 outputProvenance=provenance,
223 def debugFit(self, stepname, timeVector, meanVector, linearizer, ampName):
224 """Debug method for linearity fitting.
229 A label to use to check if we care to debug at a given
231 timeVector : `numpy.array`
232 The values to use as the independent variable in the
234 meanVector : `numpy.array`
235 The values to use as the dependent variable in the
237 linearizer : `lsst.ip.isr.Linearizer`
238 The linearity correction to compare.
240 Amplifier name to lookup linearity correction values.
242 frame = getDebugFrame(self._display, stepname)
244 import matplotlib.pyplot
as plot
245 figure = plot.figure(1)
248 axes = figure.add_axes((min(timeVector), min(meanVector),
249 max(timeVector), max(meanVector)))
250 axes.plot(timeVector, meanVector,
'k+')
252 axes.plot(timeVector,
253 np.polynomial.polynomial.polyval(linearizer.fitParams[ampName],
255 plot.xlabel(
"Exposure Time")
256 plot.ylabel(
"Mean Flux")
257 plot.title(f
"Linearity {ampName} {linearizer.linearityType[ampName]}"
258 f
" chi={linearizer.fitChiSq[ampName]}")
261 prompt =
"Press enter to continue: "
263 ans = input(prompt).lower()
264 if ans
in (
"",
"c",):