lsst.ip.isr 22.0.1-29-g4a3d635+0ff3657c89
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
22import lsst.afw.cameraGeom as cameraGeom
23import lsst.afw.cameraGeom.utils as cameraGeomUtils
24import lsst.afw.display as afwDisplay
25import lsst.afw.image as afwImage
26import lsst.pex.config as pexConfig
27import lsst.pipe.base as pipeBase
28from lsstDebug import getDebugFrame
29
30__all__ = ["AssembleCcdTask"]
31
32
33class 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
53class 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="https://developer.lsst.io/stack/debug.html">Debugging Tasks with lsstDebug</a> for more
94 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 <HR>
109 To investigate the @ref ip_isr_assemble_Debug, put something like
110 @code{.py}
111 import lsstDebug
112 def DebugInfo(name):
113 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
114 if name == "lsst.ip.isr.assembleCcdTask":
115 di.display = {'assembledExposure':2}
116 return di
117
118 lsstDebug.Info = DebugInfo
119 @endcode
120 into your debug.py file and run runAssembleTask.py with the @c --debug flag.
121
122
123 Conversion notes:
124 Display code should be updated once we settle on a standard way of controlling what is displayed.
125 """
126 ConfigClass = AssembleCcdConfig
127 _DefaultName = "assembleCcd"
128
129 def __init__(self, **kwargs):
130 """!Initialize the AssembleCcdTask
131
132 The keys for removal specified in the config are added to a default set:
133 ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN')
134 """
135 pipeBase.Task.__init__(self, **kwargs)
136
137 self.allKeysToRemoveallKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove)
138
139 def assembleCcd(self, assembleInput):
140 """!Assemble a set of amps into a single CCD size image
141 @param[in] assembleInput -- Either a dictionary of amp lsst.afw.image.Exposures or a single
142 lsst.afw.image.Exposure containing all raw
143 amps. If a dictionary of amp exposures,
144 the key should be the amp name.
145 @return assembledCcd -- An lsst.afw.image.Exposure of the assembled amp sections.
146
147 @throws TypeError with the following string:
148
149 <DL>
150 <DT> Expected either a dictionary of amp exposures or a single raw exposure
151 <DD> The input exposures to be assembled do not adhere to the required format.
152 </DL>
153
154 @throws RuntimeError with the following string:
155
156 <DL>
157 <DT> No ccd detector found
158 <DD> The detector set on the input exposure is not set.
159 </DL>
160 """
161 ccd = None
162 if isinstance(assembleInput, dict):
163 # assembleInput is a dictionary of amp name: amp exposure
164
165 # Assume all amps have the same detector, so get the detector from an arbitrary amp
166 ccd = next(iter(assembleInput.values())).getDetector()
167
168 def getNextExposure(amp):
169 return assembleInput[amp.getName()]
170 elif hasattr(assembleInput, "getMaskedImage"):
171 # assembleInput is a single exposure
172 ccd = assembleInput.getDetector()
173
174 def getNextExposure(amp):
175 return assembleInput
176 else:
177 raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure")
178
179 if ccd is None:
180 raise RuntimeError("No ccd detector found")
181
182 if not self.config.doTrim:
183 outBox = cameraGeomUtils.calcRawCcdBBox(ccd)
184 else:
185 outBox = ccd.getBBox()
186 outExposure = afwImage.ExposureF(outBox)
187 outMI = outExposure.getMaskedImage()
188
189 if self.config.doTrim:
190 assemble = cameraGeom.assembleAmplifierImage
191 else:
192 assemble = cameraGeom.assembleAmplifierRawImage
193
194 for amp in ccd:
195 inMI = getNextExposure(amp).getMaskedImage()
196 assemble(outMI, inMI, amp)
197 #
198 # If we are returning an "untrimmed" image (with overscans and extended register) we
199 # need to update the ampInfo table in the Detector as we've moved the amp images into
200 # place in a single Detector image
201 #
202 if not self.config.doTrim:
203 ccd = cameraGeom.makeUpdatedDetector(ccd)
204
205 outExposure.setDetector(ccd)
206 self.postprocessExposurepostprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0]))
207
208 return outExposure
209
210 def postprocessExposure(self, outExposure, inExposure):
211 """Set exposure non-image attributes, including wcs and metadata and display exposure (if requested)
212
213 Call after assembling the pixels
214
215 @param[in,out] outExposure assembled exposure:
216 - removes unwanted keywords
217 - sets wcs, filter, and detector
218 @param[in] inExposure input exposure
219 """
220 if inExposure.hasWcs():
221 outExposure.setWcs(inExposure.getWcs())
222
223 exposureMetadata = inExposure.getMetadata()
224 for key in self.allKeysToRemoveallKeysToRemove:
225 if exposureMetadata.exists(key):
226 exposureMetadata.remove(key)
227 outExposure.setMetadata(exposureMetadata)
228
229 # note: don't copy PhotoCalib, because it is assumed to be unknown in raw data
230 outExposure.info.id = inExposure.info.id
231 outExposure.setFilterLabel(inExposure.getFilterLabel())
232 outExposure.getInfo().setVisitInfo(inExposure.getInfo().getVisitInfo())
233
234 frame = getDebugFrame(self._display, "assembledExposure")
235 if frame:
236 afwDisplay.Display(frame=frame).mtv(outExposure, title="postprocessExposure")
def assembleCcd(self, assembleInput)
Assemble a set of amps into a single CCD size image.
def __init__(self, **kwargs)
Initialize the AssembleCcdTask.
def postprocessExposure(self, outExposure, inExposure)