27 from .applyLookupTable
import applyLookupTable
29 __all__ = [
"Linearizer",
30 "LinearizeBase",
"LinearizeLookupTable",
"LinearizeSquared",
31 "LinearizeProportional",
"LinearizePolynomial",
"LinearizeNone"]
35 """Parameter set for linearization.
37 These parameters are included in cameraGeom.Amplifier, but
38 should be accessible externally to allow for testing.
42 table : `numpy.array`, optional
43 Lookup table; a 2-dimensional array of floats:
44 - one row for each row index (value of coef[0] in the amplifier)
45 - one column for each image value
46 To avoid copying the table the last index should vary fastest
47 (numpy default "C" order)
48 override : `bool`, optional
49 Override the parameters defined in the detector/amplifier.
50 log : `lsst.log.Log`, optional
51 Logger to handle messages.
56 Raised if the supplied table is not 2D, or if the table has fewer
57 columns than rows (indicating that the indices are swapped).
59 def __init__(self, table=None, detector=None, override=False, log=None):
76 if len(table.shape) != 2:
77 raise RuntimeError(
"table shape = %s; must have two dimensions" % (table.shape,))
78 if table.shape[1] < table.shape[0]:
79 raise RuntimeError(
"table shape = %s; indices are switched" % (table.shape,))
80 self.
tableData = np.array(table, order=
"C")
86 """Apply linearity, setting parameters if necessary.
90 exposure : `lsst.afw.image.Exposure`
95 output : `lsst.pipe.base.Struct`
96 Linearization results:
98 Number of amplifiers considered.
100 Number of amplifiers linearized.
104 """Read linearity parameters from a detector.
108 detector : `lsst.afw.cameraGeom.detector`
109 Input detector with parameters to use.
111 self._detectorName = detector.getName()
112 self._detectorSerial = detector.getSerial()
113 self.populated =
True
116 for amp
in detector.getAmplifiers():
117 ampName = amp.getName()
118 self.linearityCoeffs[ampName] = amp.getLinearityCoeffs()
119 self.linearityType[ampName] = amp.getLinearityType()
120 self.linearityBBox[ampName] = amp.getBBox()
123 """Read linearity parameters from a dict.
128 Dictionary containing detector and amplifier information.
135 for amp
in yamlObject[
'amplifiers']:
136 ampName = amp[
'name']
138 self.
linearityType[ampName] = amp.get(
'linearityType',
'None')
142 """Return linearity parameters as a dict.
150 'hasTable': self._table
is not None,
151 'amplifiers': dict()}
153 outDict[
'amplifiers'][ampName] = {
'linearityType': self.
linearityType[ampName],
158 """Determine the linearity class to use from the type name.
162 linearityTypeName : str
163 String name of the linearity type that is needed.
167 linearityType : `~lsst.ip.isr.linearize.LinearizeBase`
168 The appropriate linearity class to use. If no matching class
169 is found, `None` is returned.
171 for t
in [LinearizeLookupTable,
174 LinearizeProportional,
176 if t.LinearityType == linearityTypeName:
181 """Validate linearity for a detector/amplifier.
185 detector : `lsst.afw.cameraGeom.Detector`, optional
186 Detector to validate, along with its amplifiers.
187 amplifier : `lsst.afw.cameraGeom.Amplifier`, optional
188 Single amplifier to validate.
193 Raised if there is a mismatch in linearity parameters, and
194 the cameraGeom parameters are not being overridden.
196 amplifiersToCheck = []
199 raise RuntimeError(
"Detector names don't match: %s != %s" %
202 raise RuntimeError(
"Detector serial numbers don't match: %s != %s" %
205 raise RuntimeError(
"Detector number of amps = %s does not match saved value %s" %
206 (len(detector.getAmplifiers()),
208 amplifiersToCheck.extend(detector.getAmplifiers())
211 amplifiersToCheck.extend(amplifier)
213 for amp
in amplifiersToCheck:
214 ampName = amp.getName()
216 raise RuntimeError(
"Amplifier %s is not in linearity data" %
220 self.
log.warn(
"Overriding amplifier defined linearityType (%s) for %s",
223 raise RuntimeError(
"Amplifier %s type %s does not match saved value %s" %
224 (ampName, amp.getLinearityType(), self.
linearityType[ampName]))
225 if not np.allclose(amp.getLinearityCoeffs(), self.
linearityCoeffs[ampName], equal_nan=
True):
227 self.
log.warn(
"Overriding amplifier defined linearityCoeffs (%s) for %s",
230 raise RuntimeError(
"Amplifier %s coeffs %s does not match saved value %s" %
234 """Apply the linearity to an image.
236 If the linearity parameters are populated, use those,
237 otherwise use the values from the detector.
241 image : `~lsst.afw.image.image`
243 detector : `~lsst.afw.cameraGeom.detector`
244 Detector to use for linearity parameters if not already
246 log : `~lsst.log.Log`, optional
247 Log object to use for logging.
263 if linearizer
is not None:
265 success, outOfRange = linearizer()(ampView, **{
'coeffs': self.
linearityCoeffs[ampName],
268 numOutOfRange += outOfRange
271 elif log
is not None:
272 log.warn(
"Amplifier %s did not linearize.",
276 numLinearized=numLinearized,
277 numOutOfRange=numOutOfRange
282 """Abstract base class functor for correcting non-linearity.
284 Subclasses must define __call__ and set class variable
285 LinearityType to a string that will be used for linearity type in
286 the cameraGeom.Amplifier.linearityType field.
288 All linearity corrections should be defined in terms of an
289 additive correction, such that:
291 corrected_value = uncorrected_value + f(uncorrected_value)
297 """Correct non-linearity.
301 image : `lsst.afw.image.Image`
302 Image to be corrected
304 Dictionary of parameter keywords:
306 Coefficient vector (`list` or `numpy.array`).
308 Lookup table data (`numpy.array`).
310 Logger to handle messages (`lsst.log.Log`).
315 If true, a correction was applied successfully.
320 Raised if the linearity type listed in the
321 detector does not match the class type.
326 class LinearizeLookupTable(LinearizeBase):
327 """Correct non-linearity with a persisted lookup table.
329 The lookup table consists of entries such that given
330 "coefficients" c0, c1:
332 for each i,j of image:
334 colInd = int(c1 + uncorrImage[i,j])
335 corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
337 - c0: row index; used to identify which row of the table to use
338 (typically one per amplifier, though one can have multiple
339 amplifiers use the same table)
340 - c1: column index offset; added to the uncorrected image value
341 before truncation; this supports tables that can handle
342 negative image values; also, if the c1 ends with .5 then
343 the nearest index is used instead of truncating to the
346 LinearityType =
"LookupTable"
349 """Correct for non-linearity.
353 image : `lsst.afw.image.Image`
354 Image to be corrected
356 Dictionary of parameter keywords:
358 Columnation vector (`list` or `numpy.array`).
360 Lookup table data (`numpy.array`).
362 Logger to handle messages (`lsst.log.Log`).
367 If true, a correction was applied successfully.
372 Raised if the requested row index is out of the table
377 rowInd, colIndOffset = kwargs[
'coeffs'][0:2]
378 table = kwargs[
'table']
381 numTableRows = table.shape[0]
383 if rowInd < 0
or rowInd > numTableRows:
384 raise RuntimeError(
"LinearizeLookupTable rowInd=%s not in range[0, %s)" %
385 (rowInd, numTableRows))
386 tableRow = table[rowInd, :]
389 if numOutOfRange > 0
and log
is not None:
390 log.warn(
"%s pixels were out of range of the linearization table",
392 if numOutOfRange < image.getArray().size:
393 return True, numOutOfRange
395 return False, numOutOfRange
399 """Correct non-linearity with a polynomial mode.
401 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
403 where c_i are the linearity coefficients for each amplifier.
404 Lower order coefficients are not included as they duplicate other
405 calibration parameters:
407 A coefficient multiplied by uncorrImage**0 is equivalent to
408 bias level. Irrelevant for correcting non-linearity.
410 A coefficient multiplied by uncorrImage**1 is proportional
411 to the gain. Not necessary for correcting non-linearity.
413 LinearityType =
"Polynomial"
416 """Correct non-linearity.
420 image : `lsst.afw.image.Image`
421 Image to be corrected
423 Dictionary of parameter keywords:
425 Coefficient vector (`list` or `numpy.array`).
427 Logger to handle messages (`lsst.log.Log`).
432 If true, a correction was applied successfully.
434 if np.any(np.isfinite(kwargs[
'coeffs'])):
436 if not np.any(kwargs[
'coeffs']):
439 ampArray = image.getArray()
440 correction = np.zeroes_like(ampArray)
441 for coeff, order
in enumerate(kwargs[
'coeffs'], start=2):
442 correction += coeff * np.power(ampArray, order)
443 ampArray += correction
449 """Correct non-linearity with a squared model.
451 corrImage = uncorrImage + c0*uncorrImage^2
453 where c0 is linearity coefficient 0 for each amplifier.
455 LinearityType =
"Squared"
458 """Correct for non-linearity.
462 image : `lsst.afw.image.Image`
463 Image to be corrected
465 Dictionary of parameter keywords:
467 Coefficient vector (`list` or `numpy.array`).
469 Logger to handle messages (`lsst.log.Log`).
474 If true, a correction was applied successfully.
477 sqCoeff = kwargs[
'coeffs'][0]
479 ampArr = image.getArray()
480 ampArr *= (1 + sqCoeff*ampArr)
487 """Do not correct non-linearity.
489 LinearityType =
"Proportional"
492 """Do not correct for non-linearity.
496 image : `lsst.afw.image.Image`
497 Image to be corrected
499 Dictionary of parameter keywords:
501 Coefficient vector (`list` or `numpy.array`).
503 Logger to handle messages (`lsst.log.Log`).
508 If true, a correction was applied successfully.
514 """Do not correct non-linearity.
516 LinearityType =
"None"
519 """Do not correct for non-linearity.
523 image : `lsst.afw.image.Image`
524 Image to be corrected
526 Dictionary of parameter keywords:
528 Coefficient vector (`list` or `numpy.array`).
530 Logger to handle messages (`lsst.log.Log`).
535 If true, a correction was applied successfully.