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