lsst.ip.isr g9cb75138f3+6db9579539
linearize.py
Go to the documentation of this file.
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
23import numpy as np
24
25from astropy.table import Table
26
27import lsst.afw.math as afwMath
28from lsst.pipe.base import Struct
29from lsst.geom import Box2I, Point2I, Extent2I
30from .applyLookupTable import applyLookupTable
31from .calibType import IsrCalib
32
33__all__ = ["Linearizer",
34 "LinearizeBase", "LinearizeLookupTable", "LinearizeSquared",
35 "LinearizeProportional", "LinearizePolynomial", "LinearizeSpline", "LinearizeNone"]
36
37
39 """Parameter set for linearization.
40
41 These parameters are included in cameraGeom.Amplifier, but
42 should be accessible externally to allow for testing.
43
44 Parameters
45 ----------
46 table : `numpy.array`, optional
47 Lookup table; a 2-dimensional array of floats:
48 - one row for each row index (value of coef[0] in the amplifier)
49 - one column for each image value
50 To avoid copying the table the last index should vary fastest
51 (numpy default "C" order)
52 detector : `lsst.afw.cameraGeom.Detector`, optional
53 Detector object. Passed to self.fromDetectorfromDetectorfromDetector() on init.
54 log : `logging.Logger`, optional
55 Logger to handle messages.
56 kwargs : `dict`, optional
57 Other keyword arguments to pass to the parent init.
58
59 Raises
60 ------
61 RuntimeError :
62 Raised if the supplied table is not 2D, or if the table has fewer
63 columns than rows (indicating that the indices are swapped).
64
65 Notes
66 -----
67 The linearizer attributes stored are:
68
69 hasLinearity : `bool`
70 Whether a linearity correction is defined for this detector.
71 override : `bool`
72 Whether the detector parameters should be overridden.
73 ampNames : `list` [`str`]
74 List of amplifier names to correct.
75 linearityCoeffs : `dict` [`str`, `numpy.array`]
76 Coefficients to use in correction. Indexed by amplifier
77 names. The format of the array depends on the type of
78 correction to apply.
79 linearityType : `dict` [`str`, `str`]
80 Type of correction to use, indexed by amplifier names.
81 linearityBBox : `dict` [`str`, `lsst.geom.Box2I`]
82 Bounding box the correction is valid over, indexed by
83 amplifier names.
84 fitParams : `dict` [`str`, `numpy.array`], optional
85 Linearity fit parameters used to construct the correction
86 coefficients, indexed as above.
87 fitParamsErr : `dict` [`str`, `numpy.array`], optional
88 Uncertainty values of the linearity fit parameters used to
89 construct the correction coefficients, indexed as above.
90 fitChiSq : `dict` [`str`, `float`], optional
91 Chi-squared value of the linearity fit, indexed as above.
92 tableData : `numpy.array`, optional
93 Lookup table data for the linearity correction.
94 """
95 _OBSTYPE = "LINEARIZER"
96 _SCHEMA = 'Gen3 Linearizer'
97 _VERSION = 1.1
98
99 def __init__(self, table=None, **kwargs):
100 self.hasLinearityhasLinearity = False
101 self.overrideoverride = False
102
103 self.ampNamesampNames = list()
104 self.linearityCoeffslinearityCoeffs = dict()
105 self.linearityTypelinearityType = dict()
106 self.linearityBBoxlinearityBBox = dict()
107
108 self.fitParamsfitParams = dict()
109 self.fitParamsErrfitParamsErr = dict()
110 self.fitChiSqfitChiSq = dict()
111
112 self.tableDatatableData = None
113 if table is not None:
114 if len(table.shape) != 2:
115 raise RuntimeError("table shape = %s; must have two dimensions" % (table.shape,))
116 if table.shape[1] < table.shape[0]:
117 raise RuntimeError("table shape = %s; indices are switched" % (table.shape,))
118 self.tableDatatableData = np.array(table, order="C")
119
120 super().__init__(**kwargs)
121 self.requiredAttributesrequiredAttributesrequiredAttributesrequiredAttributes.update(['hasLinearity', 'override',
122 'ampNames',
123 'linearityCoeffs', 'linearityType', 'linearityBBox',
124 'fitParams', 'fitParamsErr', 'fitChiSq',
125 'tableData'])
126
127 def updateMetadata(self, setDate=False, **kwargs):
128 """Update metadata keywords with new values.
129
130 This calls the base class's method after ensuring the required
131 calibration keywords will be saved.
132
133 Parameters
134 ----------
135 setDate : `bool`, optional
136 Update the CALIBDATE fields in the metadata to the current
137 time. Defaults to False.
138 kwargs :
139 Other keyword parameters to set in the metadata.
140 """
141 kwargs['HAS_LINEARITY'] = self.hasLinearityhasLinearity
142 kwargs['OVERRIDE'] = self.overrideoverride
143 kwargs['HAS_TABLE'] = self.tableDatatableData is not None
144
145 super().updateMetadata(setDate=setDate, **kwargs)
146
147 def fromDetector(self, detector):
148 """Read linearity parameters from a detector.
149
150 Parameters
151 ----------
152 detector : `lsst.afw.cameraGeom.detector`
153 Input detector with parameters to use.
154
155 Returns
156 -------
157 calib : `lsst.ip.isr.Linearizer`
158 The calibration constructed from the detector.
159 """
160 self._detectorName_detectorName_detectorName = detector.getName()
161 self._detectorSerial_detectorSerial_detectorSerial = detector.getSerial()
162 self._detectorId_detectorId_detectorId = detector.getId()
163 self.hasLinearityhasLinearity = True
164
165 # Do not translate Threshold, Maximum, Units.
166 for amp in detector.getAmplifiers():
167 ampName = amp.getName()
168 self.ampNamesampNames.append(ampName)
169 self.linearityTypelinearityType[ampName] = amp.getLinearityType()
170 self.linearityCoeffslinearityCoeffs[ampName] = amp.getLinearityCoeffs()
171 self.linearityBBoxlinearityBBox[ampName] = amp.getBBox()
172
173 return self
174
175 @classmethod
176 def fromDict(cls, dictionary):
177 """Construct a calibration from a dictionary of properties
178
179 Parameters
180 ----------
181 dictionary : `dict`
182 Dictionary of properties
183
184 Returns
185 -------
186 calib : `lsst.ip.isr.Linearity`
187 Constructed calibration.
188
189 Raises
190 ------
191 RuntimeError
192 Raised if the supplied dictionary is for a different
193 calibration.
194 """
195
196 calib = cls()
197
198 if calib._OBSTYPE != dictionary['metadata']['OBSTYPE']:
199 raise RuntimeError(f"Incorrect linearity supplied. Expected {calib._OBSTYPE}, "
200 f"found {dictionary['metadata']['OBSTYPE']}")
201
202 calib.setMetadata(dictionary['metadata'])
203
204 calib.hasLinearity = dictionary.get('hasLinearity',
205 dictionary['metadata'].get('HAS_LINEARITY', False))
206 calib.override = dictionary.get('override', True)
207
208 if calib.hasLinearity:
209 for ampName in dictionary['amplifiers']:
210 amp = dictionary['amplifiers'][ampName]
211 calib.ampNames.append(ampName)
212 calib.linearityCoeffs[ampName] = np.array(amp.get('linearityCoeffs', [0.0]))
213 calib.linearityType[ampName] = amp.get('linearityType', 'None')
214 calib.linearityBBox[ampName] = amp.get('linearityBBox', None)
215
216 calib.fitParams[ampName] = np.array(amp.get('fitParams', [0.0]))
217 calib.fitParamsErr[ampName] = np.array(amp.get('fitParamsErr', [0.0]))
218 calib.fitChiSq[ampName] = amp.get('fitChiSq', np.nan)
219
220 calib.tableData = dictionary.get('tableData', None)
221 if calib.tableData:
222 calib.tableData = np.array(calib.tableData)
223
224 return calib
225
226 def toDict(self):
227 """Return linearity parameters as a dict.
228
229 Returns
230 -------
231 outDict : `dict`:
232 """
233 self.updateMetadataupdateMetadataupdateMetadata()
234
235 outDict = {'metadata': self.getMetadatagetMetadata(),
236 'detectorName': self._detectorName_detectorName_detectorName,
237 'detectorSerial': self._detectorSerial_detectorSerial_detectorSerial,
238 'detectorId': self._detectorId_detectorId_detectorId,
239 'hasTable': self.tableDatatableData is not None,
240 'amplifiers': dict(),
241 }
242 for ampName in self.linearityTypelinearityType:
243 outDict['amplifiers'][ampName] = {'linearityType': self.linearityTypelinearityType[ampName],
244 'linearityCoeffs': self.linearityCoeffslinearityCoeffs[ampName].tolist(),
245 'linearityBBox': self.linearityBBoxlinearityBBox[ampName],
246 'fitParams': self.fitParamsfitParams[ampName].tolist(),
247 'fitParamsErr': self.fitParamsErrfitParamsErr[ampName].tolist(),
248 'fitChiSq': self.fitChiSqfitChiSq[ampName]}
249 if self.tableDatatableData is not None:
250 outDict['tableData'] = self.tableDatatableData.tolist()
251
252 return outDict
253
254 @classmethod
255 def fromTable(cls, tableList):
256 """Read linearity from a FITS file.
257
258 This method uses the `fromDict` method to create the
259 calibration, after constructing an appropriate dictionary from
260 the input tables.
261
262 Parameters
263 ----------
264 tableList : `list` [`astropy.table.Table`]
265 afwTable read from input file name.
266
267 Returns
268 -------
270 Linearity parameters.
271
272 Notes
273 -----
274 The method reads a FITS file with 1 or 2 extensions. The metadata is
275 read from the header of extension 1, which must exist. Then the table
276 is loaded, and the ['AMPLIFIER_NAME', 'TYPE', 'COEFFS', 'BBOX_X0',
277 'BBOX_Y0', 'BBOX_DX', 'BBOX_DY'] columns are read and used to set each
278 dictionary by looping over rows.
279 Extension 2 is then attempted to read in the try block (which only
280 exists for lookup tables). It has a column named 'LOOKUP_VALUES' that
281 contains a vector of the lookup entries in each row.
282 """
283 coeffTable = tableList[0]
284
285 metadata = coeffTable.meta
286 inDict = dict()
287 inDict['metadata'] = metadata
288 inDict['hasLinearity'] = metadata.get('HAS_LINEARITY', False)
289 inDict['amplifiers'] = dict()
290
291 for record in coeffTable:
292 ampName = record['AMPLIFIER_NAME']
293
294 fitParams = record['FIT_PARAMS'] if 'FIT_PARAMS' in record.columns else np.array([0.0])
295 fitParamsErr = record['FIT_PARAMS_ERR'] if 'FIT_PARAMS_ERR' in record.columns else np.array([0.0])
296 fitChiSq = record['RED_CHI_SQ'] if 'RED_CHI_SQ' in record.columns else np.nan
297
298 inDict['amplifiers'][ampName] = {
299 'linearityType': record['TYPE'],
300 'linearityCoeffs': record['COEFFS'],
301 'linearityBBox': Box2I(Point2I(record['BBOX_X0'], record['BBOX_Y0']),
302 Extent2I(record['BBOX_DX'], record['BBOX_DY'])),
303 'fitParams': fitParams,
304 'fitParamsErr': fitParamsErr,
305 'fitChiSq': fitChiSq,
306 }
307
308 if len(tableList) > 1:
309 tableData = tableList[1]
310 inDict['tableData'] = [record['LOOKUP_VALUES'] for record in tableData]
311
312 return cls().fromDict(inDict)
313
314 def toTable(self):
315 """Construct a list of tables containing the information in this
316 calibration.
317
318 The list of tables should create an identical calibration
319 after being passed to this class's fromTable method.
320
321 Returns
322 -------
323 tableList : `list` [`astropy.table.Table`]
324 List of tables containing the linearity calibration
325 information.
326 """
327
328 tableList = []
329 self.updateMetadataupdateMetadataupdateMetadata()
330 catalog = Table([{'AMPLIFIER_NAME': ampName,
331 'TYPE': self.linearityTypelinearityType[ampName],
332 'COEFFS': self.linearityCoeffslinearityCoeffs[ampName],
333 'BBOX_X0': self.linearityBBoxlinearityBBox[ampName].getMinX(),
334 'BBOX_Y0': self.linearityBBoxlinearityBBox[ampName].getMinY(),
335 'BBOX_DX': self.linearityBBoxlinearityBBox[ampName].getWidth(),
336 'BBOX_DY': self.linearityBBoxlinearityBBox[ampName].getHeight(),
337 'FIT_PARAMS': self.fitParamsfitParams[ampName],
338 'FIT_PARAMS_ERR': self.fitParamsErrfitParamsErr[ampName],
339 'RED_CHI_SQ': self.fitChiSqfitChiSq[ampName],
340 } for ampName in self.ampNamesampNames])
341 catalog.meta = self.getMetadatagetMetadata().toDict()
342 tableList.append(catalog)
343
344 if self.tableDatatableData is not None:
345 catalog = Table([{'LOOKUP_VALUES': value} for value in self.tableDatatableData])
346 tableList.append(catalog)
347 return(tableList)
348
349 def getLinearityTypeByName(self, linearityTypeName):
350 """Determine the linearity class to use from the type name.
351
352 Parameters
353 ----------
354 linearityTypeName : str
355 String name of the linearity type that is needed.
356
357 Returns
358 -------
359 linearityType : `~lsst.ip.isr.linearize.LinearizeBase`
360 The appropriate linearity class to use. If no matching class
361 is found, `None` is returned.
362 """
363 for t in [LinearizeLookupTable,
364 LinearizeSquared,
365 LinearizePolynomial,
366 LinearizeProportional,
367 LinearizeSpline,
368 LinearizeNone]:
369 if t.LinearityType == linearityTypeName:
370 return t
371 return None
372
373 def validate(self, detector=None, amplifier=None):
374 """Validate linearity for a detector/amplifier.
375
376 Parameters
377 ----------
378 detector : `lsst.afw.cameraGeom.Detector`, optional
379 Detector to validate, along with its amplifiers.
380 amplifier : `lsst.afw.cameraGeom.Amplifier`, optional
381 Single amplifier to validate.
382
383 Raises
384 ------
385 RuntimeError :
386 Raised if there is a mismatch in linearity parameters, and
387 the cameraGeom parameters are not being overridden.
388 """
389 amplifiersToCheck = []
390 if detector:
391 if self._detectorName_detectorName_detectorName != detector.getName():
392 raise RuntimeError("Detector names don't match: %s != %s" %
393 (self._detectorName_detectorName_detectorName, detector.getName()))
394 if int(self._detectorId_detectorId_detectorId) != int(detector.getId()):
395 raise RuntimeError("Detector IDs don't match: %s != %s" %
396 (int(self._detectorId_detectorId_detectorId), int(detector.getId())))
397 if self._detectorSerial_detectorSerial_detectorSerial != detector.getSerial():
398 raise RuntimeError("Detector serial numbers don't match: %s != %s" %
399 (self._detectorSerial_detectorSerial_detectorSerial, detector.getSerial()))
400 if len(detector.getAmplifiers()) != len(self.linearityCoeffslinearityCoeffs.keys()):
401 raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
402 (len(detector.getAmplifiers()),
403 len(self.linearityCoeffslinearityCoeffs.keys())))
404 amplifiersToCheck.extend(detector.getAmplifiers())
405
406 if amplifier:
407 amplifiersToCheck.extend(amplifier)
408
409 for amp in amplifiersToCheck:
410 ampName = amp.getName()
411 if ampName not in self.linearityCoeffslinearityCoeffs.keys():
412 raise RuntimeError("Amplifier %s is not in linearity data" %
413 (ampName, ))
414 if amp.getLinearityType() != self.linearityTypelinearityType[ampName]:
415 if self.overrideoverride:
416 self.loglog.warning("Overriding amplifier defined linearityType (%s) for %s",
417 self.linearityTypelinearityType[ampName], ampName)
418 else:
419 raise RuntimeError("Amplifier %s type %s does not match saved value %s" %
420 (ampName, amp.getLinearityType(), self.linearityTypelinearityType[ampName]))
421 if (amp.getLinearityCoeffs().shape != self.linearityCoeffslinearityCoeffs[ampName].shape or not
422 np.allclose(amp.getLinearityCoeffs(), self.linearityCoeffslinearityCoeffs[ampName], equal_nan=True)):
423 if self.overrideoverride:
424 self.loglog.warning("Overriding amplifier defined linearityCoeffs (%s) for %s",
425 self.linearityCoeffslinearityCoeffs[ampName], ampName)
426 else:
427 raise RuntimeError("Amplifier %s coeffs %s does not match saved value %s" %
428 (ampName, amp.getLinearityCoeffs(), self.linearityCoeffslinearityCoeffs[ampName]))
429
430 def applyLinearity(self, image, detector=None, log=None):
431 """Apply the linearity to an image.
432
433 If the linearity parameters are populated, use those,
434 otherwise use the values from the detector.
435
436 Parameters
437 ----------
438 image : `~lsst.afw.image.image`
439 Image to correct.
440 detector : `~lsst.afw.cameraGeom.detector`
441 Detector to use for linearity parameters if not already
442 populated.
443 log : `~logging.Logger`, optional
444 Log object to use for logging.
445 """
446 if log is None:
447 log = self.loglog
448 if detector and not self.hasLinearityhasLinearity:
449 self.fromDetectorfromDetectorfromDetector(detector)
450
451 self.validatevalidatevalidate(detector)
452
453 numAmps = 0
454 numLinearized = 0
455 numOutOfRange = 0
456 for ampName in self.linearityTypelinearityType.keys():
457 linearizer = self.getLinearityTypeByNamegetLinearityTypeByName(self.linearityTypelinearityType[ampName])
458 numAmps += 1
459 if linearizer is not None:
460 ampView = image.Factory(image, self.linearityBBoxlinearityBBox[ampName])
461 success, outOfRange = linearizer()(ampView, **{'coeffs': self.linearityCoeffslinearityCoeffs[ampName],
462 'table': self.tableDatatableData,
463 'log': self.loglog})
464 numOutOfRange += outOfRange
465 if success:
466 numLinearized += 1
467 elif log is not None:
468 log.warning("Amplifier %s did not linearize.",
469 ampName)
470 return Struct(
471 numAmps=numAmps,
472 numLinearized=numLinearized,
473 numOutOfRange=numOutOfRange
474 )
475
476
477class LinearizeBase(metaclass=abc.ABCMeta):
478 """Abstract base class functor for correcting non-linearity.
479
480 Subclasses must define __call__ and set class variable
481 LinearityType to a string that will be used for linearity type in
482 the cameraGeom.Amplifier.linearityType field.
483
484 All linearity corrections should be defined in terms of an
485 additive correction, such that:
486
487 corrected_value = uncorrected_value + f(uncorrected_value)
488 """
489 LinearityType = None # linearity type, a string used for AmpInfoCatalogs
490
491 @abc.abstractmethod
492 def __call__(self, image, **kwargs):
493 """Correct non-linearity.
494
495 Parameters
496 ----------
497 image : `lsst.afw.image.Image`
498 Image to be corrected
499 kwargs : `dict`
500 Dictionary of parameter keywords:
501 ``"coeffs"``
502 Coefficient vector (`list` or `numpy.array`).
503 ``"table"``
504 Lookup table data (`numpy.array`).
505 ``"log"``
506 Logger to handle messages (`logging.Logger`).
507
508 Returns
509 -------
510 output : `bool`
511 If true, a correction was applied successfully.
512
513 Raises
514 ------
515 RuntimeError:
516 Raised if the linearity type listed in the
517 detector does not match the class type.
518 """
519 pass
520
521
523 """Correct non-linearity with a persisted lookup table.
524
525 The lookup table consists of entries such that given
526 "coefficients" c0, c1:
527
528 for each i,j of image:
529 rowInd = int(c0)
530 colInd = int(c1 + uncorrImage[i,j])
531 corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
532
533 - c0: row index; used to identify which row of the table to use
534 (typically one per amplifier, though one can have multiple
535 amplifiers use the same table)
536 - c1: column index offset; added to the uncorrected image value
537 before truncation; this supports tables that can handle
538 negative image values; also, if the c1 ends with .5 then
539 the nearest index is used instead of truncating to the
540 next smaller index
541 """
542 LinearityType = "LookupTable"
543
544 def __call__(self, image, **kwargs):
545 """Correct for non-linearity.
546
547 Parameters
548 ----------
549 image : `lsst.afw.image.Image`
550 Image to be corrected
551 kwargs : `dict`
552 Dictionary of parameter keywords:
553 ``"coeffs"``
554 Columnation vector (`list` or `numpy.array`).
555 ``"table"``
556 Lookup table data (`numpy.array`).
557 ``"log"``
558 Logger to handle messages (`logging.Logger`).
559
560 Returns
561 -------
562 output : `tuple` [`bool`, `int`]
563 If true, a correction was applied successfully. The
564 integer indicates the number of pixels that were
565 uncorrectable by being out of range.
566
567 Raises
568 ------
569 RuntimeError:
570 Raised if the requested row index is out of the table
571 bounds.
572 """
573 numOutOfRange = 0
574
575 rowInd, colIndOffset = kwargs['coeffs'][0:2]
576 table = kwargs['table']
577 log = kwargs['log']
578
579 numTableRows = table.shape[0]
580 rowInd = int(rowInd)
581 if rowInd < 0 or rowInd > numTableRows:
582 raise RuntimeError("LinearizeLookupTable rowInd=%s not in range[0, %s)" %
583 (rowInd, numTableRows))
584 tableRow = np.array(table[rowInd, :], dtype=image.getArray().dtype)
585
586 numOutOfRange += applyLookupTable(image, tableRow, colIndOffset)
587
588 if numOutOfRange > 0 and log is not None:
589 log.warning("%s pixels were out of range of the linearization table",
590 numOutOfRange)
591 if numOutOfRange < image.getArray().size:
592 return True, numOutOfRange
593 else:
594 return False, numOutOfRange
595
596
598 """Correct non-linearity with a polynomial mode.
599
600 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
601
602 where c_i are the linearity coefficients for each amplifier.
603 Lower order coefficients are not included as they duplicate other
604 calibration parameters:
605 ``"k0"``
606 A coefficient multiplied by uncorrImage**0 is equivalent to
607 bias level. Irrelevant for correcting non-linearity.
608 ``"k1"``
609 A coefficient multiplied by uncorrImage**1 is proportional
610 to the gain. Not necessary for correcting non-linearity.
611 """
612 LinearityType = "Polynomial"
613
614 def __call__(self, image, **kwargs):
615 """Correct non-linearity.
616
617 Parameters
618 ----------
619 image : `lsst.afw.image.Image`
620 Image to be corrected
621 kwargs : `dict`
622 Dictionary of parameter keywords:
623 ``"coeffs"``
624 Coefficient vector (`list` or `numpy.array`).
625 If the order of the polynomial is n, this list
626 should have a length of n-1 ("k0" and "k1" are
627 not needed for the correction).
628 ``"log"``
629 Logger to handle messages (`logging.Logger`).
630
631 Returns
632 -------
633 output : `tuple` [`bool`, `int`]
634 If true, a correction was applied successfully. The
635 integer indicates the number of pixels that were
636 uncorrectable by being out of range.
637 """
638 if not np.any(np.isfinite(kwargs['coeffs'])):
639 return False, 0
640 if not np.any(kwargs['coeffs']):
641 return False, 0
642
643 ampArray = image.getArray()
644 correction = np.zeros_like(ampArray)
645 for order, coeff in enumerate(kwargs['coeffs'], start=2):
646 correction += coeff * np.power(ampArray, order)
647 ampArray += correction
648
649 return True, 0
650
651
653 """Correct non-linearity with a squared model.
654
655 corrImage = uncorrImage + c0*uncorrImage^2
656
657 where c0 is linearity coefficient 0 for each amplifier.
658 """
659 LinearityType = "Squared"
660
661 def __call__(self, image, **kwargs):
662 """Correct for non-linearity.
663
664 Parameters
665 ----------
666 image : `lsst.afw.image.Image`
667 Image to be corrected
668 kwargs : `dict`
669 Dictionary of parameter keywords:
670 ``"coeffs"``
671 Coefficient vector (`list` or `numpy.array`).
672 ``"log"``
673 Logger to handle messages (`logging.Logger`).
674
675 Returns
676 -------
677 output : `tuple` [`bool`, `int`]
678 If true, a correction was applied successfully. The
679 integer indicates the number of pixels that were
680 uncorrectable by being out of range.
681 """
682
683 sqCoeff = kwargs['coeffs'][0]
684 if sqCoeff != 0:
685 ampArr = image.getArray()
686 ampArr *= (1 + sqCoeff*ampArr)
687 return True, 0
688 else:
689 return False, 0
690
691
693 """Correct non-linearity with a spline model.
694
695 corrImage = uncorrImage - Spline(coeffs, uncorrImage)
696
697 Notes
698 -----
699
700 The spline fit calculates a correction as a function of the
701 expected linear flux term. Because of this, the correction needs
702 to be subtracted from the observed flux.
703
704 """
705 LinearityType = "Spline"
706
707 def __call__(self, image, **kwargs):
708 """Correct for non-linearity.
709
710 Parameters
711 ----------
712 image : `lsst.afw.image.Image`
713 Image to be corrected
714 kwargs : `dict`
715 Dictionary of parameter keywords:
716 ``"coeffs"``
717 Coefficient vector (`list` or `numpy.array`).
718 ``"log"``
719 Logger to handle messages (`logging.Logger`).
720
721 Returns
722 -------
723 output : `tuple` [`bool`, `int`]
724 If true, a correction was applied successfully. The
725 integer indicates the number of pixels that were
726 uncorrectable by being out of range.
727 """
728 splineCoeff = kwargs['coeffs']
729 centers, values = np.split(splineCoeff, 2)
730 interp = afwMath.makeInterpolate(centers.tolist(), values.tolist(),
731 afwMath.stringToInterpStyle("AKIMA_SPLINE"))
732
733 ampArr = image.getArray()
734 delta = interp.interpolate(ampArr.flatten())
735 ampArr -= np.array(delta).reshape(ampArr.shape)
736
737 return True, 0
738
739
741 """Do not correct non-linearity.
742 """
743 LinearityType = "Proportional"
744
745 def __call__(self, image, **kwargs):
746 """Do not correct for non-linearity.
747
748 Parameters
749 ----------
750 image : `lsst.afw.image.Image`
751 Image to be corrected
752 kwargs : `dict`
753 Dictionary of parameter keywords:
754 ``"coeffs"``
755 Coefficient vector (`list` or `numpy.array`).
756 ``"log"``
757 Logger to handle messages (`logging.Logger`).
758
759 Returns
760 -------
761 output : `tuple` [`bool`, `int`]
762 If true, a correction was applied successfully. The
763 integer indicates the number of pixels that were
764 uncorrectable by being out of range.
765 """
766 return True, 0
767
768
770 """Do not correct non-linearity.
771 """
772 LinearityType = "None"
773
774 def __call__(self, image, **kwargs):
775 """Do not correct for non-linearity.
776
777 Parameters
778 ----------
779 image : `lsst.afw.image.Image`
780 Image to be corrected
781 kwargs : `dict`
782 Dictionary of parameter keywords:
783 ``"coeffs"``
784 Coefficient vector (`list` or `numpy.array`).
785 ``"log"``
786 Logger to handle messages (`logging.Logger`).
787
788 Returns
789 -------
790 output : `tuple` [`bool`, `int`]
791 If true, a correction was applied successfully. The
792 integer indicates the number of pixels that were
793 uncorrectable by being out of range.
794 """
795 return True, 0
def validate(self, other=None)
Definition: calibType.py:598
def requiredAttributes(self, value)
Definition: calibType.py:142
def updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
Definition: calibType.py:181
def fromDetector(self, detector)
Definition: calibType.py:496
def __call__(self, image, **kwargs)
Definition: linearize.py:492
def __call__(self, image, **kwargs)
Definition: linearize.py:544
def __call__(self, image, **kwargs)
Definition: linearize.py:774
def __call__(self, image, **kwargs)
Definition: linearize.py:614
def __call__(self, image, **kwargs)
Definition: linearize.py:745
def __call__(self, image, **kwargs)
Definition: linearize.py:707
def __call__(self, image, **kwargs)
Definition: linearize.py:661
def getLinearityTypeByName(self, linearityTypeName)
Definition: linearize.py:349
def validate(self, detector=None, amplifier=None)
Definition: linearize.py:373
def applyLinearity(self, image, detector=None, log=None)
Definition: linearize.py:430
def fromTable(cls, tableList)
Definition: linearize.py:255
def fromDetector(self, detector)
Definition: linearize.py:147
def updateMetadata(self, setDate=False, **kwargs)
Definition: linearize.py:127
def __init__(self, table=None, **kwargs)
Definition: linearize.py:99
def fromDict(cls, dictionary)
Definition: linearize.py:176