LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
linearize.py
Go to the documentation of this file.
1 from builtins import object
2 #
3 # LSST Data Management System
4 # Copyright 2016 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 import abc
24 from builtins import zip
25 from future.utils import with_metaclass
26 
27 import numpy as np
28 
29 from lsst.pipe.base import Struct
30 from .isrLib import applyLookupTable
31 
32 __all__ = ["LinearizeBase", "LinearizeLookupTable", "LinearizeSquared"]
33 
34 
35 class LinearizeBase(with_metaclass(abc.ABCMeta, object)):
36  """Abstract base class functor for correcting non-linearity
37 
38  Subclasses must define __call__ and set class variable LinearityType to a string
39  that will be used for linearity type in AmpInfoCatalog
40  """
41  LinearityType = None # linearity type, a string used for AmpInfoCatalogs
42 
43  @abc.abstractmethod
44  def __call__(self, image, detector, log=None):
45  """Correct non-linearity
46 
47  @param[in] image image to be corrected (an lsst.afw.image.Image)
48  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector)
49  @param[in] log logger (an lsst.log.Log), or None to disable logging;
50  a warning is logged if amplifiers are skipped or other worrisome events occur
51 
52  @return an lsst.pipe.base.Struct containing at least the following fields:
53  - numAmps number of amplifiers found
54  - numLinearized number of amplifiers linearized
55 
56  @throw RuntimeError if the linearity type is wrong
57  @throw a subclass of Exception if linearization fails for any other reason
58  """
59  pass
60 
61  def checkLinearityType(self, detector):
62  """Verify that the linearity type is correct for this detector
63 
64  @warning only checks the first record of the amp info catalog
65 
66  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector)
67 
68  @throw RuntimeError if anything doesn't match
69  """
70  ampInfoType = detector.getAmpInfoCatalog()[0].getLinearityType()
71  if self.LinearityType != ampInfoType:
72  raise RuntimeError("Linearity types don't match: %s != %s" % (self.LinearityType, ampInfoType))
73 
74 
76  """Correct non-linearity with a persisted lookup table
77 
78  for each i,j of image:
79  rowInd = int(c0)
80  colInd = int(c1 + uncorrImage[i,j])
81  corrImage[i,j] = uncorrImage[i,j] + table[rowInd, colInd]
82 
83  where c0, c1 are collimation coefficients from the AmpInfoTable of the detector:
84  - c0: row index; used to identify which row of the table to use (typically one per amplifier,
85  though one can have multiple amplifiers use the same table)
86  - c1: column index offset; added to the uncorrected image value before truncation;
87  this supports tables that can handle negative image values; also, if the c1 ends with .5
88  then the nearest index is used instead of truncating to the next smaller index
89 
90  In order to keep related data together, the coefficients are persisted along with the table.
91  """
92  LinearityType = "LookupTable"
93 
94  def __init__(self, table, detector):
95  """Construct a LinearizeLookupTable
96 
97  @param[in] table lookup table; a 2-dimensional array of floats:
98  - one row for each row index (value of coef[0] in the amp info catalog)
99  - one column for each image value
100  To avoid copying the table the last index should vary fastest (numpy default "C" order)
101  @param[in] detector detector information (an instance of lsst::afw::cameraGeom::Detector);
102  the name, serial, and amplifier linearization type and coefficients are saved
103 
104  @throw RuntimeError if table is not 2-dimensional,
105  table has fewer columns than rows (indicating that the indices are swapped),
106  or if any row index (linearity coefficient 0) is out of range
107  """
108  LinearizeBase.__init__(self)
109 
110  self._table = np.array(table, order="C")
111  if len(table.shape) != 2:
112  raise RuntimeError("table shape = %s; must have two dimensions" % (table.shape,))
113  if table.shape[1] < table.shape[0]:
114  raise RuntimeError("table shape = %s; indices are switched" % (table.shape,))
115 
116  self._detectorName = detector.getName()
117  self._detectorSerial = detector.getSerial()
118  self.checkLinearityType(detector)
119  ampInfoCat = detector.getAmpInfoCatalog()
120  rowIndList = []
121  colIndOffsetList = []
122  numTableRows = table.shape[0]
123  for ampInfo in ampInfoCat:
124  rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2]
125  rowInd = int(rowInd)
126  if rowInd < 0 or rowInd >= numTableRows:
127  raise RuntimeError("Amplifier %s has rowInd=%s not in range[0, %s)" %
128  (ampInfo.getName(), rowInd, numTableRows))
129  rowIndList.append(int(rowInd))
130  colIndOffsetList.append(colIndOffset)
131  self._rowIndArr = np.array(rowIndList, dtype=int)
132  self._colIndOffsetArr = np.array(colIndOffsetList)
133 
134  def __call__(self, image, detector, log=None):
135  """Correct for non-linearity
136 
137  @param[in] image image to be corrected (an lsst.afw.image.Image)
138  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector);
139  the name, serial and number of amplifiers must match persisted data;
140  the bbox from each amplifier is read;
141  the linearization coefficients are ignored in favor of the persisted values
142  @param[in] log logger (an lsst.log.Log), or None to disable logging;
143  a warning is logged if any pixels are out of range of their lookup table
144 
145  @return an lsst.pipe.base.Struct containing:
146  - numAmps number of amplifiers found
147  - numLinearized number of amplifiers linearized (always equal to numAmps for this linearizer)
148  - numOutOfRange number of pixels out of range of their lookup table (summed across all amps)
149 
150  @throw RuntimeError if the linearity type is wrong or if the detector name, serial
151  or number of amplifiers does not match the saved data
152  """
153  self.checkDetector(detector)
154  ampInfoCat = detector.getAmpInfoCatalog()
155  numOutOfRange = 0
156  for ampInfo, rowInd, colIndOffset in zip(ampInfoCat, self._rowIndArr, self._colIndOffsetArr):
157  bbox = ampInfo.getBBox()
158  ampView = image.Factory(image, bbox)
159  tableRow = self._table[rowInd, :]
160  numOutOfRange += applyLookupTable(ampView, tableRow, colIndOffset)
161 
162  if numOutOfRange > 0 and log is not None:
163  log.warn("%s pixels of detector \"%s\" were out of range of the linearization table",
164  numOutOfRange, detector.getName())
165  numAmps = len(ampInfoCat)
166  return Struct(
167  numAmps=numAmps,
168  numLinearized=numAmps,
169  numOutOfRange=numOutOfRange,
170  )
171 
172  def checkDetector(self, detector):
173  """Check detector name and serial number, ampInfo table length and linearity type
174 
175  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector);
176 
177  @throw RuntimeError if anything doesn't match
178  """
179  if self._detectorName != detector.getName():
180  raise RuntimeError("Detector names don't match: %s != %s" %
181  (self._detectorName, detector.getName()))
182  if self._detectorSerial != detector.getSerial():
183  raise RuntimeError("Detector serial numbers don't match: %s != %s" %
184  (self._detectorSerial, detector.getSerial()))
185 
186  numAmps = len(detector.getAmpInfoCatalog())
187  if numAmps != len(self._rowIndArr):
188  raise RuntimeError("Detector number of amps = %s does not match saved value %s" %
189  (numAmps, len(self._rowIndArr)))
190  self.checkLinearityType(detector)
191 
192 
194  """Correct non-linearity with a squared model
195 
196  corrImage = uncorrImage + c0*uncorrImage^2
197 
198  where c0 is linearity coefficient 0 in the AmpInfoCatalog of the detector
199  """
200  LinearityType = "Squared"
201 
202  def __call__(self, image, detector, log=None):
203  """Correct for non-linearity
204 
205  @param[in] image image to be corrected (an lsst.afw.image.Image)
206  @param[in] detector detector info about image (an lsst.afw.cameraGeom.Detector)
207  @param[in] log logger (an lsst.log.Log), or None to disable logging;
208  a warning is logged if any amplifiers are skipped because the square coefficient is 0
209 
210  @return an lsst.pipe.base.Struct containing at least the following fields:
211  - nAmps number of amplifiers found
212  - nLinearized number of amplifiers linearized
213 
214  @throw RuntimeError if the linearity type is wrong
215  """
216  self.checkLinearityType(detector)
217  ampInfoCat = detector.getAmpInfoCatalog()
218  numLinearized = 0
219  for ampInfo in ampInfoCat:
220  sqCoeff = ampInfo.getLinearityCoeffs()[0]
221  if sqCoeff != 0:
222  bbox = ampInfo.getBBox()
223  ampArr = image.Factory(image, bbox).getArray()
224  ampArr *= (1 + sqCoeff*ampArr)
225  numLinearized += 1
226 
227  numAmps = len(ampInfoCat)
228  if numAmps > numLinearized and log is not None:
229  log.warn("%s of %s amps in detector \"%s\" were not linearized (coefficient = 0)",
230  numAmps - numLinearized, numAmps, detector.getName())
231  return Struct(
232  numAmps=numAmps,
233  numLinearized=numLinearized,
234  )
int applyLookupTable(afw::image::Image< PixelT > &image, ndarray::Array< PixelT, 1, 1 > const &table, PixelT indOffset)
Add the values in a lookup table to an image, e.g.