lsst.ip.isr 22.0.1-29-g4a3d635+366db6d849
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 read from the header of
275 extension 1, which must exist. Then the table is loaded, and the ['AMPLIFIER_NAME', 'TYPE',
276 'COEFFS', 'BBOX_X0', 'BBOX_Y0', 'BBOX_DX', 'BBOX_DY'] columns are read and used to
277 set each dictionary by looping over rows.
278 Eextension 2 is then attempted to read in the try block (which only exists for lookup tables).
279 It has a column named 'LOOKUP_VALUES' that contains a vector of the lookup entries in each row.
280
281 """
282 coeffTable = tableList[0]
283
284 metadata = coeffTable.meta
285 inDict = dict()
286 inDict['metadata'] = metadata
287 inDict['hasLinearity'] = metadata.get('HAS_LINEARITY', False)
288 inDict['amplifiers'] = dict()
289
290 for record in coeffTable:
291 ampName = record['AMPLIFIER_NAME']
292
293 fitParams = record['FIT_PARAMS'] if 'FIT_PARAMS' in record.columns else np.array([0.0])
294 fitParamsErr = record['FIT_PARAMS_ERR'] if 'FIT_PARAMS_ERR' in record.columns else np.array([0.0])
295 fitChiSq = record['RED_CHI_SQ'] if 'RED_CHI_SQ' in record.columns else np.nan
296
297 inDict['amplifiers'][ampName] = {
298 'linearityType': record['TYPE'],
299 'linearityCoeffs': record['COEFFS'],
300 'linearityBBox': Box2I(Point2I(record['BBOX_X0'], record['BBOX_Y0']),
301 Extent2I(record['BBOX_DX'], record['BBOX_DY'])),
302 'fitParams': fitParams,
303 'fitParamsErr': fitParamsErr,
304 'fitChiSq': fitChiSq,
305 }
306
307 if len(tableList) > 1:
308 tableData = tableList[1]
309 inDict['tableData'] = [record['LOOKUP_VALUES'] for record in tableData]
310
311 return cls().fromDict(inDict)
312
313 def toTable(self):
314 """Construct a list of tables containing the information in this calibration
315
316 The list of tables should create an identical calibration
317 after being passed to this class's fromTable method.
318
319 Returns
320 -------
321 tableList : `list` [`astropy.table.Table`]
322 List of tables containing the linearity calibration
323 information.
324 """
325
326 tableList = []
327 self.updateMetadataupdateMetadataupdateMetadata()
328 catalog = Table([{'AMPLIFIER_NAME': ampName,
329 'TYPE': self.linearityTypelinearityType[ampName],
330 'COEFFS': self.linearityCoeffslinearityCoeffs[ampName],
331 'BBOX_X0': self.linearityBBoxlinearityBBox[ampName].getMinX(),
332 'BBOX_Y0': self.linearityBBoxlinearityBBox[ampName].getMinY(),
333 'BBOX_DX': self.linearityBBoxlinearityBBox[ampName].getWidth(),
334 'BBOX_DY': self.linearityBBoxlinearityBBox[ampName].getHeight(),
335 'FIT_PARAMS': self.fitParamsfitParams[ampName],
336 'FIT_PARAMS_ERR': self.fitParamsErrfitParamsErr[ampName],
337 'RED_CHI_SQ': self.fitChiSqfitChiSq[ampName],
338 } for ampName in self.ampNamesampNames])
339 catalog.meta = self.getMetadatagetMetadata().toDict()
340 tableList.append(catalog)
341
342 if self.tableDatatableData is not None:
343 catalog = Table([{'LOOKUP_VALUES': value} for value in self.tableDatatableData])
344 tableList.append(catalog)
345 return(tableList)
346
347 def getLinearityTypeByName(self, linearityTypeName):
348 """Determine the linearity class to use from the type name.
349
350 Parameters
351 ----------
352 linearityTypeName : str
353 String name of the linearity type that is needed.
354
355 Returns
356 -------
357 linearityType : `~lsst.ip.isr.linearize.LinearizeBase`
358 The appropriate linearity class to use. If no matching class
359 is found, `None` is returned.
360 """
361 for t in [LinearizeLookupTable,
362 LinearizeSquared,
363 LinearizePolynomial,
364 LinearizeProportional,
365 LinearizeSpline,
366 LinearizeNone]:
367 if t.LinearityType == linearityTypeName:
368 return t
369 return None
370
371 def validate(self, detector=None, amplifier=None):
372 """Validate linearity for a detector/amplifier.
373
374 Parameters
375 ----------
376 detector : `lsst.afw.cameraGeom.Detector`, optional
377 Detector to validate, along with its amplifiers.
378 amplifier : `lsst.afw.cameraGeom.Amplifier`, optional
379 Single amplifier to validate.
380
381 Raises
382 ------
383 RuntimeError :
384 Raised if there is a mismatch in linearity parameters, and
385 the cameraGeom parameters are not being overridden.
386 """
387 amplifiersToCheck = []
388 if detector:
389 if self._detectorName_detectorName_detectorName != detector.getName():
390 raise RuntimeError("Detector names don't match: %s != %s" %
391 (self._detectorName_detectorName_detectorName, detector.getName()))
392 if int(self._detectorId_detectorId_detectorId) != int(detector.getId()):
393 raise RuntimeError("Detector IDs don't match: %s != %s" %
394 (int(self._detectorId_detectorId_detectorId), int(detector.getId())))
395 if self._detectorSerial_detectorSerial_detectorSerial != detector.getSerial():
396 raise RuntimeError("Detector serial numbers don't match: %s != %s" %
397 (self._detectorSerial_detectorSerial_detectorSerial, detector.getSerial()))
398 if len(detector.getAmplifiers()) != len(self.linearityCoeffslinearityCoeffs.keys()):
399 raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
400 (len(detector.getAmplifiers()),
401 len(self.linearityCoeffslinearityCoeffs.keys())))
402 amplifiersToCheck.extend(detector.getAmplifiers())
403
404 if amplifier:
405 amplifiersToCheck.extend(amplifier)
406
407 for amp in amplifiersToCheck:
408 ampName = amp.getName()
409 if ampName not in self.linearityCoeffslinearityCoeffs.keys():
410 raise RuntimeError("Amplifier %s is not in linearity data" %
411 (ampName, ))
412 if amp.getLinearityType() != self.linearityTypelinearityType[ampName]:
413 if self.overrideoverride:
414 self.loglog.warning("Overriding amplifier defined linearityType (%s) for %s",
415 self.linearityTypelinearityType[ampName], ampName)
416 else:
417 raise RuntimeError("Amplifier %s type %s does not match saved value %s" %
418 (ampName, amp.getLinearityType(), self.linearityTypelinearityType[ampName]))
419 if (amp.getLinearityCoeffs().shape != self.linearityCoeffslinearityCoeffs[ampName].shape or not
420 np.allclose(amp.getLinearityCoeffs(), self.linearityCoeffslinearityCoeffs[ampName], equal_nan=True)):
421 if self.overrideoverride:
422 self.loglog.warning("Overriding amplifier defined linearityCoeffs (%s) for %s",
423 self.linearityCoeffslinearityCoeffs[ampName], ampName)
424 else:
425 raise RuntimeError("Amplifier %s coeffs %s does not match saved value %s" %
426 (ampName, amp.getLinearityCoeffs(), self.linearityCoeffslinearityCoeffs[ampName]))
427
428 def applyLinearity(self, image, detector=None, log=None):
429 """Apply the linearity to an image.
430
431 If the linearity parameters are populated, use those,
432 otherwise use the values from the detector.
433
434 Parameters
435 ----------
436 image : `~lsst.afw.image.image`
437 Image to correct.
438 detector : `~lsst.afw.cameraGeom.detector`
439 Detector to use for linearity parameters if not already
440 populated.
441 log : `~logging.Logger`, optional
442 Log object to use for logging.
443 """
444 if log is None:
445 log = self.loglog
446 if detector and not self.hasLinearityhasLinearity:
447 self.fromDetectorfromDetectorfromDetector(detector)
448
449 self.validatevalidatevalidate(detector)
450
451 numAmps = 0
452 numLinearized = 0
453 numOutOfRange = 0
454 for ampName in self.linearityTypelinearityType.keys():
455 linearizer = self.getLinearityTypeByNamegetLinearityTypeByName(self.linearityTypelinearityType[ampName])
456 numAmps += 1
457 if linearizer is not None:
458 ampView = image.Factory(image, self.linearityBBoxlinearityBBox[ampName])
459 success, outOfRange = linearizer()(ampView, **{'coeffs': self.linearityCoeffslinearityCoeffs[ampName],
460 'table': self.tableDatatableData,
461 'log': self.loglog})
462 numOutOfRange += outOfRange
463 if success:
464 numLinearized += 1
465 elif log is not None:
466 log.warning("Amplifier %s did not linearize.",
467 ampName)
468 return Struct(
469 numAmps=numAmps,
470 numLinearized=numLinearized,
471 numOutOfRange=numOutOfRange
472 )
473
474
475class LinearizeBase(metaclass=abc.ABCMeta):
476 """Abstract base class functor for correcting non-linearity.
477
478 Subclasses must define __call__ and set class variable
479 LinearityType to a string that will be used for linearity type in
480 the cameraGeom.Amplifier.linearityType field.
481
482 All linearity corrections should be defined in terms of an
483 additive correction, such that:
484
485 corrected_value = uncorrected_value + f(uncorrected_value)
486 """
487 LinearityType = None # linearity type, a string used for AmpInfoCatalogs
488
489 @abc.abstractmethod
490 def __call__(self, image, **kwargs):
491 """Correct non-linearity.
492
493 Parameters
494 ----------
495 image : `lsst.afw.image.Image`
496 Image to be corrected
497 kwargs : `dict`
498 Dictionary of parameter keywords:
499 ``"coeffs"``
500 Coefficient vector (`list` or `numpy.array`).
501 ``"table"``
502 Lookup table data (`numpy.array`).
503 ``"log"``
504 Logger to handle messages (`logging.Logger`).
505
506 Returns
507 -------
508 output : `bool`
509 If true, a correction was applied successfully.
510
511 Raises
512 ------
513 RuntimeError:
514 Raised if the linearity type listed in the
515 detector does not match the class type.
516 """
517 pass
518
519
521 """Correct non-linearity with a persisted lookup table.
522
523 The lookup table consists of entries such that given
524 "coefficients" c0, c1:
525
526 for each i,j of image:
527 rowInd = int(c0)
528 colInd = int(c1 + uncorrImage[i,j])
529 corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
530
531 - c0: row index; used to identify which row of the table to use
532 (typically one per amplifier, though one can have multiple
533 amplifiers use the same table)
534 - c1: column index offset; added to the uncorrected image value
535 before truncation; this supports tables that can handle
536 negative image values; also, if the c1 ends with .5 then
537 the nearest index is used instead of truncating to the
538 next smaller index
539 """
540 LinearityType = "LookupTable"
541
542 def __call__(self, image, **kwargs):
543 """Correct for non-linearity.
544
545 Parameters
546 ----------
547 image : `lsst.afw.image.Image`
548 Image to be corrected
549 kwargs : `dict`
550 Dictionary of parameter keywords:
551 ``"coeffs"``
552 Columnation vector (`list` or `numpy.array`).
553 ``"table"``
554 Lookup table data (`numpy.array`).
555 ``"log"``
556 Logger to handle messages (`logging.Logger`).
557
558 Returns
559 -------
560 output : `tuple` [`bool`, `int`]
561 If true, a correction was applied successfully. The
562 integer indicates the number of pixels that were
563 uncorrectable by being out of range.
564
565 Raises
566 ------
567 RuntimeError:
568 Raised if the requested row index is out of the table
569 bounds.
570 """
571 numOutOfRange = 0
572
573 rowInd, colIndOffset = kwargs['coeffs'][0:2]
574 table = kwargs['table']
575 log = kwargs['log']
576
577 numTableRows = table.shape[0]
578 rowInd = int(rowInd)
579 if rowInd < 0 or rowInd > numTableRows:
580 raise RuntimeError("LinearizeLookupTable rowInd=%s not in range[0, %s)" %
581 (rowInd, numTableRows))
582 tableRow = np.array(table[rowInd, :], dtype=image.getArray().dtype)
583
584 numOutOfRange += applyLookupTable(image, tableRow, colIndOffset)
585
586 if numOutOfRange > 0 and log is not None:
587 log.warning("%s pixels were out of range of the linearization table",
588 numOutOfRange)
589 if numOutOfRange < image.getArray().size:
590 return True, numOutOfRange
591 else:
592 return False, numOutOfRange
593
594
596 """Correct non-linearity with a polynomial mode.
597
598 corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
599
600 where c_i are the linearity coefficients for each amplifier.
601 Lower order coefficients are not included as they duplicate other
602 calibration parameters:
603 ``"k0"``
604 A coefficient multiplied by uncorrImage**0 is equivalent to
605 bias level. Irrelevant for correcting non-linearity.
606 ``"k1"``
607 A coefficient multiplied by uncorrImage**1 is proportional
608 to the gain. Not necessary for correcting non-linearity.
609 """
610 LinearityType = "Polynomial"
611
612 def __call__(self, image, **kwargs):
613 """Correct non-linearity.
614
615 Parameters
616 ----------
617 image : `lsst.afw.image.Image`
618 Image to be corrected
619 kwargs : `dict`
620 Dictionary of parameter keywords:
621 ``"coeffs"``
622 Coefficient vector (`list` or `numpy.array`).
623 If the order of the polynomial is n, this list
624 should have a length of n-1 ("k0" and "k1" are
625 not needed for the correction).
626 ``"log"``
627 Logger to handle messages (`logging.Logger`).
628
629 Returns
630 -------
631 output : `tuple` [`bool`, `int`]
632 If true, a correction was applied successfully. The
633 integer indicates the number of pixels that were
634 uncorrectable by being out of range.
635 """
636 if not np.any(np.isfinite(kwargs['coeffs'])):
637 return False, 0
638 if not np.any(kwargs['coeffs']):
639 return False, 0
640
641 ampArray = image.getArray()
642 correction = np.zeros_like(ampArray)
643 for order, coeff in enumerate(kwargs['coeffs'], start=2):
644 correction += coeff * np.power(ampArray, order)
645 ampArray += correction
646
647 return True, 0
648
649
651 """Correct non-linearity with a squared model.
652
653 corrImage = uncorrImage + c0*uncorrImage^2
654
655 where c0 is linearity coefficient 0 for each amplifier.
656 """
657 LinearityType = "Squared"
658
659 def __call__(self, image, **kwargs):
660 """Correct for non-linearity.
661
662 Parameters
663 ----------
664 image : `lsst.afw.image.Image`
665 Image to be corrected
666 kwargs : `dict`
667 Dictionary of parameter keywords:
668 ``"coeffs"``
669 Coefficient vector (`list` or `numpy.array`).
670 ``"log"``
671 Logger to handle messages (`logging.Logger`).
672
673 Returns
674 -------
675 output : `tuple` [`bool`, `int`]
676 If true, a correction was applied successfully. The
677 integer indicates the number of pixels that were
678 uncorrectable by being out of range.
679 """
680
681 sqCoeff = kwargs['coeffs'][0]
682 if sqCoeff != 0:
683 ampArr = image.getArray()
684 ampArr *= (1 + sqCoeff*ampArr)
685 return True, 0
686 else:
687 return False, 0
688
689
691 """Correct non-linearity with a spline model.
692
693 corrImage = uncorrImage - Spline(coeffs, uncorrImage)
694
695 Notes
696 -----
697
698 The spline fit calculates a correction as a function of the
699 expected linear flux term. Because of this, the correction needs
700 to be subtracted from the observed flux.
701
702 """
703 LinearityType = "Spline"
704
705 def __call__(self, image, **kwargs):
706 """Correct for non-linearity.
707
708 Parameters
709 ----------
710 image : `lsst.afw.image.Image`
711 Image to be corrected
712 kwargs : `dict`
713 Dictionary of parameter keywords:
714 ``"coeffs"``
715 Coefficient vector (`list` or `numpy.array`).
716 ``"log"``
717 Logger to handle messages (`logging.Logger`).
718
719 Returns
720 -------
721 output : `tuple` [`bool`, `int`]
722 If true, a correction was applied successfully. The
723 integer indicates the number of pixels that were
724 uncorrectable by being out of range.
725 """
726 splineCoeff = kwargs['coeffs']
727 centers, values = np.split(splineCoeff, 2)
728 interp = afwMath.makeInterpolate(centers.tolist(), values.tolist(),
729 afwMath.stringToInterpStyle("AKIMA_SPLINE"))
730
731 ampArr = image.getArray()
732 delta = interp.interpolate(ampArr.flatten())
733 ampArr -= np.array(delta).reshape(ampArr.shape)
734
735 return True, 0
736
737
739 """Do not correct non-linearity.
740 """
741 LinearityType = "Proportional"
742
743 def __call__(self, image, **kwargs):
744 """Do not correct for non-linearity.
745
746 Parameters
747 ----------
748 image : `lsst.afw.image.Image`
749 Image to be corrected
750 kwargs : `dict`
751 Dictionary of parameter keywords:
752 ``"coeffs"``
753 Coefficient vector (`list` or `numpy.array`).
754 ``"log"``
755 Logger to handle messages (`logging.Logger`).
756
757 Returns
758 -------
759 output : `tuple` [`bool`, `int`]
760 If true, a correction was applied successfully. The
761 integer indicates the number of pixels that were
762 uncorrectable by being out of range.
763 """
764 return True, 0
765
766
768 """Do not correct non-linearity.
769 """
770 LinearityType = "None"
771
772 def __call__(self, image, **kwargs):
773 """Do not correct for non-linearity.
774
775 Parameters
776 ----------
777 image : `lsst.afw.image.Image`
778 Image to be corrected
779 kwargs : `dict`
780 Dictionary of parameter keywords:
781 ``"coeffs"``
782 Coefficient vector (`list` or `numpy.array`).
783 ``"log"``
784 Logger to handle messages (`logging.Logger`).
785
786 Returns
787 -------
788 output : `tuple` [`bool`, `int`]
789 If true, a correction was applied successfully. The
790 integer indicates the number of pixels that were
791 uncorrectable by being out of range.
792 """
793 return True, 0
def validate(self, other=None)
Definition: calibType.py:597
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:495
def __call__(self, image, **kwargs)
Definition: linearize.py:490
def __call__(self, image, **kwargs)
Definition: linearize.py:542
def __call__(self, image, **kwargs)
Definition: linearize.py:772
def __call__(self, image, **kwargs)
Definition: linearize.py:612
def __call__(self, image, **kwargs)
Definition: linearize.py:743
def __call__(self, image, **kwargs)
Definition: linearize.py:705
def __call__(self, image, **kwargs)
Definition: linearize.py:659
def getLinearityTypeByName(self, linearityTypeName)
Definition: linearize.py:347
def validate(self, detector=None, amplifier=None)
Definition: linearize.py:371
def applyLinearity(self, image, detector=None, log=None)
Definition: linearize.py:428
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