Coverage for python/lsst/ip/isr/assembleCcdTask.py: 21%
60 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-20 02:41 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-20 02:41 -0700
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/>.
22__all__ = ["AssembleCcdTask"]
24import lsst.afw.cameraGeom as cameraGeom
25import lsst.afw.cameraGeom.utils as cameraGeomUtils
26import lsst.afw.display as afwDisplay
27import lsst.afw.image as afwImage
28import lsst.pex.config as pexConfig
29import lsst.pipe.base as pipeBase
30from lsstDebug import getDebugFrame
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 )
45## @addtogroup LSST_task_documentation
46## @{
47## @page page_AssembleCcdTask AssembleCcdTask
48## @ref AssembleCcdTask_ "AssembleCcdTask"
49## @copybrief AssembleCcdTask
50## @}
53class AssembleCcdTask(pipeBase.Task):
54 r"""!
55 @anchor AssembleCcdTask_
57 @brief Assemble a set of amplifier images into a full detector size set of
58 pixels.
60 @section ip_isr_assemble_Contents Contents
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
69 @section ip_isr_assemble_Purpose Description
71 This task assembles sections of an image into a larger mosaic. The
72 sub-sections are typically amplifier sections and are to be assembled
73 into a detector size pixel grid. The assembly is driven by the entries in
74 the raw amp information. The task can be configured to return a detector
75 image with non-data (e.g. overscan) pixels included. The task can also
76 renormalize the pixel values to a nominal gain of 1. The task also
77 removes exposure metadata that has context in raw amps, but not in trimmed
78 detectors (e.g. 'BIASSEC').
80 @section ip_isr_assemble_Initialize Task initialization
82 @copydoc __init__
84 @section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method
86 @copydoc assembleCcd
88 @section ip_isr_assemble_Config Configuration parameters
90 See @ref AssembleCcdConfig
92 @section ip_isr_assemble_Debug Debug variables
94 The command line task interface supports a flag @c -d to import @b debug.py from your
95 @c PYTHONPATH; see <a
96 href="https://developer.lsst.io/stack/debug.html">Debugging Tasks with
97 lsstDebug</a> for more about @b debug.py files.
99 The available variables in AssembleCcdTask are:
100 <DL>
101 <DT> @c display
102 <DD> A dictionary containing debug point names as keys with frame number
103 as value. Valid keys are:
104 <DL>
105 <DT> assembledExposure
106 <DD> display assembled exposure
107 </DL>
108 </DL>
110 @section ip_isr_assemble_Example A complete example of using
111 AssembleCcdTask
113 <HR>
114 To investigate the @ref ip_isr_assemble_Debug, put something like
115 @code{.py}
116 import lsstDebug
117 def DebugInfo(name):
118 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call
119 # us recursively
120 if name == "lsst.ip.isr.assembleCcdTask":
121 di.display = {'assembledExposure':2}
122 return di
124 lsstDebug.Info = DebugInfo
125 @endcode
126 into your debug.py file and run runAssembleTask.py with the @c --debug
127 flag.
130 Conversion notes:
131 Display code should be updated once we settle on a standard way of
132 controlling what is displayed.
133 """
134 ConfigClass = AssembleCcdConfig
135 _DefaultName = "assembleCcd"
137 def __init__(self, **kwargs):
138 """!Initialize the AssembleCcdTask
140 The keys for removal specified in the config are added to a default
141 set: ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN')
142 """
143 pipeBase.Task.__init__(self, **kwargs)
145 self.allKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove)
147 def assembleCcd(self, assembleInput):
148 """!Assemble a set of amps into a single CCD size image
149 @param[in] assembleInput -- Either a dictionary of amp
150 lsst.afw.image.Exposures or a single
151 lsst.afw.image.Exposure containing all raw
152 amps. If a dictionary of amp exposures,
153 the key should be the amp name.
154 @return assembledCcd -- An lsst.afw.image.Exposure of the assembled
155 amp sections.
157 @throws TypeError with the following string:
159 <DL>
160 <DT> Expected either a dictionary of amp exposures or a single raw
161 exposure.
162 <DD> The input exposures to be assembled do not adhere to the
163 required format.
164 </DL>
166 @throws RuntimeError with the following string:
168 <DL>
169 <DT> No ccd detector found
170 <DD> The detector set on the input exposure is not set.
171 </DL>
172 """
173 ccd = None
174 if isinstance(assembleInput, dict):
175 # assembleInput is a dictionary of amp name: amp exposure
177 # Assume all amps have the same detector, so get the detector from
178 # an arbitrary amp.
179 ccd = next(iter(assembleInput.values())).getDetector()
181 def getNextExposure(amp):
182 return assembleInput[amp.getName()]
183 elif hasattr(assembleInput, "getMaskedImage"):
184 # assembleInput is a single exposure
185 ccd = assembleInput.getDetector()
187 def getNextExposure(amp):
188 return assembleInput
189 else:
190 raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure")
192 if ccd is None:
193 raise RuntimeError("No ccd detector found")
195 if not self.config.doTrim:
196 outBox = cameraGeomUtils.calcRawCcdBBox(ccd)
197 else:
198 outBox = ccd.getBBox()
199 outExposure = afwImage.ExposureF(outBox)
200 outMI = outExposure.getMaskedImage()
202 if self.config.doTrim:
203 assemble = cameraGeom.assembleAmplifierImage
204 else:
205 assemble = cameraGeom.assembleAmplifierRawImage
207 for amp in ccd:
208 inMI = getNextExposure(amp).getMaskedImage()
209 assemble(outMI, inMI, amp)
210 #
211 # If we are returning an "untrimmed" image (with overscans and
212 # extended register) we need to update the ampInfo table in the
213 # Detector as we've moved the amp images into
214 # place in a single Detector image
215 #
216 if not self.config.doTrim:
217 ccd = cameraGeom.makeUpdatedDetector(ccd)
219 outExposure.setDetector(ccd)
220 self.postprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0]))
222 return outExposure
224 def postprocessExposure(self, outExposure, inExposure):
225 """Set exposure non-image attributes, including wcs and metadata and
226 display exposure (if requested)
228 Call after assembling the pixels
230 @param[in,out] outExposure assembled exposure:
231 - removes unwanted keywords
232 - sets wcs, filter, and detector
233 @param[in] inExposure input exposure
234 """
235 if inExposure.hasWcs():
236 outExposure.setWcs(inExposure.getWcs())
238 exposureMetadata = inExposure.getMetadata()
239 for key in self.allKeysToRemove:
240 if exposureMetadata.exists(key):
241 exposureMetadata.remove(key)
242 outExposure.setMetadata(exposureMetadata)
244 # note: don't copy PhotoCalib, because it is assumed to be unknown in
245 # raw data.
246 outExposure.info.id = inExposure.info.id
247 outExposure.setFilter(inExposure.getFilter())
248 outExposure.getInfo().setVisitInfo(inExposure.getInfo().getVisitInfo())
250 frame = getDebugFrame(self._display, "assembledExposure")
251 if frame:
252 afwDisplay.Display(frame=frame).mtv(outExposure, title="postprocessExposure")