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"),
62 pipelineConnections=LinearitySolveConnections):
63 """Configuration for solving the linearity from PTC dataset.
65 linearityType = pexConfig.ChoiceField(
67 doc=
"Type of linearizer to construct.",
70 "LookupTable":
"Create a lookup table solution.",
71 "Polynomial":
"Create an arbitrary polynomial solution.",
72 "Squared":
"Create a single order squared solution.",
73 "None":
"Create a dummy solution.",
76 polynomialOrder = pexConfig.Field(
78 doc=
"Degree of polynomial to fit.",
81 maxLookupTableAdu = pexConfig.Field(
83 doc=
"Maximum DN value for a LookupTable linearizer.",
89 """Fit the linearity from the PTC dataset.
91 ConfigClass = LinearitySolveConfig
92 _DefaultName =
'cpLinearitySolve'
95 """Ensure that the input and output dimensions are passed along.
99 butlerQC : `lsst.daf.butler.butlerQuantumContext.ButlerQuantumContext`
100 Butler to operate on.
101 inputRefs : `lsst.pipe.base.connections.InputQuantizedConnection`
102 Input data refs to load.
103 ouptutRefs : `lsst.pipe.base.connections.OutputQuantizedConnection`
104 Output data refs to persist.
106 inputs = butlerQC.get(inputRefs)
109 inputs[
'inputDims'] = [exp.dataId.byName()
for exp
in inputRefs.inputPtc]
111 outputs = self.
run(**inputs)
112 butlerQC.put(outputs, outputRefs)
114 def run(self, inputPtc, camera=None, inputDims=None):
115 """Fit non-linearity to PTC data, returning the correct Linearizer
120 inputPtc : `lsst.cp.pipe.PtcDataset`
121 Pre-measured PTC dataset.
122 camera : `lsst.afw.cameraGeom.Camera`, optional
124 inputDims : `lsst.daf.butler.DataCoordinate` or `dict`, optional
125 DataIds to use to populate the output calibration.
129 results : `lsst.pipe.base.Struct`
130 The results struct containing:
132 ``outputLinearizer`` : `lsst.ip.isr.Linearizer`
133 Final linearizer calibration.
134 ``outputProvenance`` : `lsst.ip.isr.IsrProvenance`
135 Provenance data for the new calibration.
139 This task currently fits only polynomial-defined corrections,
140 where the correction coefficients are defined such that:
141 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
142 These `c_i` are defined in terms of the direct polynomial fit:
143 meanVector ~ P(x=timeVector) = sum_j k_j x^j
144 such that c_(j-2) = -k_j/(k_1^j) in units of DN^(1-j) (c.f.,
145 Eq. 37 of 2003.05978). The `config.polynomialOrder` defines
146 the maximum order of x^j to fit. As k_0 and k_1 are
147 degenerate with bias level and gain, they are not included in
148 the non-linearity correction.
152 detector = camera[inputDims[
'detector']]
154 if self.config.linearityType ==
'LookupTable':
155 table = np.zeros((len(detector), self.config.maxLookupTableAdu), dtype=np.float32)
162 linearizer = Linearizer(detector=detector, table=table, log=self.log)
164 for i, amp
in enumerate(detector):
165 ampName = amp.getName()
166 if (len(inputPtc.expIdMask[ampName]) == 0):
167 self.log.warn(f
"Mask not found for {ampName} in non-linearity fit. Using all points.")
168 mask = np.repeat(
True, len(inputPtc.rawExpTimes[ampName]))
170 mask = inputPtc.expIdMask[ampName]
172 timeVector = np.array(inputPtc.rawExpTimes[ampName])[mask]
173 meanVector = np.array(inputPtc.rawMeans[ampName])[mask]
175 if self.config.linearityType
in [
'Polynomial',
'Squared',
'LookupTable']:
176 polyFit = np.zeros(self.config.polynomialOrder + 1)
178 polyFit, polyFitErr, chiSq =
fitLeastSq(polyFit, timeVector, meanVector, funcPolynomial)
182 linearityFit = [-coeff/(k1**order)
for order, coeff
in enumerate(polyFit)]
183 significant = np.where(np.abs(linearityFit) > 1e-10,
True,
False)
184 self.log.info(f
"Significant polynomial fits: {significant}")
186 if self.config.linearityType ==
'Squared':
187 linearityFit = [linearityFit[2]]
188 elif self.config.linearityType ==
'LookupTable':
190 tMax = (self.config.maxLookupTableAdu - polyFit[0])/polyFit[1]
191 timeRange = np.linspace(0, tMax, self.config.maxLookupTableAdu)
192 signalIdeal = polyFit[0] + polyFit[1]*timeRange
194 lookupTableRow = signalIdeal - signalUncorrected
196 linearizer.tableData[tableIndex, :] = lookupTableRow
197 linearityFit = [tableIndex, 0]
205 linearizer.linearityType[ampName] =
'self.config.linearityType'
206 linearizer.linearityCoeffs[ampName] = linearityFit
207 linearizer.linearityBBox[ampName] = amp.getBBox()
208 linearizer.fitParams[ampName] = polyFit
209 linearizer.fitParamsErr[ampName] = polyFitErr
210 linearizer.fitChiSq[ampName] = chiSq
211 self.
debugFit(
'solution', timeVector, meanVector, linearizer, ampName)
213 linearizer.validate()
214 linearizer.updateMetadata(setDate=
True)
215 provenance = IsrProvenance(calibType=
'linearizer')
217 return pipeBase.Struct(
218 outputLinearizer=linearizer,
219 outputProvenance=provenance,
222 def debugFit(self, stepname, timeVector, meanVector, linearizer, ampName):
223 """Debug method for linearity fitting.
228 A label to use to check if we care to debug at a given
230 timeVector : `numpy.array`
231 The values to use as the independent variable in the
233 meanVector : `numpy.array`
234 The values to use as the dependent variable in the
236 linearizer : `lsst.ip.isr.Linearizer`
237 The linearity correction to compare.
239 Amplifier name to lookup linearity correction values.
241 frame = getDebugFrame(self._display, stepname)
243 import matplotlib.pyplot
as plot
244 figure = plot.figure(1)
247 axes = figure.add_axes((min(timeVector), min(meanVector),
248 max(timeVector), max(meanVector)))
249 axes.plot(timeVector, meanVector,
'k+')
251 axes.plot(timeVector,
252 np.polynomial.polynomial.polyval(linearizer.fitParams[ampName],
254 plot.xlabel(
"Exposure Time")
255 plot.ylabel(
"Mean Flux")
256 plot.title(f
"Linearity {ampName} {linearizer.linearityType[ampName]}"
257 f
" chi={linearizer.fitChiSq[ampName]}")
260 prompt =
"Press enter to continue: "
262 ans = input(prompt).lower()
263 if ans
in (
"",
"c",):