LSSTApplications  19.0.0-14-gb0260a2+72efe9b372,20.0.0+7927753e06,20.0.0+8829bf0056,20.0.0+995114c5d2,20.0.0+b6f4b2abd1,20.0.0+bddc4f4cbe,20.0.0-1-g253301a+8829bf0056,20.0.0-1-g2b7511a+0d71a2d77f,20.0.0-1-g5b95a8c+7461dd0434,20.0.0-12-g321c96ea+23efe4bbff,20.0.0-16-gfab17e72e+fdf35455f6,20.0.0-2-g0070d88+ba3ffc8f0b,20.0.0-2-g4dae9ad+ee58a624b3,20.0.0-2-g61b8584+5d3db074ba,20.0.0-2-gb780d76+d529cf1a41,20.0.0-2-ged6426c+226a441f5f,20.0.0-2-gf072044+8829bf0056,20.0.0-2-gf1f7952+ee58a624b3,20.0.0-20-geae50cf+e37fec0aee,20.0.0-25-g3dcad98+544a109665,20.0.0-25-g5eafb0f+ee58a624b3,20.0.0-27-g64178ef+f1f297b00a,20.0.0-3-g4cc78c6+e0676b0dc8,20.0.0-3-g8f21e14+4fd2c12c9a,20.0.0-3-gbd60e8c+187b78b4b8,20.0.0-3-gbecbe05+48431fa087,20.0.0-38-ge4adf513+a12e1f8e37,20.0.0-4-g97dc21a+544a109665,20.0.0-4-gb4befbc+087873070b,20.0.0-4-gf910f65+5d3db074ba,20.0.0-5-gdfe0fee+199202a608,20.0.0-5-gfbfe500+d529cf1a41,20.0.0-6-g64f541c+d529cf1a41,20.0.0-6-g9a5b7a1+a1cd37312e,20.0.0-68-ga3f3dda+5fca18c6a4,20.0.0-9-g4aef684+e18322736b,w.2020.45
LSSTDataManagementBasePackage
linearize.py
Go to the documentation of this file.
1 #
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 import abc
23 import numpy as np
24 
25 from astropy.table import Table
26 
27 import lsst.afw.math as afwMath
28 from lsst.pipe.base import Struct
29 from lsst.geom import Box2I, Point2I, Extent2I
30 from .applyLookupTable import applyLookupTable
31 from .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.fromDetector() on init.
54  log : `lsst.log.Log`, 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.hasLinearity = False
101  self.override = False
102 
103  self.ampNames = list()
104  self.linearityCoeffs = dict()
105  self.linearityType = dict()
106  self.linearityBBox = dict()
107 
108  self.fitParams = dict()
109  self.fitParamsErr = dict()
110  self.fitChiSq = dict()
111 
112  self.tableData = 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.tableData = np.array(table, order="C")
119 
120  super().__init__(**kwargs)
121  self.requiredAttributes.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.hasLinearity
142  kwargs['OVERRIDE'] = self.override
143  kwargs['HAS_TABLE'] = self.tableData 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 = detector.getName()
161  self._detectorSerial = detector.getSerial()
162  self._detectorId = detector.getId()
163  self.hasLinearity = True
164 
165  # Do not translate Threshold, Maximum, Units.
166  for amp in detector.getAmplifiers():
167  ampName = amp.getName()
168  self.ampNames.append(ampName)
169  self.linearityType[ampName] = amp.getLinearityType()
170  self.linearityCoeffs[ampName] = amp.getLinearityCoeffs()
171  self.linearityBBox[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.updateMetadata()
234 
235  outDict = {'metadata': self.getMetadata(),
236  'detectorName': self._detectorName,
237  'detectorSerial': self._detectorSerial,
238  'detectorId': self._detectorId,
239  'hasTable': self.tableData is not None,
240  'amplifiers': dict(),
241  }
242  for ampName in self.linearityType:
243  outDict['amplifiers'][ampName] = {'linearityType': self.linearityType[ampName],
244  'linearityCoeffs': self.linearityCoeffs[ampName].tolist(),
245  'linearityBBox': self.linearityBBox[ampName],
246  'fitParams': self.fitParams[ampName].tolist(),
247  'fitParamsErr': self.fitParamsErr[ampName].tolist(),
248  'fitChiSq': self.fitChiSq[ampName]}
249  if self.tableData is not None:
250  outDict['tableData'] = self.tableData.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  -------
269  linearity : `~lsst.ip.isr.linearize.Linearizer``
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.updateMetadata()
328  catalog = Table([{'AMPLIFIER_NAME': ampName,
329  'TYPE': self.linearityType[ampName],
330  'COEFFS': self.linearityCoeffs[ampName],
331  'BBOX_X0': self.linearityBBox[ampName].getMinX(),
332  'BBOX_Y0': self.linearityBBox[ampName].getMinY(),
333  'BBOX_DX': self.linearityBBox[ampName].getWidth(),
334  'BBOX_DY': self.linearityBBox[ampName].getHeight(),
335  'FIT_PARAMS': self.fitParams[ampName],
336  'FIT_PARAMS_ERR': self.fitParamsErr[ampName],
337  'RED_CHI_SQ': self.fitChiSq[ampName],
338  } for ampName in self.ampNames])
339  catalog.meta = self.getMetadata().toDict()
340  tableList.append(catalog)
341 
342  if self.tableData:
343  catalog = Table([{'LOOKUP_VALUES': value} for value in self.tableData])
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 != detector.getName():
390  raise RuntimeError("Detector names don't match: %s != %s" %
391  (self._detectorName, detector.getName()))
392  if int(self._detectorId) != int(detector.getId()):
393  raise RuntimeError("Detector IDs don't match: %s != %s" %
394  (int(self._detectorId), int(detector.getId())))
395  if self._detectorSerial != detector.getSerial():
396  raise RuntimeError("Detector serial numbers don't match: %s != %s" %
397  (self._detectorSerial, detector.getSerial()))
398  if len(detector.getAmplifiers()) != len(self.linearityCoeffs.keys()):
399  raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
400  (len(detector.getAmplifiers()),
401  len(self.linearityCoeffs.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.linearityCoeffs.keys():
410  raise RuntimeError("Amplifier %s is not in linearity data" %
411  (ampName, ))
412  if amp.getLinearityType() != self.linearityType[ampName]:
413  if self.override:
414  self.log.warn("Overriding amplifier defined linearityType (%s) for %s",
415  self.linearityType[ampName], ampName)
416  else:
417  raise RuntimeError("Amplifier %s type %s does not match saved value %s" %
418  (ampName, amp.getLinearityType(), self.linearityType[ampName]))
419  if (amp.getLinearityCoeffs().shape != self.linearityCoeffs[ampName].shape or not
420  np.allclose(amp.getLinearityCoeffs(), self.linearityCoeffs[ampName], equal_nan=True)):
421  if self.override:
422  self.log.warn("Overriding amplifier defined linearityCoeffs (%s) for %s",
423  self.linearityCoeffs[ampName], ampName)
424  else:
425  raise RuntimeError("Amplifier %s coeffs %s does not match saved value %s" %
426  (ampName, amp.getLinearityCoeffs(), self.linearityCoeffs[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 : `~lsst.log.Log`, optional
442  Log object to use for logging.
443  """
444  if log is None:
445  log = self.log
446  if detector and not self.hasLinearity:
447  self.fromDetector(detector)
448 
449  self.validate(detector)
450 
451  numAmps = 0
452  numLinearized = 0
453  numOutOfRange = 0
454  for ampName in self.linearityType.keys():
455  linearizer = self.getLinearityTypeByName(self.linearityType[ampName])
456  numAmps += 1
457  if linearizer is not None:
458  ampView = image.Factory(image, self.linearityBBox[ampName])
459  success, outOfRange = linearizer()(ampView, **{'coeffs': self.linearityCoeffs[ampName],
460  'table': self.tableData,
461  'log': self.log})
462  numOutOfRange += outOfRange
463  if success:
464  numLinearized += 1
465  elif log is not None:
466  log.warn("Amplifier %s did not linearize.",
467  ampName)
468  return Struct(
469  numAmps=numAmps,
470  numLinearized=numLinearized,
471  numOutOfRange=numOutOfRange
472  )
473 
474 
475 class 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 (`lsst.log.Log`).
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 
520 class LinearizeLookupTable(LinearizeBase):
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 (`lsst.log.Log`).
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 = table[rowInd, :]
583  numOutOfRange += applyLookupTable(image, tableRow, colIndOffset)
584 
585  if numOutOfRange > 0 and log is not None:
586  log.warn("%s pixels were out of range of the linearization table",
587  numOutOfRange)
588  if numOutOfRange < image.getArray().size:
589  return True, numOutOfRange
590  else:
591  return False, numOutOfRange
592 
593 
595  """Correct non-linearity with a polynomial mode.
596 
597  corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
598 
599  where c_i are the linearity coefficients for each amplifier.
600  Lower order coefficients are not included as they duplicate other
601  calibration parameters:
602  ``"k0"``
603  A coefficient multiplied by uncorrImage**0 is equivalent to
604  bias level. Irrelevant for correcting non-linearity.
605  ``"k1"``
606  A coefficient multiplied by uncorrImage**1 is proportional
607  to the gain. Not necessary for correcting non-linearity.
608  """
609  LinearityType = "Polynomial"
610 
611  def __call__(self, image, **kwargs):
612  """Correct non-linearity.
613 
614  Parameters
615  ----------
616  image : `lsst.afw.image.Image`
617  Image to be corrected
618  kwargs : `dict`
619  Dictionary of parameter keywords:
620  ``"coeffs"``
621  Coefficient vector (`list` or `numpy.array`).
622  If the order of the polynomial is n, this list
623  should have a length of n-1 ("k0" and "k1" are
624  not needed for the correction).
625  ``"log"``
626  Logger to handle messages (`lsst.log.Log`).
627 
628  Returns
629  -------
630  output : `tuple` [`bool`, `int`]
631  If true, a correction was applied successfully. The
632  integer indicates the number of pixels that were
633  uncorrectable by being out of range.
634  """
635  if not np.any(np.isfinite(kwargs['coeffs'])):
636  return False, 0
637  if not np.any(kwargs['coeffs']):
638  return False, 0
639 
640  ampArray = image.getArray()
641  correction = np.zeros_like(ampArray)
642  for order, coeff in enumerate(kwargs['coeffs'], start=2):
643  correction += coeff * np.power(ampArray, order)
644  ampArray += correction
645 
646  return True, 0
647 
648 
650  """Correct non-linearity with a squared model.
651 
652  corrImage = uncorrImage + c0*uncorrImage^2
653 
654  where c0 is linearity coefficient 0 for each amplifier.
655  """
656  LinearityType = "Squared"
657 
658  def __call__(self, image, **kwargs):
659  """Correct for non-linearity.
660 
661  Parameters
662  ----------
663  image : `lsst.afw.image.Image`
664  Image to be corrected
665  kwargs : `dict`
666  Dictionary of parameter keywords:
667  ``"coeffs"``
668  Coefficient vector (`list` or `numpy.array`).
669  ``"log"``
670  Logger to handle messages (`lsst.log.Log`).
671 
672  Returns
673  -------
674  output : `tuple` [`bool`, `int`]
675  If true, a correction was applied successfully. The
676  integer indicates the number of pixels that were
677  uncorrectable by being out of range.
678  """
679 
680  sqCoeff = kwargs['coeffs'][0]
681  if sqCoeff != 0:
682  ampArr = image.getArray()
683  ampArr *= (1 + sqCoeff*ampArr)
684  return True, 0
685  else:
686  return False, 0
687 
688 
690  """Correct non-linearity with a spline model.
691 
692  corrImage = uncorrImage - Spline(coeffs, uncorrImage)
693 
694  Notes
695  -----
696 
697  The spline fit calculates a correction as a function of the
698  expected linear flux term. Because of this, the correction needs
699  to be subtracted from the observed flux.
700 
701  """
702  LinearityType = "Spline"
703 
704  def __call__(self, image, **kwargs):
705  """Correct for non-linearity.
706 
707  Parameters
708  ----------
709  image : `lsst.afw.image.Image`
710  Image to be corrected
711  kwargs : `dict`
712  Dictionary of parameter keywords:
713  ``"coeffs"``
714  Coefficient vector (`list` or `numpy.array`).
715  ``"log"``
716  Logger to handle messages (`lsst.log.Log`).
717 
718  Returns
719  -------
720  output : `tuple` [`bool`, `int`]
721  If true, a correction was applied successfully. The
722  integer indicates the number of pixels that were
723  uncorrectable by being out of range.
724  """
725  splineCoeff = kwargs['coeffs']
726  centers, values = np.split(splineCoeff, 2)
727  interp = afwMath.makeInterpolate(centers.tolist(), values.tolist(),
728  afwMath.stringToInterpStyle("AKIMA_SPLINE"))
729 
730  ampArr = image.getArray()
731  delta = interp.interpolate(ampArr.flatten())
732  ampArr -= np.array(delta).reshape(ampArr.shape)
733 
734  return True, 0
735 
736 
738  """Do not correct non-linearity.
739  """
740  LinearityType = "Proportional"
741 
742  def __call__(self, image, **kwargs):
743  """Do not correct for non-linearity.
744 
745  Parameters
746  ----------
747  image : `lsst.afw.image.Image`
748  Image to be corrected
749  kwargs : `dict`
750  Dictionary of parameter keywords:
751  ``"coeffs"``
752  Coefficient vector (`list` or `numpy.array`).
753  ``"log"``
754  Logger to handle messages (`lsst.log.Log`).
755 
756  Returns
757  -------
758  output : `tuple` [`bool`, `int`]
759  If true, a correction was applied successfully. The
760  integer indicates the number of pixels that were
761  uncorrectable by being out of range.
762  """
763  return True, 0
764 
765 
767  """Do not correct non-linearity.
768  """
769  LinearityType = "None"
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  ``"coeffs"``
781  Coefficient vector (`list` or `numpy.array`).
782  ``"log"``
783  Logger to handle messages (`lsst.log.Log`).
784 
785  Returns
786  -------
787  output : `tuple` [`bool`, `int`]
788  If true, a correction was applied successfully. The
789  integer indicates the number of pixels that were
790  uncorrectable by being out of range.
791  """
792  return True, 0
lsst::ip::isr.linearize.Linearizer.toTable
def toTable(self)
Definition: linearize.py:313
lsst::ip::isr.linearize.Linearizer.override
override
Definition: linearize.py:101
lsst::afw::math::stringToInterpStyle
Interpolate::Style stringToInterpStyle(std::string const &style)
Conversion function to switch a string to an Interpolate::Style.
Definition: Interpolate.cc:257
lsst::ip::isr.linearize.Linearizer
Definition: linearize.py:38
lsst::log.log.logContinued.warn
def warn(fmt, *args)
Definition: logContinued.py:205
lsst::ip::isr.linearize.LinearizeBase.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:490
lsst::ip::isr.linearize.LinearizeBase
Definition: linearize.py:475
lsst::ip::isr.linearize.LinearizeSpline
Definition: linearize.py:689
lsst::ip::isr.calibType.IsrCalib.fromDetector
def fromDetector(self, detector)
Definition: calibType.py:430
lsst::ip::isr.linearize.Linearizer.hasLinearity
hasLinearity
Definition: linearize.py:100
ast::append
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
lsst::ip::isr.calibType.IsrCalib.validate
def validate(self, other=None)
Definition: calibType.py:532
astshim.keyMap.keyMapContinued.keys
def keys(self)
Definition: keyMapContinued.py:6
lsst::ip::isr.calibType.IsrCalib.updateMetadata
def updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
Definition: calibType.py:148
lsst::ip::isr.linearize.LinearizeSquared.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:658
lsst::ip::isr.linearize.Linearizer.updateMetadata
def updateMetadata(self, setDate=False, **kwargs)
Definition: linearize.py:127
lsst::ip::isr.linearize.Linearizer.fitChiSq
fitChiSq
Definition: linearize.py:110
lsst::afw::geom.transform.transformContinued.cls
cls
Definition: transformContinued.py:33
lsst::ip::isr.calibType.IsrCalib.log
log
Definition: calibType.py:82
lsst::ip::isr.linearize.Linearizer.fitParams
fitParams
Definition: linearize.py:108
lsst::ip::isr.linearize.Linearizer.fromDetector
def fromDetector(self, detector)
Definition: linearize.py:147
lsst::ip::isr.linearize.Linearizer.linearityBBox
linearityBBox
Definition: linearize.py:106
lsst::ip::isr.linearize.LinearizeNone.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:771
lsst::ip::isr.linearize.LinearizeLookupTable.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:542
lsst::ip::isr.linearize.LinearizePolynomial
Definition: linearize.py:594
lsst::afw::math::makeInterpolate
std::shared_ptr< Interpolate > makeInterpolate(ndarray::Array< double const, 1 > const &x, ndarray::Array< double const, 1 > const &y, Interpolate::Style const style=Interpolate::AKIMA_SPLINE)
Definition: Interpolate.cc:353
lsst::ip::isr.calibType.IsrCalib._detectorSerial
_detectorSerial
Definition: calibType.py:68
lsst.pipe.base.struct.Struct
Definition: struct.py:26
lsst::ip::isr.calibType.IsrCalib.requiredAttributes
requiredAttributes
Definition: calibType.py:77
lsst::ip::isr.linearize.Linearizer.__init__
def __init__(self, table=None, **kwargs)
Definition: linearize.py:99
applyLookupTable
lsst::ip::isr.linearize.Linearizer.tableData
tableData
Definition: linearize.py:112
lsst::ip::isr.linearize.Linearizer.linearityType
linearityType
Definition: linearize.py:105
lsst::ip::isr.linearize.LinearizeNone
Definition: linearize.py:766
lsst::ip::isr.calibType.IsrCalib.getMetadata
def getMetadata(self)
Definition: calibType.py:114
lsst::ip::isr.linearize.Linearizer.toDict
def toDict(self)
Definition: linearize.py:226
lsst::ip::isr.linearize.Linearizer.validate
def validate(self, detector=None, amplifier=None)
Definition: linearize.py:371
lsst::ip::isr.linearize.LinearizeProportional
Definition: linearize.py:737
lsst::geom
Definition: AffineTransform.h:36
lsst::ip::isr.linearize.Linearizer.fromTable
def fromTable(cls, tableList)
Definition: linearize.py:255
lsst::ip::isr.calibType.IsrCalib._detectorId
_detectorId
Definition: calibType.py:69
lsst::ip::isr.linearize.LinearizeProportional.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:742
list
daf::base::PropertyList * list
Definition: fits.cc:913
lsst::afw::math
Definition: statistics.dox:6
lsst::geom::Extent2I
Extent< int, 2 > Extent2I
Definition: Extent.h:397
lsst::ip::isr.linearize.LinearizeSquared
Definition: linearize.py:649
lsst::ip::isr.linearize.LinearizeSpline.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:704
lsst::geom::Point2I
Point< int, 2 > Point2I
Definition: Point.h:321
lsst::geom::Box2I
An integer coordinate rectangle.
Definition: Box.h:55
lsst::ip::isr.linearize.Linearizer.linearityCoeffs
linearityCoeffs
Definition: linearize.py:104
lsst::ip::isr.calibType.IsrCalib
Definition: calibType.py:36
lsst::ip::isr.linearize.Linearizer.getLinearityTypeByName
def getLinearityTypeByName(self, linearityTypeName)
Definition: linearize.py:347
lsst::ip::isr.linearize.Linearizer.ampNames
ampNames
Definition: linearize.py:103
lsst::ip::isr.linearize.Linearizer.fitParamsErr
fitParamsErr
Definition: linearize.py:109
lsst::ip::isr.calibType.IsrCalib._detectorName
_detectorName
Definition: calibType.py:67
lsst.pipe.base
Definition: __init__.py:1
lsst::ip::isr.linearize.Linearizer.applyLinearity
def applyLinearity(self, image, detector=None, log=None)
Definition: linearize.py:428
lsst::ip::isr.linearize.LinearizePolynomial.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:611
lsst::ip::isr.linearize.Linearizer.fromDict
def fromDict(cls, dictionary)
Definition: linearize.py:176