LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
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, hasArrays):
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 hasArrays:
40 # MaskedImage
41 inArrList = inView.getArrays()
42 outArrList = outView.getArrays()
43 else:
44 inArrList = [inView.getArray()]
45 outArrList = [outView.getArray()]
46
47 for inArr, outArr in zip(inArrList, outArrList):
48 # y,x because numpy arrays are transposed w.r.t. afw Images
49 outArr[:] = inArr[ySlice, xSlice]
50
51
52def assembleAmplifierImage(destImage, rawImage, amplifier):
53 """Assemble the amplifier region of an image from a raw image.
54
55 Parameters
56 ----------
58 Assembled image; the region amplifier.getBBox() is overwritten with
59 the assembled amplifier image.
61 Raw image (same type as destImage).
63 Amplifier geometry, with raw amplifier info.
64
65 Raises
66 ------
67 RuntimeError
68 Raised if image types do not match or amplifier has no raw amplifier info.
69 """
70 if type(destImage.Factory) != type(rawImage.Factory): # noqa: E721
71 raise RuntimeError(f"destImage type = {type(destImage.Factory).__name__} != "
72 f"{type(rawImage.Factory).__name__} = rawImage type")
73 inView = rawImage.Factory(rawImage, amplifier.getRawDataBBox())
74 outView = destImage.Factory(destImage, amplifier.getBBox())
75
76 _insertPixelChunk(outView, inView, amplifier,
77 hasattr(rawImage, "getArrays"))
78
79
80def assembleAmplifierRawImage(destImage, rawImage, amplifier):
81 """Assemble the amplifier region of a raw CCD image.
82
83 For most cameras this is a no-op: the raw image already is an assembled
84 CCD image.
85 However, it is useful for camera such as LSST for which each amplifier
86 image is a separate image.
87
88 Parameters
89 ----------
91 CCD Image; the region amplifier.getRawAmplifier().getBBox()
92 is overwritten with the raw amplifier image.
94 Raw image (same type as destImage).
96 Amplifier geometry with raw amplifier info
97
98 Raises
99 ------
100 RuntimeError
101 Raised if image types do not match or amplifier has no raw amplifier info.
102 """
103 if type(destImage.Factory) != type(rawImage.Factory): # noqa: E721
104 raise RuntimeError(f"destImage type = {type(destImage.Factory).__name__} != "
105 f"{type(rawImage.Factory).__name__} = rawImage type")
106 inBBox = amplifier.getRawBBox()
107 inView = rawImage.Factory(rawImage, inBBox)
108 outBBox = amplifier.getRawBBox()
109 outBBox.shift(amplifier.getRawXYOffset())
110 outView = destImage.Factory(destImage, outBBox)
111
112 _insertPixelChunk(outView, inView, amplifier,
113 hasattr(rawImage, "getArrays"))
114
115
117 """Return a Detector that has had the definitions of amplifier geometry
118 updated post assembly.
119
120 Parameters
121 ----------
122 ccd : `lsst.afw.image.Detector`
123 The detector to copy and update.
124 """
125 builder = ccd.rebuild()
126 for amp in builder.getAmplifiers():
127 amp.transform()
128 return builder.finish()
129
130
132 """A class that can extracts single-amplifier subimages from trimmed or
133 untrimmed assembled images and transforms them to a particular orientation
134 and offset.
135
136 Callers who have a in-memory assembled `lsst.afw.image.Exposure` should
137 generally just use the `apply` class method. Other methods can be used to
138 implement subimage loads of on on-disk images (e.g. formatter classes in
139 ``obs_base``) or obtain subsets from other image classes.
140
141 Parameters
142 ----------
143 amplifier : `Amplifier`
144 Amplifier object that identifies the amplifier to load and sets the
145 orientation and offset of the returned subimage.
146 parent_bbox : `lsst.geom.Box2I`
147 Bounding box of the assembled parent image. This must be equal to
148 either ``parent_detector.getBBox()`` or
149 ``parent_detector.getRawBBox()``; which one is used to determine
150 whether the parent image (and hence the amplifier subimages) is
151 trimmed.
152 parent_detector : `Detector`
153 Detector object that describes the parent image.
154 """
155
156 def __init__(self, amplifier, parent_bbox, parent_detector):
157 self._amplifier = amplifier
158 self._parent_detector = parent_detector
159 self._parent_amplifier = self._parent_detector[self._amplifier.getName()]
160 self._is_parent_trimmed = (parent_bbox == self._parent_detector.getBBox())
161 self._amplifier_comparison = self._amplifier.compareGeometry(self._parent_amplifier)
162 if self._is_parent_trimmed:
163 # We only care about the final bounding box; don't check e.g.
164 # overscan regions for consistency.
165 if self._parent_amplifier.getBBox() != self._amplifier.getBBox():
166 raise ValueError(
167 f"The given amplifier's trimmed bounding box ({self._amplifier.getBBox()}) is not the "
168 "same as the trimmed bounding box of the same amplifier in the parent image "
169 f"({self._parent_amplifier.getBBox()})."
170 )
171 else:
172 # Parent is untrimmed, so we need all regions to be consistent
173 # between the amplifiers modulo flips and offsets.
174 if self._amplifier_comparison & self._amplifier_comparison.REGIONS_DIFFER:
175 raise ValueError(
176 "The given amplifier's subregions are fundamentally incompatible with those of the "
177 "parent image's amplifier."
178 )
179
180 @property
181 def subimage_bbox(self):
182 """The bounding box of the target amplifier in the parent image
183 (`lsst.geom.Box2I`).
184 """
185 if self._is_parent_trimmed:
186 return self._parent_amplifier.getBBox()
187 else:
188 return self._parent_amplifier.getRawBBox()
189
190 def transform_subimage(self, subimage):
191 """Transform an already-extracted subimage to match the orientation
192 and offset of the target amplifier.
193
194 Parameters
195 ----------
196 subimage : image-like
197 The subimage to transform; may be any of `lsst.afw.image.Image`,
200
201 Returns
202 -------
203 transformed : image-like
204 Transformed image of the same type as ``subimage``.
205 """
206 from lsst.afw.math import flipImage
207 if hasattr(subimage, "getMaskedImage"):
208 # flipImage doesn't support Exposure natively.
209 # And sadly, there's no way to write to an existing MaskedImage,
210 # so we need to make yet another copy.
211 result = subimage.clone()
212 result.setMaskedImage(
213 flipImage(
214 subimage.getMaskedImage(),
215 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_X,
216 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_Y,
217 )
218 )
219 else:
220 result = flipImage(
221 subimage,
222 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_X,
223 self._amplifier_comparison & self._amplifier_comparison.FLIPPED_Y,
224 )
225 if self._is_parent_trimmed:
226 result.setXY0(self._amplifier.getBBox().getMin())
227 else:
228 result.setXY0(self._amplifier.getRawBBox().getMin() + self._amplifier.getRawXYOffset())
229 return result
230
231 def make_detector(self):
232 """Create a single-amplifier detector that describes the transformed
233 subimage.
234
235 Returns
236 -------
237 detector : `Detector`
238 Detector object with a single amplifier, a trimmed bounding box
239 equal to the amplifier's trimmed bounding box, and no crosstalk.
240 """
241 detector = self._parent_detector.rebuild()
242 detector.clear()
243 detector.append(self._amplifier.rebuild())
244 detector.setBBox(self._amplifier.getBBox())
245 detector.unsetCrosstalk()
246 return detector.finish()
247
248 @classmethod
249 def apply(cls, parent_exposure, amplifier):
250 """Obtain a single-amplifier `lsst.afw.image.Exposure` subimage that
251 masquerades as full-detector image for a single-amp detector.
252
253 Parameters
254 ----------
255 parent_exposure : `lsst.afw.image.Exposure`
256 Parent image to obtain a subset from.
257 `~lsst.afw.image.Exposure.getDetector` must not return `None`.
258 amplifier : `Amplifier`
259 Target amplifier for the subimage. May differ from the amplifier
260 obtained by ``parent_exposure.getDetector()[amplifier.getName()]``
261 only by flips and differences in `~Amplifier.getRawXYOffset`.
262
263 Returns
264 -------
265 subimage : `lsst.afw.image.Exposure`
266 Exposure subimage for the target amplifier, with the
267 orientation and XY0 described by that amplifier, and a single-amp
268 detector holding a copy of that amplifier.
269
270 Notes
271 -----
272 Because we use the target amplifier's bounding box as the bounding box
273 of the detector attached to the returned exposure, other exposure
274 components that are passed through unmodified (e.g. the WCS) should
275 still be valid for the single-amp exposure after it is trimmed and
276 "assembled". Unlike most trimmed+assembled images, however, it will
277 have a nonzero XY0, and code that (incorrectly!) does not pay attention
278 to XY0 may break.
279 """
280 instance = cls(amplifier, parent_bbox=parent_exposure.getBBox(),
281 parent_detector=parent_exposure.getDetector())
282 result = instance.transform_subimage(parent_exposure[instance.subimage_bbox])
283 result.setDetector(instance.make_detector())
284 return result
table::Key< int > type
Definition: Detector.cc:163
def __init__(self, amplifier, parent_bbox, parent_detector)
def apply(cls, parent_exposure, amplifier)
Geometry and electronic information about raw amplifier images.
Definition: Amplifier.h:86
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:74
An integer coordinate rectangle.
Definition: Box.h:55