26 "LinearizeLookupTable",
28 "LinearizeProportional",
29 "LinearizePolynomial",
31 "LinearizeDoubleSpline",
37from scipy.interpolate
import Akima1DInterpolator
39from astropy.table
import Table
41from lsst.pipe.base
import Struct
42from lsst.geom import Box2I, Point2I, Extent2I
43from .applyLookupTable
import applyLookupTable
44from .calibType
import IsrCalib
45from .isrFunctions
import isTrimmedImage
49 """Parameter set for linearization.
51 These parameters are included in `lsst.afw.cameraGeom.Amplifier`, but
52 should be accessible externally to allow for testing.
56 table : `numpy.array`, optional
57 Lookup table; a 2-dimensional array of floats:
59 - one row for each row index (value of coef[0] in the amplifier)
60 - one column for each image value
62 To avoid copying the table the last index should vary fastest
63 (numpy default "C" order)
64 detector : `lsst.afw.cameraGeom.Detector`, optional
65 Detector object. Passed to self.fromDetector() on init.
66 log : `logging.Logger`, optional
67 Logger to handle messages.
68 kwargs : `dict`, optional
69 Other keyword arguments to pass to the parent init.
74 Raised if the supplied table is not 2D, or if the table has fewer
75 columns than rows (indicating that the indices are swapped).
79 The linearizer attributes stored are:
82 Whether a linearity correction is defined for this detector.
84 Whether the detector parameters should be overridden.
85 ampNames : `list` [`str`]
86 List of amplifier names to correct.
87 inputGain : `dict` [`str`, `float`]
88 Dictionary keyed by amplifirer name containing the input
89 gain from the PTC used to construct this linearizer.
90 linearityCoeffs : `dict` [`str`, `np.ndarray`]
91 Coefficients to use in correction. Indexed by amplifier
92 names. The format of the array depends on the type of
94 linearityType : `dict` [`str`, `str`]
95 Type of correction to use, indexed by amplifier names.
96 linearityBBox : `dict` [`str`, `lsst.geom.Box2I`]
97 Bounding box the correction is valid over, indexed by
99 fitParams : `dict` [`str`, `np.ndarray`], optional
100 Linearity fit parameters used to construct the correction
101 coefficients, indexed as above.
102 fitParamsErr : `dict` [`str`, `np.ndarray`], optional
103 Uncertainty values of the linearity fit parameters used to
104 construct the correction coefficients, indexed as above.
105 fitChiSq : `dict` [`str`, `float`], optional
106 Chi-squared value of the linearity fit, indexed as above.
107 fitResiduals : `dict` [`str`, `np.ndarray`], optional
108 Residuals of the fit, indexed as above. Used for
109 calculating photodiode corrections
110 fitResidualsSigmaMad : `dict` [`str`, `float`], optional
111 Robust median-absolute-deviation of fit residuals, scaled
113 fitResidualsUnmasked : `dict` [`str`, `np.ndarray`], optional
114 Same as fitResiduals, but all outliers are included and
116 fitResidualsModel : `dict` [`str`, `np.ndarray`], optional
117 The model count level for each of the fitResiduals.
118 linearFit : The linear fit to the low flux region of the curve.
120 tableData : `np.ndarray`, optional
121 Lookup table data for the linearity correction.
122 inputAbscissa : `dict` [`str`, `np.ndarray`], optional
123 Input abscissa used to construct linearizer (usually photodiode
125 inputOrdinate : `dict` [`str`, `np.ndarray`], optional
126 Input ordinate used to construct linearizer (raw mean counts).
127 inputMask : `dict` [`str`, `np.ndarray`], optional
128 Input mask used for the fitting.
129 inputGroupingIndex : `dict` [`str`, `np.ndarray`], optional
130 Input grouping index used for fitting.
131 inputNormalization : `dict` [`str`, `np.ndarray`], optional
132 Input normalization that was applied to the abscissa for
133 each pair from the PTC used for the linearization fit.
134 absoluteReferenceAmplifier : `str`, optional
135 Amplifier used for the reference for absolute linearization
138 Version 1.4 adds ``linearityTurnoff`` and ``linearityMaxSignal``.
139 Version 1.5 adds ``fitResidualsUnmasked``, ``inputAbscissa``,
140 ``inputOrdinate``, ``inputMask``, ``inputGroupingIndex``,
141 ``fitResidualsModel``, and ``inputNormalization``.
142 Version 1.6 adds ``absoluteReferenceAmplifier``.
143 Version 1.7 adds ``inputGain``.
145 _OBSTYPE =
"LINEARIZER"
146 _SCHEMA =
'Gen3 Linearizer'
175 if table
is not None:
176 if len(table.shape) != 2:
177 raise RuntimeError(
"table shape = %s; must have two dimensions" % (table.shape,))
178 if table.shape[1] < table.shape[0]:
179 raise RuntimeError(
"table shape = %s; indices are switched" % (table.shape,))
180 self.
tableData = np.array(table, order=
"C")
188 'ampNames',
'inputGain',
189 'linearityCoeffs',
'linearityType',
'linearityBBox',
190 'fitParams',
'fitParamsErr',
'fitChiSq',
191 'fitResiduals',
'fitResidualsSigmaMad',
'linearFit',
'tableData',
192 'units',
'linearityTurnoff',
'linearityMaxSignal',
193 'fitResidualsUnmasked',
'inputAbscissa',
'inputOrdinate',
194 'inputMask',
'inputGroupingIndex',
'fitResidualsModel',
195 'inputNormalization',
'absoluteReferenceAmplifier'])
198 """Update metadata keywords with new values.
200 This calls the base class's method after ensuring the required
201 calibration keywords will be saved.
205 setDate : `bool`, optional
206 Update the CALIBDATE fields in the metadata to the current
207 time. Defaults to False.
209 Other keyword parameters to set in the metadata.
213 kwargs[
'HAS_TABLE'] = self.
tableData is not None
219 """Read linearity parameters from a detector.
223 detector : `lsst.afw.cameraGeom.detector`
224 Input detector with parameters to use.
228 calib : `lsst.ip.isr.Linearizer`
229 The calibration constructed from the detector.
237 for amp
in detector.getAmplifiers():
238 ampName = amp.getName()
251 """Construct a calibration from a dictionary of properties
256 Dictionary of properties
260 calib : `lsst.ip.isr.Linearity`
261 Constructed calibration.
266 Raised if the supplied dictionary is for a different
272 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
273 raise RuntimeError(f
"Incorrect linearity supplied. Expected {calib._OBSTYPE}, "
274 f
"found {dictionary['metadata']['OBSTYPE']}")
276 calib.setMetadata(dictionary[
'metadata'])
278 calib.hasLinearity = dictionary.get(
'hasLinearity',
279 dictionary[
'metadata'].get(
'HAS_LINEARITY',
False))
280 calib.override = dictionary.get(
'override',
True)
285 calib.linearityUnits = dictionary.get(
'linearityUnits',
'adu')
287 if calib.hasLinearity:
288 for ampName
in dictionary[
'amplifiers']:
289 amp = dictionary[
'amplifiers'][ampName]
290 calib.ampNames.append(ampName)
294 calib.inputGain[ampName] = amp.get(
'inputGain', np.nan)
295 calib.linearityCoeffs[ampName] = np.array(amp.get(
'linearityCoeffs', [0.0]))
296 calib.linearityType[ampName] = amp.get(
'linearityType',
'None')
297 calib.linearityBBox[ampName] = amp.get(
'linearityBBox',
None)
299 calib.inputAbscissa[ampName] = np.array(amp.get(
'inputAbscissa', [0.0]))
300 calib.inputOrdinate[ampName] = np.array(amp.get(
'inputOrdinate', [0.0]))
301 calib.inputMask[ampName] = np.array(amp.get(
'inputMask', [
False]))
302 calib.inputGroupingIndex[ampName] = np.array(amp.get(
'inputGroupingIndex', [0.0]))
303 calib.inputNormalization[ampName] = np.array(amp.get(
'inputNormalization', [1.0]))
305 calib.fitParams[ampName] = np.array(amp.get(
'fitParams', [0.0]))
306 calib.fitParamsErr[ampName] = np.array(amp.get(
'fitParamsErr', [0.0]))
307 calib.fitChiSq[ampName] = amp.get(
'fitChiSq', np.nan)
308 calib.fitResiduals[ampName] = np.array(amp.get(
'fitResiduals', [0.0]))
309 calib.fitResidualsSigmaMad[ampName] = np.array(amp.get(
'fitResidualsSigmaMad', np.nan))
310 calib.fitResidualsUnmasked[ampName] = np.array(amp.get(
'fitResidualsUnmasked', [0.0]))
311 calib.fitResidualsModel[ampName] = np.array(amp.get(
'fitResidualsModel', [0.0]))
312 calib.linearFit[ampName] = np.array(amp.get(
'linearFit', [0.0]))
314 calib.linearityTurnoff[ampName] = np.array(amp.get(
'linearityTurnoff', np.nan))
315 calib.linearityMaxSignal[ampName] = np.array(amp.get(
'linearityMaxSignal', np.nan))
317 calib.tableData = dictionary.get(
'tableData',
None)
319 calib.tableData = np.array(calib.tableData)
321 calib.absoluteReferenceAmplifier = dictionary.get(
322 'absoluteReferenceAmplifier', calib.absoluteReferenceAmplifier,
328 """Return linearity parameters as a dict.
341 'amplifiers': dict(),
345 outDict[
'amplifiers'][ampName] = {
352 'inputMask': self.
inputMask[ampName].tolist(),
355 'fitParams': self.
fitParams[ampName].tolist(),
362 'linearFit': self.
linearFit[ampName].tolist(),
367 outDict[
'tableData'] = self.
tableData.tolist()
375 """Read linearity from a FITS file.
377 This method uses the `fromDict` method to create the
378 calibration, after constructing an appropriate dictionary from
383 tableList : `list` [`astropy.table.Table`]
384 afwTable read from input file name.
388 linearity : `~lsst.ip.isr.linearize.Linearizer``
389 Linearity parameters.
393 The method reads a FITS file with 1 or 2 extensions. The metadata is
394 read from the header of extension 1, which must exist. Then the table
395 is loaded, and the ['AMPLIFIER_NAME', 'TYPE', 'COEFFS', 'BBOX_X0',
396 'BBOX_Y0', 'BBOX_DX', 'BBOX_DY'] columns are read and used to set each
397 dictionary by looping over rows.
398 Extension 2 is then attempted to read in the try block (which only
399 exists for lookup tables). It has a column named 'LOOKUP_VALUES' that
400 contains a vector of the lookup entries in each row.
402 coeffTable = tableList[0]
404 metadata = coeffTable.meta
406 inDict[
'metadata'] = metadata
407 inDict[
'hasLinearity'] = metadata.get(
'HAS_LINEARITY',
False)
408 inDict[
'amplifiers'] = dict()
409 inDict[
'linearityUnits'] = metadata.get(
'LINEARITY_UNITS',
'adu')
411 for record
in coeffTable:
412 ampName = record[
'AMPLIFIER_NAME']
413 inputGain = record[
'INPUT_GAIN']
if 'INPUT_GAIN' in record.columns
else np.nan
414 inputAbscissa = record[
'INP_ABSCISSA']
if 'INP_ABSCISSA' in record.columns
else np.array([0.0])
415 inputOrdinate = record[
'INP_ORDINATE']
if 'INP_ORDINATE' in record.columns
else np.array([0.0])
416 inputMask = record[
'INP_MASK']
if 'INP_MASK' in record.columns
else np.array([
False])
417 inputGroupingIndex = record[
'INP_GROUPING_INDEX']
if 'INP_GROUPING_INDEX' in record.columns \
419 inputNormalization = record[
'INP_NORMALIZATION']
if 'INP_NORMALIZATION' in record.columns \
421 fitParams = record[
'FIT_PARAMS']
if 'FIT_PARAMS' in record.columns
else np.array([0.0])
422 fitParamsErr = record[
'FIT_PARAMS_ERR']
if 'FIT_PARAMS_ERR' in record.columns
else np.array([0.0])
423 fitChiSq = record[
'RED_CHI_SQ']
if 'RED_CHI_SQ' in record.columns
else np.nan
424 fitResiduals = record[
'FIT_RES']
if 'FIT_RES' in record.columns
else np.array([0.0])
425 fitResidualsSigmaMad = record[
'FIT_RES_SIGMAD']
if 'FIT_RES_SIGMAD' in record.columns
else np.nan
426 fitResidualsUnmasked = record[
'FIT_RES_UNMASKED'] \
427 if 'FIT_RES_UNMASKED' in record.columns
else np.array([0.0])
428 fitResidualsModel = record[
'FIT_RES_MODEL'] \
429 if 'FIT_RES_MODEL' in record.columns
else np.array([0.0])
430 linearFit = record[
'LIN_FIT']
if 'LIN_FIT' in record.columns
else np.array([0.0])
432 linearityTurnoff = record[
'LINEARITY_TURNOFF']
if 'LINEARITY_TURNOFF' in record.columns \
434 linearityMaxSignal = record[
'LINEARITY_MAX_SIGNAL']
if 'LINEARITY_MAX_SIGNAL' in record.columns \
437 inDict[
'amplifiers'][ampName] = {
438 'inputGain': inputGain,
439 'linearityType': record[
'TYPE'],
440 'linearityCoeffs': record[
'COEFFS'],
441 'linearityBBox':
Box2I(
Point2I(record[
'BBOX_X0'], record[
'BBOX_Y0']),
442 Extent2I(record[
'BBOX_DX'], record[
'BBOX_DY'])),
443 'inputAbscissa': inputAbscissa,
444 'inputOrdinate': inputOrdinate,
445 'inputMask': inputMask,
446 'inputGroupingIndex': inputGroupingIndex,
447 'inputNormalization': inputNormalization,
448 'fitParams': fitParams,
449 'fitParamsErr': fitParamsErr,
450 'fitChiSq': fitChiSq,
451 'fitResiduals': fitResiduals,
452 'fitResidualsSigmaMad': fitResidualsSigmaMad,
453 'fitResidualsUnmasked': fitResidualsUnmasked,
454 'fitResidualsModel': fitResidualsModel,
455 'linearFit': linearFit,
456 'linearityTurnoff': linearityTurnoff,
457 'linearityMaxSignal': linearityMaxSignal,
460 if len(tableList) > 1:
461 tableData = tableList[1]
462 inDict[
'tableData'] = [record[
'LOOKUP_VALUES']
for record
in tableData]
464 if 'ABS_REF_AMP' in coeffTable.columns:
465 inDict[
'absoluteReferenceAmplifier'] = str(coeffTable[
'ABS_REF_AMP'][0])
467 inDict[
'absoluteReferenceAmplifier'] =
''
472 """Construct a list of tables containing the information in this
475 The list of tables should create an identical calibration
476 after being passed to this class's fromTable method.
480 tableList : `list` [`astropy.table.Table`]
481 List of tables containing the linearity calibration
487 catalog = Table([{
'AMPLIFIER_NAME': ampName,
502 'RED_CHI_SQ': self.
fitChiSq[ampName],
513 tableList.append(catalog)
516 catalog = Table([{
'LOOKUP_VALUES': value}
for value
in self.
tableData])
517 tableList.append(catalog)
521 """Determine the linearity class to use from the type name.
525 linearityTypeName : str
526 String name of the linearity type that is needed.
530 linearityType : `~lsst.ip.isr.linearize.LinearizeBase`
531 The appropriate linearity class to use. If no matching class
532 is found, `None` is returned.
534 for t
in [LinearizeLookupTable,
537 LinearizeProportional,
539 LinearizeDoubleSpline,
541 if t.LinearityType == linearityTypeName:
546 """Validate linearity for a detector/amplifier.
550 detector : `lsst.afw.cameraGeom.Detector`, optional
551 Detector to validate, along with its amplifiers.
552 amplifier : `lsst.afw.cameraGeom.Amplifier`, optional
553 Single amplifier to validate.
558 Raised if there is a mismatch in linearity parameters, and
559 the cameraGeom parameters are not being overridden.
561 amplifiersToCheck = []
564 raise RuntimeError(
"Detector names don't match: %s != %s" %
567 raise RuntimeError(
"Detector IDs don't match: %s != %s" %
576 raise RuntimeError(
"Detector number of amps = %s does not match saved value %s" %
577 (len(detector.getAmplifiers()),
579 amplifiersToCheck.extend(detector.getAmplifiers())
582 amplifiersToCheck.extend(amplifier)
584 for amp
in amplifiersToCheck:
585 ampName = amp.getName()
587 raise RuntimeError(
"Amplifier %s is not in linearity data" %
591 self.
log.debug(
"Overriding amplifier defined linearityType (%s) for %s",
594 raise RuntimeError(
"Amplifier %s type %s does not match saved value %s" %
595 (ampName, amp.getLinearityType(), self.
linearityType[ampName]))
596 if (amp.getLinearityCoeffs().shape != self.
linearityCoeffs[ampName].shape
or not
597 np.allclose(amp.getLinearityCoeffs(), self.
linearityCoeffs[ampName], equal_nan=
True)):
599 self.
log.debug(
"Overriding amplifier defined linearityCoeffs (%s) for %s",
602 raise RuntimeError(
"Amplifier %s coeffs %s does not match saved value %s" %
606 """Apply the linearity to an image.
608 If the linearity parameters are populated, use those,
609 otherwise use the values from the detector.
613 image : `~lsst.afw.image.image`
615 detector : `~lsst.afw.cameraGeom.detector`, optional
616 Detector to use to determine exposure trimmed state. If
617 supplied, but no other linearity information is provided
618 by the calibration, then the static solution stored in the
619 detector will be used.
620 log : `~logging.Logger`, optional
621 Log object to use for logging.
622 gains : `dict` [`str`, `float`], optional
623 Dictionary of amp name to gain. If this is provided then
624 linearity terms will be converted from adu to electrons.
625 Only used for Spline linearity corrections.
647 gainValue = gains[ampName]
651 if linearizer
is not None:
654 bbox = detector[ampName].getBBox()
656 bbox = detector[ampName].getRawBBox()
660 ampView = image.Factory(image, bbox)
661 success, outOfRange = linearizer()(ampView, **{
'coeffs': self.
linearityCoeffs[ampName],
665 numOutOfRange += outOfRange
668 elif log
is not None:
669 log.warning(
"Amplifier %s did not linearize.",
673 numLinearized=numLinearized,
674 numOutOfRange=numOutOfRange
679 """Abstract base class functor for correcting non-linearity.
681 Subclasses must define ``__call__`` and set class variable
682 LinearityType to a string that will be used for linearity type in
683 the cameraGeom.Amplifier.linearityType field.
685 All linearity corrections should be defined in terms of an
686 additive correction, such that:
688 corrected_value = uncorrected_value + f(uncorrected_value)
694 """Correct non-linearity.
698 image : `lsst.afw.image.Image`
699 Image to be corrected
701 Dictionary of parameter keywords:
704 Coefficient vector (`list` or `np.ndarray`).
706 Lookup table data (`np.ndarray`).
708 Logger to handle messages (`logging.Logger`).
713 If `True`, a correction was applied successfully.
718 Raised if the linearity type listed in the
719 detector does not match the class type.
724class LinearizeLookupTable(LinearizeBase):
725 """Correct non-linearity with a persisted lookup table.
727 The lookup table consists of entries such that given
728 "coefficients" c0, c1:
730 for each i,j of image:
732 colInd = int(c1 + uncorrImage[i,j])
733 corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
735 - c0: row index; used to identify which row of the table to use
736 (typically one per amplifier, though one can have multiple
737 amplifiers use the same table)
738 - c1: column index offset; added to the uncorrected image value
739 before truncation; this supports tables that can handle
740 negative image values; also, if the c1 ends with .5 then
741 the nearest index is used instead of truncating to the
744 LinearityType =
"LookupTable"
747 """Correct for non-linearity.
751 image : `lsst.afw.image.Image`
752 Image to be corrected
754 Dictionary of parameter keywords:
757 Columnation vector (`list` or `np.ndarray`).
759 Lookup table data (`np.ndarray`).
761 Logger to handle messages (`logging.Logger`).
765 output : `tuple` [`bool`, `int`]
766 If true, a correction was applied successfully. The
767 integer indicates the number of pixels that were
768 uncorrectable by being out of range.
773 Raised if the requested row index is out of the table
778 rowInd, colIndOffset = kwargs[
'coeffs'][0:2]
779 table = kwargs[
'table']
782 numTableRows = table.shape[0]
784 if rowInd < 0
or rowInd > numTableRows:
785 raise RuntimeError(
"LinearizeLookupTable rowInd=%s not in range[0, %s)" %
786 (rowInd, numTableRows))
787 tableRow = np.array(table[rowInd, :], dtype=image.getArray().dtype)
791 if numOutOfRange > 0
and log
is not None:
792 log.warning(
"%s pixels were out of range of the linearization table",
794 if numOutOfRange < image.getArray().size:
795 return True, numOutOfRange
797 return False, numOutOfRange
801 """Correct non-linearity with a polynomial mode.
805 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
807 where ``c_i`` are the linearity coefficients for each amplifier.
808 Lower order coefficients are not included as they duplicate other
809 calibration parameters:
812 A coefficient multiplied by ``uncorrImage**0`` is equivalent to
813 bias level. Irrelevant for correcting non-linearity.
815 A coefficient multiplied by ``uncorrImage**1`` is proportional
816 to the gain. Not necessary for correcting non-linearity.
818 LinearityType =
"Polynomial"
821 """Correct non-linearity.
825 image : `lsst.afw.image.Image`
826 Image to be corrected
828 Dictionary of parameter keywords:
831 Coefficient vector (`list` or `np.ndarray`).
832 If the order of the polynomial is n, this list
833 should have a length of n-1 ("k0" and "k1" are
834 not needed for the correction).
836 Logger to handle messages (`logging.Logger`).
840 output : `tuple` [`bool`, `int`]
841 If true, a correction was applied successfully. The
842 integer indicates the number of pixels that were
843 uncorrectable by being out of range.
845 if not np.any(np.isfinite(kwargs[
'coeffs'])):
847 if not np.any(kwargs[
'coeffs']):
850 ampArray = image.getArray()
851 correction = np.zeros_like(ampArray)
852 for order, coeff
in enumerate(kwargs[
'coeffs'], start=2):
853 correction += coeff * np.power(ampArray, order)
854 ampArray += correction
860 """Correct non-linearity with a squared model.
862 corrImage = uncorrImage + c0*uncorrImage^2
864 where c0 is linearity coefficient 0 for each amplifier.
866 LinearityType =
"Squared"
869 """Correct for non-linearity.
873 image : `lsst.afw.image.Image`
874 Image to be corrected
876 Dictionary of parameter keywords:
879 Coefficient vector (`list` or `np.ndarray`).
881 Logger to handle messages (`logging.Logger`).
885 output : `tuple` [`bool`, `int`]
886 If true, a correction was applied successfully. The
887 integer indicates the number of pixels that were
888 uncorrectable by being out of range.
891 sqCoeff = kwargs[
'coeffs'][0]
893 ampArr = image.getArray()
894 ampArr *= (1 + sqCoeff*ampArr)
901 """Correct non-linearity with a spline model.
903 corrImage = uncorrImage - Spline(coeffs, uncorrImage)
908 The spline fit calculates a correction as a function of the
909 expected linear flux term. Because of this, the correction needs
910 to be subtracted from the observed flux.
913 LinearityType =
"Spline"
916 """Correct for non-linearity.
920 image : `lsst.afw.image.Image`
921 Image to be corrected
923 Dictionary of parameter keywords:
926 Coefficient vector (`list` or `np.ndarray`).
928 Logger to handle messages (`logging.Logger`).
934 output : `tuple` [`bool`, `int`]
935 If true, a correction was applied successfully. The
936 integer indicates the number of pixels that were
937 uncorrectable by being out of range.
939 splineCoeff = kwargs[
'coeffs']
940 gain = kwargs.get(
'gain', 1.0)
941 centers, values = np.split(splineCoeff, 2)
944 centers = gain * centers
945 values = gain * values
952 if centers[0] != 0.0:
953 centers = np.concatenate(([0.0], centers))
954 values = np.concatenate(([0.0], values))
958 if np.any(~np.isfinite(centers))
or np.any(~np.isfinite(values)):
964 interp = Akima1DInterpolator(centers, values, method=
"akima")
966 ampArr -= interp(np.clip(ampArr, centers[0], centers[-1] - 0.1))
972 """Correct non-linearity with a spline model.
974 corrImage1 = uncorrImage - Spline1(coeffs, uncorrImage)
975 corrImage = corrImage1 - Spline2(coeffs, corrImage1)
980 The spline fit calculates a correction as a function of the
981 expected linear flux term. Because of this, the correction needs
982 to be subtracted from the observed flux.
985 LinearityType =
"DoubleSpline"
988 """Correct for non-linearity.
992 image : `lsst.afw.image.Image`
993 Image to be corrected
995 Dictionary of parameter keywords:
998 Coefficient vector (`list` or `np.ndarray`).
1000 Logger to handle messages (`logging.Logger`).
1002 Gain value to apply.
1006 output : `tuple` [`bool`, `int`]
1007 If true, a correction was applied successfully. The
1008 integer indicates the number of pixels that were
1009 uncorrectable by being out of range.
1011 coeffs = kwargs[
'coeffs']
1012 gain = kwargs.get(
'gain', 1.0)
1016 nNodes1 = int(coeffs[0])
1017 nNodes2 = int(coeffs[1])
1019 ampArr = image.array
1022 splineCoeff1 = coeffs[2: 2 + 2*nNodes1]
1023 centers1, values1 = np.split(splineCoeff1, 2)
1025 if np.any(~np.isfinite(centers1))
or np.any(~np.isfinite(values1)):
1031 centers1 = centers1 * gain
1032 values1 = values1 * gain
1034 splineCoeff2 = coeffs[2 + 2*nNodes1: 2 + 2*nNodes1 + 2*nNodes2]
1035 centers2, values2 = np.split(splineCoeff2, 2)
1037 if np.any(~np.isfinite(centers2))
or np.any(~np.isfinite(values2)):
1043 centers2 = centers2 * gain
1044 values2 = values2 * gain
1049 interp1 = Akima1DInterpolator(centers1, values1, method=
"akima")
1051 ampArr -= interp1(np.clip(ampArr, centers1[0], centers1[-1] - 0.01))
1053 interp2 = Akima1DInterpolator(centers2, values2, method=
"akima")
1055 ampArr -= interp2(np.clip(ampArr, centers2[0], centers2[-1] - 0.01))
1061 """Do not correct non-linearity.
1063 LinearityType =
"Proportional"
1066 """Do not correct for non-linearity.
1070 image : `lsst.afw.image.Image`
1071 Image to be corrected
1073 Dictionary of parameter keywords:
1076 Coefficient vector (`list` or `np.ndarray`).
1078 Logger to handle messages (`logging.Logger`).
1082 output : `tuple` [`bool`, `int`]
1083 If true, a correction was applied successfully. The
1084 integer indicates the number of pixels that were
1085 uncorrectable by being out of range.
1091 """Do not correct non-linearity.
1093 LinearityType =
"None"
1096 """Do not correct for non-linearity.
1100 image : `lsst.afw.image.Image`
1101 Image to be corrected
1103 Dictionary of parameter keywords:
1106 Coefficient vector (`list` or `np.ndarray`).
1108 Logger to handle messages (`logging.Logger`).
1112 output : `tuple` [`bool`, `int`]
1113 If true, a correction was applied successfully. The
1114 integer indicates the number of pixels that were
1115 uncorrectable by being out of range.
validate(self, other=None)
fromDetector(self, detector)
updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
__call__(self, image, **kwargs)
str absoluteReferenceAmplifier
getLinearityTypeByName(self, linearityTypeName)
fromDetector(self, detector)
fromTable(cls, tableList)
__init__(self, table=None, **kwargs)
updateMetadata(self, setDate=False, **kwargs)
fromDict(cls, dictionary)
applyLinearity(self, image, detector=None, log=None, gains=None)
validate(self, detector=None, amplifier=None)
isTrimmedImage(image, detector)