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