LSSTApplications  20.0.0
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 copy
24 import datetime
25 
26 import numpy as np
27 import yaml
28 
29 import lsst.afw.table as afwTable
30 from lsst.daf.base import PropertyList
31 from lsst.pipe.base import Struct
32 from lsst.geom import Box2I, Point2I, Extent2I
33 from .applyLookupTable import applyLookupTable
34 
35 __all__ = ["Linearizer",
36  "LinearizeBase", "LinearizeLookupTable", "LinearizeSquared",
37  "LinearizeProportional", "LinearizePolynomial", "LinearizeNone"]
38 
39 
40 class Linearizer(abc.ABC):
41  """Parameter set for linearization.
42 
43  These parameters are included in cameraGeom.Amplifier, but
44  should be accessible externally to allow for testing.
45 
46  Parameters
47  ----------
48  table : `numpy.array`, optional
49  Lookup table; a 2-dimensional array of floats:
50  - one row for each row index (value of coef[0] in the amplifier)
51  - one column for each image value
52  To avoid copying the table the last index should vary fastest
53  (numpy default "C" order)
54  detector : `lsst.afw.cameraGeom.Detector`
55  Detector object
56  override : `bool`, optional
57  Override the parameters defined in the detector/amplifier.
58  log : `lsst.log.Log`, optional
59  Logger to handle messages.
60 
61  Raises
62  ------
63  RuntimeError :
64  Raised if the supplied table is not 2D, or if the table has fewer
65  columns than rows (indicating that the indices are swapped).
66  """
67 
68  _OBSTYPE = "linearizer"
69  """The dataset type name used for this class"""
70 
71  def __init__(self, table=None, detector=None, override=False, log=None):
72  self._detectorName = None
73  self._detectorSerial = None
74  self._detectorId = None
75  self._metadata = PropertyList()
76 
77  self.linearityCoeffs = dict()
78  self.linearityType = dict()
79  self.linearityThreshold = dict()
80  self.linearityMaximum = dict()
81  self.linearityUnits = dict()
82  self.linearityBBox = dict()
83 
84  self.override = override
85  self.populated = False
86  self.log = log
87 
88  self.tableData = None
89  if table is not None:
90  if len(table.shape) != 2:
91  raise RuntimeError("table shape = %s; must have two dimensions" % (table.shape,))
92  if table.shape[1] < table.shape[0]:
93  raise RuntimeError("table shape = %s; indices are switched" % (table.shape,))
94  self.tableData = np.array(table, order="C")
95 
96  if detector:
97  self.fromDetector(detector)
98 
99  def __call__(self, exposure):
100  """Apply linearity, setting parameters if necessary.
101 
102  Parameters
103  ----------
104  exposure : `lsst.afw.image.Exposure`
105  Exposure to correct.
106 
107  Returns
108  -------
109  output : `lsst.pipe.base.Struct`
110  Linearization results:
111  ``"numAmps"``
112  Number of amplifiers considered.
113  ``"numLinearized"``
114  Number of amplifiers linearized.
115  """
116 
117  def fromDetector(self, detector):
118  """Read linearity parameters from a detector.
119 
120  Parameters
121  ----------
122  detector : `lsst.afw.cameraGeom.detector`
123  Input detector with parameters to use.
124  """
125  self._detectorName = detector.getName()
126  self._detectorSerial = detector.getSerial()
127  self._detectorId = detector.getId()
128  self.populated = True
129 
130  # Do not translate Threshold, Maximum, Units.
131  for amp in detector.getAmplifiers():
132  ampName = amp.getName()
133  self.linearityCoeffs[ampName] = amp.getLinearityCoeffs()
134  self.linearityType[ampName] = amp.getLinearityType()
135  self.linearityBBox[ampName] = amp.getBBox()
136 
137  def fromYaml(self, yamlObject):
138  """Read linearity parameters from a dict.
139 
140  Parameters
141  ----------
142  yamlObject : `dict`
143  Dictionary containing detector and amplifier information.
144  """
145  self.setMetadata(metadata=yamlObject.get('metadata', None))
146  self._detectorName = yamlObject['detectorName']
147  self._detectorSerial = yamlObject['detectorSerial']
148  self._detectorId = yamlObject['detectorId']
149  self.populated = True
150  self.override = True
151 
152  for ampName in yamlObject['amplifiers']:
153  amp = yamlObject['amplifiers'][ampName]
154  self.linearityCoeffs[ampName] = np.array(amp.get('linearityCoeffs', None), dtype=np.float64)
155  self.linearityType[ampName] = amp.get('linearityType', 'None')
156  self.linearityBBox[ampName] = amp.get('linearityBBox', None)
157 
158  if self.tableData is None:
159  self.tableData = yamlObject.get('tableData', None)
160  if self.tableData:
161  self.tableData = np.array(self.tableData)
162 
163  return self
164 
165  def toDict(self):
166  """Return linearity parameters as a dict.
167 
168  Returns
169  -------
170  outDict : `dict`:
171  """
172  # metadata copied from defects code
173  now = datetime.datetime.utcnow()
174  self.updateMetadata(date=now)
175 
176  outDict = {'metadata': self.getMetadata(),
177  'detectorName': self._detectorName,
178  'detectorSerial': self._detectorSerial,
179  'detectorId': self._detectorId,
180  'hasTable': self.tableData is not None,
181  'amplifiers': dict()}
182  for ampName in self.linearityType:
183  outDict['amplifiers'][ampName] = {'linearityType': self.linearityType[ampName],
184  'linearityCoeffs': self.linearityCoeffs[ampName],
185  'linearityBBox': self.linearityBBox[ampName]}
186  if self.tableData is not None:
187  outDict['tableData'] = self.tableData.tolist()
188 
189  return outDict
190 
191  @classmethod
192  def readText(cls, filename):
193  """Read linearity from text file.
194 
195  Parameters
196  ----------
197  filename : `str`
198  Name of the file containing the linearity definition.
199  Returns
200  -------
201  linearity : `~lsst.ip.isr.linearize.Linearizer``
202  Linearity parameters.
203  """
204  data = ''
205  with open(filename, 'r') as f:
206  data = yaml.load(f, Loader=yaml.CLoader)
207  return cls().fromYaml(data)
208 
209  def writeText(self, filename):
210  """Write the linearity model to a text file.
211 
212  Parameters
213  ----------
214  filename : `str`
215  Name of the file to write.
216 
217  Returns
218  -------
219  used : `str`
220  The name of the file used to write the data.
221 
222  Raises
223  ------
224  RuntimeError :
225  Raised if filename does not end in ".yaml".
226 
227  Notes
228  -----
229  The file is written to YAML format and will include any metadata
230  associated with the `Linearity`.
231  """
232  outDict = self.toDict()
233  if filename.lower().endswith((".yaml")):
234  with open(filename, 'w') as f:
235  yaml.dump(outDict, f)
236  else:
237  raise RuntimeError(f"Attempt to write to a file {filename} that does not end in '.yaml'")
238 
239  return filename
240 
241  @classmethod
242  def fromTable(cls, table, tableExtTwo=None):
243  """Read linearity from a FITS file.
244 
245  Parameters
246  ----------
247  table : `lsst.afw.table`
248  afwTable read from input file name.
249  tableExtTwo: `lsst.afw.table`, optional
250  afwTable read from second extension of input file name
251 
252  Returns
253  -------
254  linearity : `~lsst.ip.isr.linearize.Linearizer``
255  Linearity parameters.
256 
257  Notes
258  -----
259  The method reads a FITS file with 1 or 2 extensions. The metadata is read from the header of
260  extension 1, which must exist. Then the table is loaded, and the ['AMPLIFIER_NAME', 'TYPE',
261  'COEFFS', 'BBOX_X0', 'BBOX_Y0', 'BBOX_DX', 'BBOX_DY'] columns are read and used to
262  set each dictionary by looping over rows.
263  Eextension 2 is then attempted to read in the try block (which only exists for lookup tables).
264  It has a column named 'LOOKUP_VALUES' that contains a vector of the lookup entries in each row.
265  """
266  metadata = table.getMetadata()
267  schema = table.getSchema()
268 
269  linDict = dict()
270  linDict['metadata'] = metadata
271  linDict['detectorId'] = metadata['DETECTOR']
272  linDict['detectorName'] = metadata['DETECTOR_NAME']
273  try:
274  linDict['detectorSerial'] = metadata['DETECTOR_SERIAL']
275  except Exception:
276  linDict['detectorSerial'] = 'NOT SET'
277  linDict['amplifiers'] = dict()
278 
279  # Preselect the keys
280  ampNameKey = schema['AMPLIFIER_NAME'].asKey()
281  typeKey = schema['TYPE'].asKey()
282  coeffsKey = schema['COEFFS'].asKey()
283  x0Key = schema['BBOX_X0'].asKey()
284  y0Key = schema['BBOX_Y0'].asKey()
285  dxKey = schema['BBOX_DX'].asKey()
286  dyKey = schema['BBOX_DY'].asKey()
287 
288  for record in table:
289  ampName = record[ampNameKey]
290  ampDict = dict()
291  ampDict['linearityType'] = record[typeKey]
292  ampDict['linearityCoeffs'] = record[coeffsKey]
293  ampDict['linearityBBox'] = Box2I(Point2I(record[x0Key], record[y0Key]),
294  Extent2I(record[dxKey], record[dyKey]))
295 
296  linDict['amplifiers'][ampName] = ampDict
297 
298  if tableExtTwo is not None:
299  lookupValuesKey = 'LOOKUP_VALUES'
300  linDict["tableData"] = [record[lookupValuesKey] for record in tableExtTwo]
301 
302  return cls().fromYaml(linDict)
303 
304  @classmethod
305  def readFits(cls, filename):
306  """Read linearity from a FITS file.
307 
308  Parameters
309  ----------
310  filename : `str`
311  Name of the file containing the linearity definition.
312  Returns
313  -------
314  linearity : `~lsst.ip.isr.linearize.Linearizer``
315  Linearity parameters.
316 
317  Notes
318  -----
319  This method and `fromTable` read a FITS file with 1 or 2 extensions. The metadata is read from the
320  header of extension 1, which must exist. Then the table is loaded, and the ['AMPLIFIER_NAME',
321  'TYPE', 'COEFFS', 'BBOX_X0', 'BBOX_Y0', 'BBOX_DX', 'BBOX_DY'] columns are read and used to
322  set each dictionary by looping over rows.
323  Extension 2 is then attempted to read in the try block (which only exists for lookup tables).
324  It has a column named 'LOOKUP_VALUES' that contains a vector of the lookup entries in each row.
325  """
326  table = afwTable.BaseCatalog.readFits(filename)
327  tableExtTwo = None
328  try:
329  tableExtTwo = afwTable.BaseCatalog.readFits(filename, 2)
330  except Exception:
331  pass
332  return cls().fromTable(table, tableExtTwo=tableExtTwo)
333 
334  def toAmpTable(self, metadata):
335  """Produce linearity catalog
336 
337  Parameters
338  ----------
339  metadata : `lsst.daf.base.PropertyList`
340  Linearizer metadata
341 
342  Returns
343  -------
344  catalog : `lsst.afw.table.BaseCatalog`
345  Catalog to write
346  """
347  metadata["LINEARITY_SCHEMA"] = "Linearity table"
348  metadata["LINEARITY_VERSION"] = 1
349 
350  # Now pack it into a fits table.
351  length = max([len(self.linearityCoeffs[x]) for x in self.linearityCoeffs.keys()])
352 
353  schema = afwTable.Schema()
354  names = schema.addField("AMPLIFIER_NAME", type="String", size=16, doc="linearity amplifier name")
355  types = schema.addField("TYPE", type="String", size=16, doc="linearity type names")
356  coeffs = schema.addField("COEFFS", type="ArrayD", size=length, doc="linearity coefficients")
357  boxX = schema.addField("BBOX_X0", type="I", doc="linearity bbox minimum x")
358  boxY = schema.addField("BBOX_Y0", type="I", doc="linearity bbox minimum y")
359  boxDx = schema.addField("BBOX_DX", type="I", doc="linearity bbox x dimension")
360  boxDy = schema.addField("BBOX_DY", type="I", doc="linearity bbox y dimension")
361 
362  catalog = afwTable.BaseCatalog(schema)
363  catalog.resize(len(self.linearityCoeffs.keys()))
364 
365  for ii, ampName in enumerate(self.linearityType):
366  catalog[ii][names] = ampName
367  catalog[ii][types] = self.linearityType[ampName]
368  catalog[ii][coeffs] = np.array(self.linearityCoeffs[ampName], dtype=float)
369 
370  bbox = self.linearityBBox[ampName]
371  catalog[ii][boxX], catalog[ii][boxY] = bbox.getMin()
372  catalog[ii][boxDx], catalog[ii][boxDy] = bbox.getDimensions()
373  catalog.setMetadata(metadata)
374 
375  return catalog
376 
377  def toTableDataTable(self, metadata):
378  """Produce linearity catalog from table data
379 
380  Parameters
381  ----------
382  metadata : `lsst.daf.base.PropertyList`
383  Linearizer metadata
384 
385  Returns
386  -------
387  catalog : `lsst.afw.table.BaseCatalog`
388  Catalog to write
389  """
390 
391  schema = afwTable.Schema()
392  dimensions = self.tableData.shape
393  lut = schema.addField("LOOKUP_VALUES", type='ArrayF', size=dimensions[1],
394  doc="linearity lookup data")
395  catalog = afwTable.BaseCatalog(schema)
396  catalog.resize(dimensions[0])
397 
398  for ii in range(dimensions[0]):
399  catalog[ii][lut] = np.array(self.tableData[ii], dtype=np.float32)
400 
401  metadata["LINEARITY_LOOKUP"] = True
402  catalog.setMetadata(metadata)
403 
404  return catalog
405 
406  def writeFits(self, filename):
407  """Write the linearity model to a FITS file.
408 
409  Parameters
410  ----------
411  filename : `str`
412  Name of the file to write.
413 
414  Notes
415  -----
416  The file is written to YAML format and will include any metadata
417  associated with the `Linearity`.
418  """
419  now = datetime.datetime.utcnow()
420  self.updateMetadata(date=now)
421  metadata = copy.copy(self.getMetadata())
422  catalog = self.toAmpTable(metadata)
423  catalog.writeFits(filename)
424 
425  if self.tableData is not None:
426  catalog = self.toTableDataTable(metadata)
427  catalog.writeFits(filename, "a")
428 
429  return
430 
431  def getMetadata(self):
432  """Retrieve metadata associated with this `Linearizer`.
433 
434  Returns
435  -------
436  meta : `lsst.daf.base.PropertyList`
437  Metadata. The returned `~lsst.daf.base.PropertyList` can be
438  modified by the caller and the changes will be written to
439  external files.
440  """
441  return self._metadata
442 
443  def setMetadata(self, metadata=None):
444  """Store a copy of the supplied metadata with the `Linearizer`.
445 
446  Parameters
447  ----------
448  metadata : `lsst.daf.base.PropertyList`, optional
449  Metadata to associate with the linearizer. Will be copied and
450  overwrite existing metadata. If not supplied the existing
451  metadata will be reset.
452  """
453  if metadata is None:
454  self._metadata = PropertyList()
455  else:
456  self._metadata = copy.copy(metadata)
457 
458  # Ensure that we have the obs type required by calibration ingest
459  self._metadata["OBSTYPE"] = self._OBSTYPE
460 
461  def updateMetadata(self, date=None, detectorId=None, detectorName=None, instrumentName=None, calibId=None,
462  serial=None):
463  """Update metadata keywords with new values.
464 
465  Parameters
466  ----------
467  date : `datetime.datetime`, optional
468  detectorId : `int`, optional
469  detectorName: `str`, optional
470  instrumentName : `str`, optional
471  calibId: `str`, optional
472  serial: detector serial, `str`, optional
473 
474  """
475  mdOriginal = self.getMetadata()
476  mdSupplemental = dict()
477 
478  if date:
479  mdSupplemental['CALIBDATE'] = date.isoformat()
480  mdSupplemental['CALIB_CREATION_DATE'] = date.date().isoformat(),
481  mdSupplemental['CALIB_CREATION_TIME'] = date.time().isoformat(),
482  if detectorId:
483  mdSupplemental['DETECTOR'] = f"{detectorId}"
484  if detectorName:
485  mdSupplemental['DETECTOR_NAME'] = detectorName
486  if instrumentName:
487  mdSupplemental['INSTRUME'] = instrumentName
488  if calibId:
489  mdSupplemental['CALIB_ID'] = calibId
490  if serial:
491  mdSupplemental['DETECTOR_SERIAL'] = serial
492 
493  mdOriginal.update(mdSupplemental)
494 
495  def getLinearityTypeByName(self, linearityTypeName):
496  """Determine the linearity class to use from the type name.
497 
498  Parameters
499  ----------
500  linearityTypeName : str
501  String name of the linearity type that is needed.
502 
503  Returns
504  -------
505  linearityType : `~lsst.ip.isr.linearize.LinearizeBase`
506  The appropriate linearity class to use. If no matching class
507  is found, `None` is returned.
508  """
509  for t in [LinearizeLookupTable,
510  LinearizeSquared,
511  LinearizePolynomial,
512  LinearizeProportional,
513  LinearizeNone]:
514  if t.LinearityType == linearityTypeName:
515  return t
516  return None
517 
518  def validate(self, detector=None, amplifier=None):
519  """Validate linearity for a detector/amplifier.
520 
521  Parameters
522  ----------
523  detector : `lsst.afw.cameraGeom.Detector`, optional
524  Detector to validate, along with its amplifiers.
525  amplifier : `lsst.afw.cameraGeom.Amplifier`, optional
526  Single amplifier to validate.
527 
528  Raises
529  ------
530  RuntimeError :
531  Raised if there is a mismatch in linearity parameters, and
532  the cameraGeom parameters are not being overridden.
533  """
534  amplifiersToCheck = []
535  if detector:
536  if self._detectorName != detector.getName():
537  raise RuntimeError("Detector names don't match: %s != %s" %
538  (self._detectorName, detector.getName()))
539  if int(self._detectorId) != int(detector.getId()):
540  raise RuntimeError("Detector IDs don't match: %s != %s" %
541  (int(self._detectorId), int(detector.getId())))
542  if self._detectorSerial != detector.getSerial():
543  raise RuntimeError("Detector serial numbers don't match: %s != %s" %
544  (self._detectorSerial, detector.getSerial()))
545  if len(detector.getAmplifiers()) != len(self.linearityCoeffs.keys()):
546  raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
547  (len(detector.getAmplifiers()),
548  len(self.linearityCoeffs.keys())))
549  amplifiersToCheck.extend(detector.getAmplifiers())
550 
551  if amplifier:
552  amplifiersToCheck.extend(amplifier)
553 
554  for amp in amplifiersToCheck:
555  ampName = amp.getName()
556  if ampName not in self.linearityCoeffs.keys():
557  raise RuntimeError("Amplifier %s is not in linearity data" %
558  (ampName, ))
559  if amp.getLinearityType() != self.linearityType[ampName]:
560  if self.override:
561  self.log.warn("Overriding amplifier defined linearityType (%s) for %s",
562  self.linearityType[ampName], ampName)
563  else:
564  raise RuntimeError("Amplifier %s type %s does not match saved value %s" %
565  (ampName, amp.getLinearityType(), self.linearityType[ampName]))
566  if (amp.getLinearityCoeffs().shape != self.linearityCoeffs[ampName].shape or not
567  np.allclose(amp.getLinearityCoeffs(), self.linearityCoeffs[ampName], equal_nan=True)):
568  if self.override:
569  self.log.warn("Overriding amplifier defined linearityCoeffs (%s) for %s",
570  self.linearityCoeffs[ampName], ampName)
571  else:
572  raise RuntimeError("Amplifier %s coeffs %s does not match saved value %s" %
573  (ampName, amp.getLinearityCoeffs(), self.linearityCoeffs[ampName]))
574 
575  def applyLinearity(self, image, detector=None, log=None):
576  """Apply the linearity to an image.
577 
578  If the linearity parameters are populated, use those,
579  otherwise use the values from the detector.
580 
581  Parameters
582  ----------
583  image : `~lsst.afw.image.image`
584  Image to correct.
585  detector : `~lsst.afw.cameraGeom.detector`
586  Detector to use for linearity parameters if not already
587  populated.
588  log : `~lsst.log.Log`, optional
589  Log object to use for logging.
590  """
591  if log is None:
592  log = self.log
593  if detector and not self.populated:
594  self.fromDetector(detector)
595 
596  self.validate(detector)
597 
598  numAmps = 0
599  numLinearized = 0
600  numOutOfRange = 0
601  for ampName in self.linearityType.keys():
602  linearizer = self.getLinearityTypeByName(self.linearityType[ampName])
603  numAmps += 1
604  if linearizer is not None:
605  ampView = image.Factory(image, self.linearityBBox[ampName])
606  success, outOfRange = linearizer()(ampView, **{'coeffs': self.linearityCoeffs[ampName],
607  'table': self.tableData,
608  'log': self.log})
609  numOutOfRange += outOfRange
610  if success:
611  numLinearized += 1
612  elif log is not None:
613  log.warn("Amplifier %s did not linearize.",
614  ampName)
615  return Struct(
616  numAmps=numAmps,
617  numLinearized=numLinearized,
618  numOutOfRange=numOutOfRange
619  )
620 
621 
622 class LinearizeBase(metaclass=abc.ABCMeta):
623  """Abstract base class functor for correcting non-linearity.
624 
625  Subclasses must define __call__ and set class variable
626  LinearityType to a string that will be used for linearity type in
627  the cameraGeom.Amplifier.linearityType field.
628 
629  All linearity corrections should be defined in terms of an
630  additive correction, such that:
631 
632  corrected_value = uncorrected_value + f(uncorrected_value)
633  """
634  LinearityType = None # linearity type, a string used for AmpInfoCatalogs
635 
636  @abc.abstractmethod
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  ``"coeffs"``
647  Coefficient vector (`list` or `numpy.array`).
648  ``"table"``
649  Lookup table data (`numpy.array`).
650  ``"log"``
651  Logger to handle messages (`lsst.log.Log`).
652 
653  Returns
654  -------
655  output : `bool`
656  If true, a correction was applied successfully.
657 
658  Raises
659  ------
660  RuntimeError:
661  Raised if the linearity type listed in the
662  detector does not match the class type.
663  """
664  pass
665 
666 
667 class LinearizeLookupTable(LinearizeBase):
668  """Correct non-linearity with a persisted lookup table.
669 
670  The lookup table consists of entries such that given
671  "coefficients" c0, c1:
672 
673  for each i,j of image:
674  rowInd = int(c0)
675  colInd = int(c1 + uncorrImage[i,j])
676  corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
677 
678  - c0: row index; used to identify which row of the table to use
679  (typically one per amplifier, though one can have multiple
680  amplifiers use the same table)
681  - c1: column index offset; added to the uncorrected image value
682  before truncation; this supports tables that can handle
683  negative image values; also, if the c1 ends with .5 then
684  the nearest index is used instead of truncating to the
685  next smaller index
686  """
687  LinearityType = "LookupTable"
688 
689  def __call__(self, image, **kwargs):
690  """Correct for non-linearity.
691 
692  Parameters
693  ----------
694  image : `lsst.afw.image.Image`
695  Image to be corrected
696  kwargs : `dict`
697  Dictionary of parameter keywords:
698  ``"coeffs"``
699  Columnation vector (`list` or `numpy.array`).
700  ``"table"``
701  Lookup table data (`numpy.array`).
702  ``"log"``
703  Logger to handle messages (`lsst.log.Log`).
704 
705  Returns
706  -------
707  output : `bool`
708  If true, a correction was applied successfully.
709 
710  Raises
711  ------
712  RuntimeError:
713  Raised if the requested row index is out of the table
714  bounds.
715  """
716  numOutOfRange = 0
717 
718  rowInd, colIndOffset = kwargs['coeffs'][0:2]
719  table = kwargs['table']
720  log = kwargs['log']
721 
722  numTableRows = table.shape[0]
723  rowInd = int(rowInd)
724  if rowInd < 0 or rowInd > numTableRows:
725  raise RuntimeError("LinearizeLookupTable rowInd=%s not in range[0, %s)" %
726  (rowInd, numTableRows))
727  tableRow = table[rowInd, :]
728  numOutOfRange += applyLookupTable(image, tableRow, colIndOffset)
729 
730  if numOutOfRange > 0 and log is not None:
731  log.warn("%s pixels were out of range of the linearization table",
732  numOutOfRange)
733  if numOutOfRange < image.getArray().size:
734  return True, numOutOfRange
735  else:
736  return False, numOutOfRange
737 
738 
740  """Correct non-linearity with a polynomial mode.
741 
742  corrImage = uncorrImage + sum_i c_i uncorrImage^(2 + i)
743 
744  where c_i are the linearity coefficients for each amplifier.
745  Lower order coefficients are not included as they duplicate other
746  calibration parameters:
747  ``"k0"``
748  A coefficient multiplied by uncorrImage**0 is equivalent to
749  bias level. Irrelevant for correcting non-linearity.
750  ``"k1"``
751  A coefficient multiplied by uncorrImage**1 is proportional
752  to the gain. Not necessary for correcting non-linearity.
753  """
754  LinearityType = "Polynomial"
755 
756  def __call__(self, image, **kwargs):
757  """Correct non-linearity.
758 
759  Parameters
760  ----------
761  image : `lsst.afw.image.Image`
762  Image to be corrected
763  kwargs : `dict`
764  Dictionary of parameter keywords:
765  ``"coeffs"``
766  Coefficient vector (`list` or `numpy.array`).
767  If the order of the polynomial is n, this list
768  should have a length of n-1 ("k0" and "k1" are
769  not needed for the correction).
770  ``"log"``
771  Logger to handle messages (`lsst.log.Log`).
772 
773  Returns
774  -------
775  output : `bool`
776  If true, a correction was applied successfully.
777  """
778  if not np.any(np.isfinite(kwargs['coeffs'])):
779  return False, 0
780  if not np.any(kwargs['coeffs']):
781  return False, 0
782 
783  ampArray = image.getArray()
784  correction = np.zeros_like(ampArray)
785  for order, coeff in enumerate(kwargs['coeffs'], start=2):
786  correction += coeff * np.power(ampArray, order)
787  ampArray += correction
788 
789  return True, 0
790 
791 
793  """Correct non-linearity with a squared model.
794 
795  corrImage = uncorrImage + c0*uncorrImage^2
796 
797  where c0 is linearity coefficient 0 for each amplifier.
798  """
799  LinearityType = "Squared"
800 
801  def __call__(self, image, **kwargs):
802  """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  ``"coeffs"``
811  Coefficient vector (`list` or `numpy.array`).
812  ``"log"``
813  Logger to handle messages (`lsst.log.Log`).
814 
815  Returns
816  -------
817  output : `bool`
818  If true, a correction was applied successfully.
819  """
820 
821  sqCoeff = kwargs['coeffs'][0]
822  if sqCoeff != 0:
823  ampArr = image.getArray()
824  ampArr *= (1 + sqCoeff*ampArr)
825  return True, 0
826  else:
827  return False, 0
828 
829 
831  """Do not correct non-linearity.
832  """
833  LinearityType = "Proportional"
834 
835  def __call__(self, image, **kwargs):
836  """Do not correct for non-linearity.
837 
838  Parameters
839  ----------
840  image : `lsst.afw.image.Image`
841  Image to be corrected
842  kwargs : `dict`
843  Dictionary of parameter keywords:
844  ``"coeffs"``
845  Coefficient vector (`list` or `numpy.array`).
846  ``"log"``
847  Logger to handle messages (`lsst.log.Log`).
848 
849  Returns
850  -------
851  output : `bool`
852  If true, a correction was applied successfully.
853  """
854  return True, 0
855 
856 
858  """Do not correct non-linearity.
859  """
860  LinearityType = "None"
861 
862  def __call__(self, image, **kwargs):
863  """Do not correct for non-linearity.
864 
865  Parameters
866  ----------
867  image : `lsst.afw.image.Image`
868  Image to be corrected
869  kwargs : `dict`
870  Dictionary of parameter keywords:
871  ``"coeffs"``
872  Coefficient vector (`list` or `numpy.array`).
873  ``"log"``
874  Logger to handle messages (`lsst.log.Log`).
875 
876  Returns
877  -------
878  output : `bool`
879  If true, a correction was applied successfully.
880  """
881  return True, 0
lsst::ip::isr.linearize.Linearizer.override
override
Definition: linearize.py:84
lsst::ip::isr.linearize.Linearizer
Definition: linearize.py:40
lsst::log.log.logContinued.warn
def warn(fmt, *args)
Definition: logContinued.py:202
lsst::ip::isr.linearize.Linearizer.fromTable
def fromTable(cls, table, tableExtTwo=None)
Definition: linearize.py:242
lsst::ip::isr.linearize.LinearizeBase.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:637
lsst::ip::isr.linearize.Linearizer.updateMetadata
def updateMetadata(self, date=None, detectorId=None, detectorName=None, instrumentName=None, calibId=None, serial=None)
Definition: linearize.py:461
lsst::ip::isr.linearize.Linearizer.writeFits
def writeFits(self, filename)
Definition: linearize.py:406
lsst::ip::isr.linearize.LinearizeBase
Definition: linearize.py:622
lsst::ip::isr.linearize.Linearizer.__init__
def __init__(self, table=None, detector=None, override=False, log=None)
Definition: linearize.py:71
lsst::ip::isr.linearize.Linearizer._detectorName
_detectorName
Definition: linearize.py:72
lsst::ip::isr.linearize.Linearizer.linearityMaximum
linearityMaximum
Definition: linearize.py:80
lsst::ip::isr.linearize.Linearizer.fromYaml
def fromYaml(self, yamlObject)
Definition: linearize.py:137
lsst::daf::base::PropertyList
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
astshim.keyMap.keyMapContinued.keys
def keys(self)
Definition: keyMapContinued.py:6
lsst::afw::table::Schema
Defines the fields and offsets for a table.
Definition: Schema.h:50
lsst::ip::isr.linearize.Linearizer.populated
populated
Definition: linearize.py:85
lsst::ip::isr.linearize.LinearizeSquared.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:801
lsst::afw::geom.transform.transformContinued.cls
cls
Definition: transformContinued.py:33
lsst::ip::isr.linearize.Linearizer.fromDetector
def fromDetector(self, detector)
Definition: linearize.py:117
lsst::ip::isr.linearize.Linearizer.linearityBBox
linearityBBox
Definition: linearize.py:82
lsst::ip::isr.linearize.Linearizer.toTableDataTable
def toTableDataTable(self, metadata)
Definition: linearize.py:377
lsst::ip::isr.linearize.LinearizeNone.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:862
lsst::ip::isr.linearize.LinearizeLookupTable.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:689
lsst::ip::isr.linearize.LinearizePolynomial
Definition: linearize.py:739
lsst.pipe.base.struct.Struct
Definition: struct.py:26
lsst::ip::isr.linearize.Linearizer.getMetadata
def getMetadata(self)
Definition: linearize.py:431
lsst::ip::isr.linearize.Linearizer._detectorId
_detectorId
Definition: linearize.py:74
applyLookupTable
lsst::ip::isr.linearize.Linearizer.tableData
tableData
Definition: linearize.py:88
lsst::ip::isr.linearize.Linearizer.linearityType
linearityType
Definition: linearize.py:78
lsst::ip::isr.linearize.Linearizer.linearityUnits
linearityUnits
Definition: linearize.py:81
lsst::ip::isr.linearize.Linearizer._metadata
_metadata
Definition: linearize.py:75
lsst::ip::isr.linearize.LinearizeNone
Definition: linearize.py:857
max
int max
Definition: BoundedField.cc:104
lsst::afw::table
Definition: table.dox:3
lsst::ip::isr.linearize.Linearizer._detectorSerial
_detectorSerial
Definition: linearize.py:73
lsst::ip::isr.linearize.Linearizer.readFits
def readFits(cls, filename)
Definition: linearize.py:305
lsst::ip::isr.linearize.Linearizer.toDict
def toDict(self)
Definition: linearize.py:165
lsst::ip::isr.linearize.Linearizer.validate
def validate(self, detector=None, amplifier=None)
Definition: linearize.py:518
lsst::ip::isr.linearize.Linearizer.__call__
def __call__(self, exposure)
Definition: linearize.py:99
lsst::ip::isr.linearize.Linearizer.writeText
def writeText(self, filename)
Definition: linearize.py:209
lsst::ip::isr.linearize.LinearizeProportional
Definition: linearize.py:830
lsst::ip::isr.linearize.Linearizer.linearityThreshold
linearityThreshold
Definition: linearize.py:79
lsst::geom
Definition: geomOperators.dox:4
lsst::daf::base
Definition: Utils.h:47
lsst::ip::isr.linearize.Linearizer.setMetadata
def setMetadata(self, metadata=None)
Definition: linearize.py:443
lsst::ip::isr.linearize.LinearizeProportional.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:835
lsst::ip::isr.linearize.Linearizer.log
log
Definition: linearize.py:86
lsst::geom::Extent2I
Extent< int, 2 > Extent2I
Definition: Extent.h:397
lsst::ip::isr.linearize.LinearizeSquared
Definition: linearize.py:792
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:77
lsst::ip::isr.linearize.Linearizer.toAmpTable
def toAmpTable(self, metadata)
Definition: linearize.py:334
lsst::ip::isr.linearize.Linearizer.getLinearityTypeByName
def getLinearityTypeByName(self, linearityTypeName)
Definition: linearize.py:495
lsst::ip::isr.linearize.Linearizer._OBSTYPE
string _OBSTYPE
Definition: linearize.py:68
lsst.pipe.base
Definition: __init__.py:1
lsst::ip::isr.linearize.Linearizer.applyLinearity
def applyLinearity(self, image, detector=None, log=None)
Definition: linearize.py:575
lsst::afw::table::CatalogT< BaseRecord >
lsst::ip::isr.linearize.Linearizer.readText
def readText(cls, filename)
Definition: linearize.py:192
lsst::ip::isr.linearize.LinearizePolynomial.__call__
def __call__(self, image, **kwargs)
Definition: linearize.py:756