LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
LSST Data Management Base Package
Loading...
Searching...
No Matches
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__all__ = ["AssembleCcdTask", "AssembleCcdConfig"]
23
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
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
46class AssembleCcdTask(pipeBase.Task):
47 """Assemble a set of amplifier images into a full detector size set of
48 pixels.
49
50 The keys for removal specified in
51 `lsst.ip.isr.AssembleCcdConfig.keysToRemove` are added to a default set:
52 ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN').
53 """
54 ConfigClass = AssembleCcdConfig
55 _DefaultName = "assembleCcd"
56
57 def __init__(self, **kwargs):
58 pipeBase.Task.__init__(self, **kwargs)
59
60 self.allKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove)
61
62 def assembleCcd(self, assembleInput):
63 """Assemble a set of amps into a single CCD size image.
64
65 Parameters
66 ----------
67 assembleInput : `dict` [`str`, `lsst.afw.image.Exposure`] or \
68 `lsst.afw.image.Exposure`
69 Either a dictionary of amp exposures, or a single exposure
70 containing all raw amps. If a dictionary of amp exposures, the key
71 should be the amp name.
72
73 Returns
74 -------
75 assembledCcd : `lsst.afw.image.Exposure`
76 An exposure of the assembled amp sections.
77
78 Raises
79 ------
80 TypeError
81 Raised if the input exposures to be assembled do not adhere to the
82 required format.
83 RuntimeError
84 Raised if the detector set on the input exposure is not set.
85 """
86 ccd = None
87 if isinstance(assembleInput, dict):
88 # assembleInput is a dictionary of amp name: amp exposure
89
90 # Assume all amps have the same detector, so get the detector from
91 # an arbitrary amp.
92 ccd = next(iter(assembleInput.values())).getDetector()
93
94 def getNextExposure(amp):
95 return assembleInput[amp.getName()]
96 elif hasattr(assembleInput, "getMaskedImage"):
97 # assembleInput is a single exposure
98 ccd = assembleInput.getDetector()
99
100 def getNextExposure(amp):
101 return assembleInput
102 else:
103 raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure")
104
105 if ccd is None:
106 raise RuntimeError("No ccd detector found")
107
108 if not self.config.doTrim:
109 outBox = cameraGeomUtils.calcRawCcdBBox(ccd)
110 else:
111 outBox = ccd.getBBox()
112 outExposure = afwImage.ExposureF(outBox)
113 outMI = outExposure.getMaskedImage()
114
115 if self.config.doTrim:
116 assemble = cameraGeom.assembleAmplifierImage
117 else:
118 assemble = cameraGeom.assembleAmplifierRawImage
119
120 for amp in ccd:
121 inMI = getNextExposure(amp).getMaskedImage()
122 assemble(outMI, inMI, amp)
123 #
124 # If we are returning an "untrimmed" image (with overscans and
125 # extended register) we need to update the ampInfo table in the
126 # Detector as we've moved the amp images into
127 # place in a single Detector image
128 #
129 if not self.config.doTrim:
130 ccd = cameraGeom.makeUpdatedDetector(ccd)
131
132 outExposure.setDetector(ccd)
133 self.postprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0]))
134
135 return outExposure
136
137 def postprocessExposure(self, outExposure, inExposure):
138 """Set exposure non-image attributes, including wcs and metadata and
139 display exposure (if requested).
140
141 Call after assembling the pixels.
142
143 Parameters
144 ----------
145 outExposure : `lsst.afw.image.Exposure`
146 The exposure to modify by copying metadata (after removing unwanted
147 keywords), wcs, filter, and detector from ``inExposure``.
148 inExposure : `lsst.afw.image.Exposure`
149 The input exposure providing metadata, wcs, filter, and detector.
150 """
151 if inExposure.hasWcs():
152 outExposure.setWcs(inExposure.getWcs())
153
154 exposureMetadata = inExposure.getMetadata()
155 for key in self.allKeysToRemove:
156 if exposureMetadata.exists(key):
157 exposureMetadata.remove(key)
158 outExposure.setMetadata(exposureMetadata)
159
160 # note: don't copy PhotoCalib, because it is assumed to be unknown in
161 # raw data.
162 outExposure.info.id = inExposure.info.id
163 outExposure.setFilter(inExposure.getFilter())
164 outExposure.getInfo().setVisitInfo(inExposure.getInfo().getVisitInfo())
165
166 frame = getDebugFrame(self._display, "assembledExposure")
167 if frame:
168 afwDisplay.Display(frame=frame).mtv(outExposure, title="postprocessExposure")
postprocessExposure(self, outExposure, inExposure)