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
assembleCcdTask.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-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 lsst.afw.cameraGeom as cameraGeom
23 import lsst.afw.cameraGeom.utils as cameraGeomUtils
24 import lsst.afw.image as afwImage
25 import lsst.pex.config as pexConfig
26 import lsst.pipe.base as pipeBase
27 from lsstDebug import getDebugFrame
28 from lsst.afw.display import getDisplay
29 from .isr import calcEffectiveGain
30 
31 __all__ = ["AssembleCcdTask"]
32 
33 
34 class AssembleCcdConfig(pexConfig.Config):
35  setGain = pexConfig.Field(
36  doc="set gain?",
37  dtype=bool,
38  default=True,
39  )
40  doRenorm = pexConfig.Field(
41  doc="renormalize to a gain of 1? (ignored if setGain false). "
42  "Setting to True gives 1 ADU per electron. "
43  "Setting to True is not recommended for mosaic cameras because it breaks normalization across "
44  "the focal plane. However, if the CCDs are sufficiently flat then the resulting error "
45  "may be acceptable.",
46  dtype=bool,
47  default=False,
48  )
49  doTrim = pexConfig.Field(
50  doc="trim out non-data regions?",
51  dtype=bool,
52  default=True,
53  )
54  keysToRemove = pexConfig.ListField(
55  doc="FITS headers to remove (in addition to DATASEC, BIASSEC, TRIMSEC and perhaps GAIN)",
56  dtype=str,
57  default=(),
58  )
59 
60 ## \addtogroup LSST_task_documentation
61 ## \{
62 ## \page AssembleCcdTask
63 ## \ref AssembleCcdTask_ "AssembleCcdTask"
64 ## \copybrief AssembleCcdTask
65 ## \}
66 
67 
68 class AssembleCcdTask(pipeBase.Task):
69  """!
70  \anchor AssembleCcdTask_
71 
72  \brief Assemble a set of amplifier images into a full detector size set of pixels.
73 
74  \section ip_isr_assemble_Contents Contents
75 
76  - \ref ip_isr_assemble_Purpose
77  - \ref ip_isr_assemble_Initialize
78  - \ref ip_isr_assemble_IO
79  - \ref ip_isr_assemble_Config
80  - \ref ip_isr_assemble_Debug
81  - \ref ip_isr_assemble_Example
82 
83  \section ip_isr_assemble_Purpose Description
84 
85  This task assembles sections of an image into a larger mosaic. The sub-sections
86  are typically amplifier sections and are to be assembled into a detector size pixel grid.
87  The assembly is driven by the entries in the raw amp information. The task can be configured
88  to return a detector image with non-data (e.g. overscan) pixels included. The task can also
89  renormalize the pixel values to a nominal gain of 1. The task also removes exposure metadata that
90  has context in raw amps, but not in trimmed detectors (e.g. 'BIASSEC').
91 
92  \section ip_isr_assemble_Initialize Task initialization
93 
94  \copydoc \_\_init\_\_
95 
96  \section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method
97 
98  \copydoc assembleCcd
99 
100  \section ip_isr_assemble_Config Configuration parameters
101 
102  See \ref AssembleCcdConfig
103 
104  \section ip_isr_assemble_Debug Debug variables
105 
106  The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
107  flag \c -d to import \b debug.py from your \c PYTHONPATH; see <a
108  href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
109  Using lsstDebug to control debugging output</a> for more about \b debug.py files.
110 
111  The available variables in AssembleCcdTask are:
112  <DL>
113  <DT> \c display
114  <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
115  <DL>
116  <DT> assembledExposure
117  <DD> display assembled exposure
118  </DL>
119  </DL>
120 
121  \section ip_isr_assemble_Example A complete example of using AssembleCcdTask
122 
123  This code is in runAssembleTask.py in the examples directory, and can be run as \em e.g.
124  \code
125  python examples/runAssembleTask.py
126  \endcode
127 
128  \dontinclude runAssembleTask.py
129  Import the task. There are other imports. Read the source file for more info.
130  \skipline AssembleCcdTask
131 
132  \dontinclude exampleUtils.py
133  Create some input images with the help of some utilities in examples/exampleUtils.py
134  \skip makeAssemblyInput
135  @until inputData
136  The above numbers can be changed. The assumption that the readout corner is flipped on every other amp is
137  hardcoded in createDetector.
138 
139  \dontinclude runAssembleTask.py
140  Run the assembler task
141  \skip runAssembler
142  @until frame += 1
143 
144  <HR>
145  To investigate the \ref ip_isr_assemble_Debug, put something like
146  \code{.py}
147  import lsstDebug
148  def DebugInfo(name):
149  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
150  if name == "lsst.ip.isr.assembleCcdTask":
151  di.display = {'assembledExposure':2}
152  return di
153 
154  lsstDebug.Info = DebugInfo
155  \endcode
156  into your debug.py file and run runAssembleTask.py with the \c --debug flag.
157 
158 
159  Conversion notes:
160  Display code should be updated once we settle on a standard way of controlling what is displayed.
161  """
162  ConfigClass = AssembleCcdConfig
163  _DefaultName = "assembleCcd"
164 
165  def __init__(self, **kwargs):
166  """!Initialize the AssembleCcdTask
167 
168  The keys for removal specified in the config are added to a default set:
169  ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN')
170  """
171  pipeBase.Task.__init__(self, **kwargs)
172 
173  self.allKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove)
174 
175  def assembleCcd(self, assembleInput):
176  """!Assemble a set of amps into a single CCD size image
177  \param[in] assembleInput -- Either a dictionary of amp lsst.afw.image.Exposures or a single
178  lsst.afw.image.Exposure containing all raw
179  amps. If a dictionary of amp exposures,
180  the key should be the amp name.
181  \return assembledCcd -- An lsst.afw.image.Exposure of the assembled amp sections.
182 
183  \throws TypeError with the following string:
184 
185  <DL>
186  <DT> Expected either a dictionary of amp exposures or a single raw exposure
187  <DD> The input exposures to be assembled do not adhere to the required format.
188  </DL>
189 
190  \throws RuntimeError with the following string:
191 
192  <DL>
193  <DT> No ccd detector found
194  <DD> The detector set on the input exposure is not set.
195  </DL>
196  """
197  ccd = None
198  if isinstance(assembleInput, dict):
199  # assembleInput is a dictionary of amp name: amp exposure
200 
201  # Assume all amps have the same detector, so get the detector from an arbitrary amp
202  ccd = next(iter(assembleInput.values())).getDetector()
203 
204  def getNextExposure(amp):
205  return assembleInput[amp.getName()]
206  elif hasattr(assembleInput, "getMaskedImage"):
207  # assembleInput is a single exposure
208  ccd = assembleInput.getDetector()
209 
210  def getNextExposure(amp):
211  return assembleInput
212  else:
213  raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure")
214 
215  if ccd is None:
216  raise RuntimeError("No ccd detector found")
217 
218  if not self.config.doTrim:
219  outBox = cameraGeomUtils.calcRawCcdBBox(ccd)
220  else:
221  outBox = ccd.getBBox()
222  outExposure = afwImage.ExposureF(outBox)
223  outMI = outExposure.getMaskedImage()
224 
225  if self.config.doTrim:
226  assemble = cameraGeom.assembleAmplifierImage
227  else:
228  assemble = cameraGeom.assembleAmplifierRawImage
229 
230  for amp in ccd:
231  inMI = getNextExposure(amp).getMaskedImage()
232  assemble(outMI, inMI, amp)
233  outExposure.setDetector(ccd)
234  self.postprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0]))
235 
236  return outExposure
237 
238  def postprocessExposure(self, outExposure, inExposure):
239  """Set exposure non-image attributes, including wcs and metadata and display exposure (if requested)
240 
241  Call after assembling the pixels
242 
243  @param[in,out] outExposure assembled exposure:
244  - removes unwanted keywords
245  - sets calib, filter, and detector
246  @param[in] inExposure input exposure
247  """
248  self.setWcs(outExposure=outExposure, inExposure=inExposure)
249 
250  exposureMetadata = inExposure.getMetadata()
251  for key in self.allKeysToRemove:
252  if exposureMetadata.exists(key):
253  exposureMetadata.remove(key)
254  outExposure.setMetadata(exposureMetadata)
255 
256  if self.config.setGain:
257  self.setGain(outExposure=outExposure)
258 
259  # note: Calib is not copied, presumably because it is assumed unknown in raw data
260  outExposure.setFilter(inExposure.getFilter())
261  outExposure.getInfo().setVisitInfo(inExposure.getInfo().getVisitInfo())
262 
263  frame = getDebugFrame(self._display, "assembledExposure")
264  if frame:
265  getDisplay(frame).mtv(outExposure)
266 
267  def setWcs(self, outExposure, inExposure):
268  """Set output WCS = input WCS, adjusted as required for datasecs not starting at lower left corner
269 
270  @param[in,out] outExposure assembled exposure; wcs is set
271  @param[in] inExposure input exposure
272  """
273  if inExposure.hasWcs():
274  wcs = inExposure.getWcs()
275  ccd = outExposure.getDetector()
276  amp0 = ccd[0]
277  if amp0 is None:
278  raise RuntimeError("No amplifier detector information found")
279  cameraGeomUtils.prepareWcsData(wcs, amp0, isTrimmed=self.config.doTrim)
280  outExposure.setWcs(wcs)
281  else:
282  self.log.warn("No WCS found in input exposure")
283 
284  def setGain(self, outExposure):
285  """Renormalize, if requested, and set gain metadata
286 
287  @param[in,out] outExposure assembled exposure:
288  - scales the pixels if config.doRenorm is true
289  - adds some gain keywords to the metadata
290  """
291  if outExposure.getMaskedImage().getVariance().getArray().max() == 0:
292  raise RuntimeError("Can't calculate the effective gain since the variance plane is set to zero")
293  ccd = outExposure.getDetector()
294  exposureMetadata = outExposure.getMetadata()
295  gain = 0.
296  namps = 0
297  for amp in ccd:
298  gain += amp.getGain()
299  namps += 1
300  gain /= namps
301  if self.config.doRenorm:
302  mi = outExposure.getMaskedImage()
303  mi *= gain
304  exposureMetadata.set("GAIN", 1.0)
305  medgain, meangain = calcEffectiveGain(outExposure.getMaskedImage())
306  exposureMetadata.add("MEDGAIN", medgain)
307  exposureMetadata.add("MEANGAIN", meangain)
308  exposureMetadata.add("GAINEFF", medgain)
int iter
Assemble a set of amplifier images into a full detector size set of pixels.
def assembleCcd
Assemble a set of amps into a single CCD size image.
def __init__
Initialize the AssembleCcdTask.
def calcEffectiveGain
Definition: isr.py:47
def getDebugFrame
Definition: lsstDebug.py:66