LSST Applications g180d380827+78227d2bc4,g2079a07aa2+86d27d4dc4,g2305ad1205+bdd7851fe3,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3d1719c13e+260d7c3927,g3ddfee87b4+723a6db5f3,g487adcacf7+29e55ea757,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+9443c4b912,g62aa8f1a4b+7e2ea9cd42,g858d7b2824+260d7c3927,g864b0138d7+8498d97249,g95921f966b+dffe86973d,g991b906543+260d7c3927,g99cad8db69+4809d78dd9,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+260d7c3927,gba4ed39666+c2a2e4ac27,gbb8dafda3b+e22341fd87,gbd998247f1+585e252eca,gc120e1dc64+713f94b854,gc28159a63d+c6a8a0fb72,gc3e9b769f7+385ea95214,gcf0d15dbbd+723a6db5f3,gdaeeff99f8+f9a426f77a,ge6526c86ff+fde82a80b9,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,w.2024.18
LSST Data Management Base Package
Loading...
Searching...
No Matches
_assembleImage.py
Go to the documentation of this file.
1# This file is part of afw.
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__ = ['assembleAmplifierImage', 'assembleAmplifierRawImage',
23 'makeUpdatedDetector', 'AmplifierIsolator']
24
25# dict of doFlip: slice
26_SliceDict = {
27 False: slice(None, None, 1),
28 True: slice(None, None, -1),
29}
30
31
32def _insertPixelChunk(outView, inView, amplifier):
33 # For the sake of simplicity and robustness, this code does not short-circuit the case flipX=flipY=False.
34 # However, it would save a bit of time, including the cost of making numpy array views.
35 # If short circuiting is wanted, do it here.
36
37 xSlice = _SliceDict[amplifier.getRawFlipX()]
38 ySlice = _SliceDict[amplifier.getRawFlipY()]
39 if hasattr(inView, "image"):
40 inArrList = (inView.image.array, inView.mask.array, inView.variance.array)
41 outArrList = (outView.image.array, outView.mask.array, outView.variance.array)
42 else:
43 inArrList = [inView.array]
44 outArrList = [outView.array]
45
46 for inArr, outArr in zip(inArrList, outArrList):
47 # y,x because numpy arrays are transposed w.r.t. afw Images
48 outArr[:] = inArr[ySlice, xSlice]
49
50
51def assembleAmplifierImage(destImage, rawImage, amplifier):
52 """Assemble the amplifier region of an image from a raw image.
53
54 Parameters
55 ----------
56 destImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
57 Assembled image; the region amplifier.getBBox() is overwritten with
58 the assembled amplifier image.
59 rawImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
60 Raw image (same type as destImage).
61 amplifier : `lsst.afw.cameraGeom.Amplifier`
62 Amplifier geometry, with raw amplifier info.
63
64 Raises
65 ------
66 RuntimeError
67 Raised if image types do not match or amplifier has no raw amplifier info.
68 """
69 if type(destImage.Factory) != type(rawImage.Factory): # noqa: E721
70 raise RuntimeError(f"destImage type = {type(destImage.Factory).__name__} != "
71 f"{type(rawImage.Factory).__name__} = rawImage type")
72 inView = rawImage.Factory(rawImage, amplifier.getRawDataBBox())
73 outView = destImage.Factory(destImage, amplifier.getBBox())
74
75 _insertPixelChunk(outView, inView, amplifier)
76
77
78def assembleAmplifierRawImage(destImage, rawImage, amplifier):
79 """Assemble the amplifier region of a raw CCD image.
80
81 For most cameras this is a no-op: the raw image already is an assembled
82 CCD image.
83 However, it is useful for camera such as LSST for which each amplifier
84 image is a separate image.
85
86 Parameters
87 ----------
88 destImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
89 CCD Image; the region amplifier.getRawAmplifier().getBBox()
90 is overwritten with the raw amplifier image.
91 rawImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
92 Raw image (same type as destImage).
93 amplifier : `lsst.afw.cameraGeom.Amplifier`
94 Amplifier geometry with raw amplifier info
95
96 Raises
97 ------
98 RuntimeError
99 Raised if image types do not match or amplifier has no raw amplifier info.
100 """
101 if type(destImage.Factory) != type(rawImage.Factory): # noqa: E721
102 raise RuntimeError(f"destImage type = {type(destImage.Factory).__name__} != "
103 f"{type(rawImage.Factory).__name__} = rawImage type")
104 inBBox = amplifier.getRawBBox()
105 inView = rawImage.Factory(rawImage, inBBox)
106 outBBox = amplifier.getRawBBox()
107 outBBox.shift(amplifier.getRawXYOffset())
108 outView = destImage.Factory(destImage, outBBox)
109
110 _insertPixelChunk(outView, inView, amplifier)
111
112
114 """Return a Detector that has had the definitions of amplifier geometry
115 updated post assembly.
116
117 Parameters
118 ----------
119 ccd : `lsst.afw.image.Detector`
120 The detector to copy and update.
121 """
122 builder = ccd.rebuild()
123 for amp in builder.getAmplifiers():
124 amp.transform()
125 return builder.finish()
126
127
129 """A class that can extracts single-amplifier subimages from trimmed or
130 untrimmed assembled images and transforms them to a particular orientation
131 and offset.
132
133 Callers who have a in-memory assembled `lsst.afw.image.Exposure` should
134 generally just use the `apply` class method. Other methods can be used to
135 implement subimage loads of on on-disk images (e.g. formatter classes in
136 ``obs_base``) or obtain subsets from other image classes.
137
138 Parameters
139 ----------
140 amplifier : `Amplifier`
141 Amplifier object that identifies the amplifier to load and sets the
142 orientation and offset of the returned subimage.
143 parent_bbox : `lsst.geom.Box2I`
144 Bounding box of the assembled parent image. This must be equal to
145 either ``parent_detector.getBBox()`` or
146 ``parent_detector.getRawBBox()``; which one is used to determine
147 whether the parent image (and hence the amplifier subimages) is
148 trimmed.
149 parent_detector : `Detector`
150 Detector object that describes the parent image.
151 """
152
153 def __init__(self, amplifier, parent_bbox, parent_detector):
154 self._amplifier = amplifier
155 self._parent_detector = parent_detector
157 self._is_parent_trimmed = (parent_bbox == self._parent_detector.getBBox())
158 self._amplifier_comparison = self._amplifier.compareGeometry(self._parent_amplifier)
159 if self._is_parent_trimmed:
160 # We only care about the final bounding box; don't check e.g.
161 # overscan regions for consistency.
162 if self._parent_amplifier.getBBox() != self._amplifier.getBBox():
163 raise ValueError(
164 f"The given amplifier's trimmed bounding box ({self._amplifier.getBBox()}) is not the "
165 "same as the trimmed bounding box of the same amplifier in the parent image "
166 f"({self._parent_amplifier.getBBox()})."
167 )
168 else:
169 # Parent is untrimmed, so we need all regions to be consistent
170 # between the amplifiers modulo flips and offsets.
171 if self._amplifier_comparison & self._amplifier_comparison.REGIONS_DIFFER:
172 raise ValueError(
173 "The given amplifier's subregions are fundamentally incompatible with those of the "
174 "parent image's amplifier."
175 )
176
177 @property
178 def subimage_bbox(self):
179 """The bounding box of the target amplifier in the parent image
180 (`lsst.geom.Box2I`).
181 """
182 if self._is_parent_trimmed:
183 return self._parent_amplifier.getBBox()
184 else:
185 return self._parent_amplifier.getRawBBox()
186
187 def transform_subimage(self, subimage):
188 """Transform an already-extracted subimage to match the orientation
189 and offset of the target amplifier.
190
191 Parameters
192 ----------
193 subimage : image-like
194 The subimage to transform; may be any of `lsst.afw.image.Image`,
195 `lsst.afw.image.Mask`, `lsst.afw.image.MaskedImage`, and
196 `lsst.afw.image.Exposure`.
197
198 Returns
199 -------
200 transformed : image-like
201 Transformed image of the same type as ``subimage``.
202 """
203 from lsst.afw.math import flipImage
204 if hasattr(subimage, "getMaskedImage"):
205 # flipImage doesn't support Exposure natively.
206 # And sadly, there's no way to write to an existing MaskedImage,
207 # so we need to make yet another copy.
208 result = subimage.clone()
209 result.setMaskedImage(
210 flipImage(
211 subimage.getMaskedImage(),
212 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_X,
213 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_Y,
214 )
215 )
216 else:
217 result = flipImage(
218 subimage,
219 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_X,
220 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_Y,
221 )
222 if self._is_parent_trimmed:
223 result.setXY0(self._amplifier.getBBox().getMin())
224 else:
225 result.setXY0(self._amplifier.getRawBBox().getMin() + self._amplifier.getRawXYOffset())
226 return result
227
228 def make_detector(self):
229 """Create a single-amplifier detector that describes the transformed
230 subimage.
231
232 Returns
233 -------
234 detector : `Detector`
235 Detector object with a single amplifier, a trimmed bounding box
236 equal to the amplifier's trimmed bounding box, and no crosstalk.
237 """
238 detector = self._parent_detector.rebuild()
239 detector.clear()
240 detector.append(self._amplifier.rebuild())
241 detector.setBBox(self._amplifier.getBBox())
242 detector.unsetCrosstalk()
243 return detector.finish()
244
245 @classmethod
246 def apply(cls, parent_exposure, amplifier):
247 """Obtain a single-amplifier `lsst.afw.image.Exposure` subimage that
248 masquerades as full-detector image for a single-amp detector.
249
250 Parameters
251 ----------
252 parent_exposure : `lsst.afw.image.Exposure`
253 Parent image to obtain a subset from.
254 `~lsst.afw.image.Exposure.getDetector` must not return `None`.
255 amplifier : `Amplifier`
256 Target amplifier for the subimage. May differ from the amplifier
257 obtained by ``parent_exposure.getDetector()[amplifier.getName()]``
258 only by flips and differences in `~Amplifier.getRawXYOffset`.
259
260 Returns
261 -------
262 subimage : `lsst.afw.image.Exposure`
263 Exposure subimage for the target amplifier, with the
264 orientation and XY0 described by that amplifier, and a single-amp
265 detector holding a copy of that amplifier.
266
267 Notes
268 -----
269 Because we use the target amplifier's bounding box as the bounding box
270 of the detector attached to the returned exposure, other exposure
271 components that are passed through unmodified (e.g. the WCS) should
272 still be valid for the single-amp exposure after it is trimmed and
273 "assembled". Unlike most trimmed+assembled images, however, it will
274 have a nonzero XY0, and code that (incorrectly!) does not pay attention
275 to XY0 may break.
276 """
277 instance = cls(amplifier, parent_bbox=parent_exposure.getBBox(),
278 parent_detector=parent_exposure.getDetector())
279 result = instance.transform_subimage(parent_exposure[instance.subimage_bbox])
280 result.setDetector(instance.make_detector())
281 return result
__init__(self, amplifier, parent_bbox, parent_detector)
assembleAmplifierImage(destImage, rawImage, amplifier)
assembleAmplifierRawImage(destImage, rawImage, amplifier)
_insertPixelChunk(outView, inView, amplifier)