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