LSSTApplications  18.1.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 
24 import numpy as np
25 
26 from lsst.pipe.base import Struct
27 from .applyLookupTable import applyLookupTable
28 
29 __all__ = ["LinearizeBase", "LinearizeLookupTable", "LinearizeSquared"]
30 
31 
32 def getLinearityTypeByName(linearityTypeName):
33  """Determine the linearity class to use if the type is known.
34 
35  Parameters
36  ----------
37  linearityTypeName : str
38  String name of the linearity type that is needed.
39 
40  Returns
41  -------
42  linearityType : `~lsst.ip.isr.linearize.LinearizeSquared`
43  The appropriate linearity class to use. If no matching class
44  is found, `None` is returned.
45  """
46  for t in [LinearizeLookupTable, LinearizeSquared]:
47  if t.LinearityType == linearityTypeName:
48  return t
49  return None
50 
51 
52 class LinearizeBase(metaclass=abc.ABCMeta):
53  """Abstract base class functor for correcting non-linearity
54 
55  Subclasses must define __call__ and set class variable LinearityType to a string
56  that will be used for linearity type in AmpInfoCatalog
57  """
58  LinearityType = None # linearity type, a string used for AmpInfoCatalogs
59 
60  @abc.abstractmethod
61  def __call__(self, image, detector, log=None):
62  """Correct non-linearity
63 
64  @param[in] image image to be corrected (an lsst.afw.image.Image)
65  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector)
66  @param[in] log logger (an lsst.log.Log), or None to disable logging;
67  a warning is logged if amplifiers are skipped or other worrisome events occur
68 
69  @return an lsst.pipe.base.Struct containing at least the following fields:
70  - numAmps number of amplifiers found
71  - numLinearized number of amplifiers linearized
72 
73  @throw RuntimeError if the linearity type is wrong
74  @throw a subclass of Exception if linearization fails for any other reason
75  """
76  pass
77 
78  def checkLinearityType(self, detector):
79  """Verify that the linearity type is correct for this detector
80 
81  @warning only checks the first record of the amp info catalog
82 
83  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector)
84 
85  @throw RuntimeError if anything doesn't match
86  """
87  ampInfoType = detector.getAmpInfoCatalog()[0].getLinearityType()
88  if self.LinearityType != ampInfoType:
89  raise RuntimeError("Linearity types don't match: %s != %s" % (self.LinearityType, ampInfoType))
90 
91 
93  """Correct non-linearity with a persisted lookup table
94 
95  for each i,j of image:
96  rowInd = int(c0)
97  colInd = int(c1 + uncorrImage[i,j])
98  corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
99 
100  where c0, c1 are collimation coefficients from the AmpInfoTable of the detector:
101  - c0: row index; used to identify which row of the table to use (typically one per amplifier,
102  though one can have multiple amplifiers use the same table)
103  - c1: column index offset; added to the uncorrected image value before truncation;
104  this supports tables that can handle negative image values; also, if the c1 ends with .5
105  then the nearest index is used instead of truncating to the next smaller index
106 
107  In order to keep related data together, the coefficients are persisted along with the table.
108  """
109  LinearityType = "LookupTable"
110 
111  def __init__(self, table, detector):
112  """Construct a LinearizeLookupTable
113 
114  @param[in] table lookup table; a 2-dimensional array of floats:
115  - one row for each row index (value of coef[0] in the amp info catalog)
116  - one column for each image value
117  To avoid copying the table the last index should vary fastest (numpy default "C" order)
118  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector);
119  the name, serial, and amplifier linearization type and coefficients are saved
120 
121  @throw RuntimeError if table is not 2-dimensional,
122  table has fewer columns than rows (indicating that the indices are swapped),
123  or if any row index (linearity coefficient 0) is out of range
124  """
125  LinearizeBase.__init__(self)
126 
127  self._table = np.array(table, order="C")
128  if len(table.shape) != 2:
129  raise RuntimeError("table shape = %s; must have two dimensions" % (table.shape,))
130  if table.shape[1] < table.shape[0]:
131  raise RuntimeError("table shape = %s; indices are switched" % (table.shape,))
132 
133  self._detectorName = detector.getName()
134  self._detectorSerial = detector.getSerial()
135  self.checkLinearityType(detector)
136  ampInfoCat = detector.getAmpInfoCatalog()
137  rowIndList = []
138  colIndOffsetList = []
139  numTableRows = table.shape[0]
140  for ampInfo in ampInfoCat:
141  rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2]
142  rowInd = int(rowInd)
143  if rowInd < 0 or rowInd >= numTableRows:
144  raise RuntimeError("Amplifier %s has rowInd=%s not in range[0, %s)" %
145  (ampInfo.getName(), rowInd, numTableRows))
146  rowIndList.append(int(rowInd))
147  colIndOffsetList.append(colIndOffset)
148  self._rowIndArr = np.array(rowIndList, dtype=int)
149  self._colIndOffsetArr = np.array(colIndOffsetList)
150 
151  def __call__(self, image, detector, log=None):
152  """Correct for non-linearity
153 
154  @param[in] image image to be corrected (an lsst.afw.image.Image)
155  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector);
156  the name, serial and number of amplifiers must match persisted data;
157  the bbox from each amplifier is read;
158  the linearization coefficients are ignored in favor of the persisted values
159  @param[in] log logger (an lsst.log.Log), or None to disable logging;
160  a warning is logged if any pixels are out of range of their lookup table
161 
162  @return an lsst.pipe.base.Struct containing:
163  - numAmps number of amplifiers found
164  - numLinearized number of amplifiers linearized (always equal to numAmps for this linearizer)
165  - numOutOfRange number of pixels out of range of their lookup table (summed across all amps)
166 
167  @throw RuntimeError if the linearity type is wrong or if the detector name, serial
168  or number of amplifiers does not match the saved data
169  """
170  self.checkDetector(detector)
171  ampInfoCat = detector.getAmpInfoCatalog()
172  numOutOfRange = 0
173  for ampInfo, rowInd, colIndOffset in zip(ampInfoCat, self._rowIndArr, self._colIndOffsetArr):
174  bbox = ampInfo.getBBox()
175  ampView = image.Factory(image, bbox)
176  tableRow = self._table[rowInd, :]
177  numOutOfRange += applyLookupTable(ampView, tableRow, colIndOffset)
178 
179  if numOutOfRange > 0 and log is not None:
180  log.warn("%s pixels of detector \"%s\" were out of range of the linearization table",
181  numOutOfRange, detector.getName())
182  numAmps = len(ampInfoCat)
183  return Struct(
184  numAmps=numAmps,
185  numLinearized=numAmps,
186  numOutOfRange=numOutOfRange,
187  )
188 
189  def checkDetector(self, detector):
190  """Check detector name and serial number, ampInfo table length and linearity type
191 
192  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector);
193 
194  @throw RuntimeError if anything doesn't match
195  """
196  if self._detectorName != detector.getName():
197  raise RuntimeError("Detector names don't match: %s != %s" %
198  (self._detectorName, detector.getName()))
199  if self._detectorSerial != detector.getSerial():
200  raise RuntimeError("Detector serial numbers don't match: %s != %s" %
201  (self._detectorSerial, detector.getSerial()))
202 
203  numAmps = len(detector.getAmpInfoCatalog())
204  if numAmps != len(self._rowIndArr):
205  raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
206  (numAmps, len(self._rowIndArr)))
207  self.checkLinearityType(detector)
208 
209 
211  """Correct non-linearity with a squared model
212 
213  corrImage = uncorrImage + c0*uncorrImage^2
214 
215  where c0 is linearity coefficient 0 in the AmpInfoCatalog of the detector
216  """
217  LinearityType = "Squared"
218 
219  def __call__(self, image, detector, log=None):
220  """Correct for non-linearity
221 
222  @param[in] image image to be corrected (an lsst.afw.image.Image)
223  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector)
224  @param[in] log logger (an lsst.log.Log), or None to disable logging;
225  a warning is logged if any amplifiers are skipped because the square coefficient is 0
226 
227  @return an lsst.pipe.base.Struct containing at least the following fields:
228  - nAmps number of amplifiers found
229  - nLinearized number of amplifiers linearized
230 
231  @throw RuntimeError if the linearity type is wrong
232  """
233  self.checkLinearityType(detector)
234  ampInfoCat = detector.getAmpInfoCatalog()
235  numLinearized = 0
236  for ampInfo in ampInfoCat:
237  sqCoeff = ampInfo.getLinearityCoeffs()[0]
238  if sqCoeff != 0:
239  bbox = ampInfo.getBBox()
240  ampArr = image.Factory(image, bbox).getArray()
241  ampArr *= (1 + sqCoeff*ampArr)
242  numLinearized += 1
243 
244  numAmps = len(ampInfoCat)
245  if numAmps > numLinearized and log is not None:
246  log.warn("%s of %s amps in detector \"%s\" were not linearized (coefficient = 0)",
247  numAmps - numLinearized, numAmps, detector.getName())
248  return Struct(
249  numAmps=numAmps,
250  numLinearized=numLinearized,
251  )
def __call__(self, image, detector, log=None)
Definition: linearize.py:219
def getLinearityTypeByName(linearityTypeName)
Definition: linearize.py:32
def __call__(self, image, detector, log=None)
Definition: linearize.py:61
def checkLinearityType(self, detector)
Definition: linearize.py:78
def __call__(self, image, detector, log=None)
Definition: linearize.py:151
def __init__(self, table, detector)
Definition: linearize.py:111