LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
_multiband.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# (http://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 <http://www.gnu.org/licenses/>.
21
22__all__ = ["MultibandExposure", "computePsfImage", "IncompleteDataError"]
23
24import numpy as np
25
26from lsst.geom import Point2D, Point2I, Box2I
27from lsst.pex.exceptions import InvalidParameterError
28from . import Exposure, ExposureF
29from ..utils import projectImage
30from .._image._multiband import MultibandTripleBase, MultibandPixel, MultibandImage
31from .._image._multiband import tripleFromSingles, tripleFromArrays, makeTripleFromKwargs
32from .._maskedImage import MaskedImage
33
34
35class IncompleteDataError(Exception):
36 """The PSF could not be computed due to incomplete data
37
38 Attributes
39 ----------
40 missingBands: `list[str]`
41 The bands for which the PSF could not be calculated.
42 position: `Point2D`
43 The point at which the PSF could not be calcualted in the
44 missing bands.
45 partialPsf: `MultibandImage`
46 The image of the PSF using only the bands that successfully
47 computed a PSF image.
48
49 Parameters
50 ----------
51 bands : `list` of `str`
52 The full list of bands in the `MultibandExposure` generating
53 the PSF.
54 """
55 def __init__(self, bands, position, partialPsf):
56 missingBands = [band for band in bands if band not in partialPsf.filters]
57
58 self.missingBands = missingBands
59 self.position = position
60 self.partialPsf = partialPsf
61 message = f"Failed to compute PSF at {position} in {missingBands}"
62 super().__init__(message)
63
64
65def computePsfImage(psfModels, position, useKernelImage=True):
66 """Get a multiband PSF image
67
68 The PSF Image or PSF Kernel Image is computed for each band
69 and combined into a (filter, y, x) array.
70
71 Parameters
72 ----------
73 psfModels : `dict[str, lsst.afw.detection.Psf]`
74 The list of PSFs in each band.
75 position : `Point2D` or `tuple`
76 Coordinates to evaluate the PSF.
77 useKernelImage: `bool`
78 Execute ``Psf.computeKernelImage`` when ``True`,
79 ``PSF/computeImage`` when ``False``.
80
81 Returns
82 -------
83 psfs: `lsst.afw.image.MultibandImage`
84 The multiband PSF image.
85 """
86 psfs = {}
87 # Make the coordinates into a Point2D (if necessary)
88 if not isinstance(position, Point2D):
89 position = Point2D(position[0], position[1])
90
91 incomplete = False
92
93 for band, psfModel in psfModels.items():
94 try:
95 if useKernelImage:
96 psf = psfModel.computeKernelImage(position)
97 else:
98 psf = psfModel.computeImage(position)
99 psfs[band] = psf
100 except InvalidParameterError:
101 incomplete = True
102
103 left = np.min([psf.getBBox().getMinX() for psf in psfs.values()])
104 bottom = np.min([psf.getBBox().getMinY() for psf in psfs.values()])
105 right = np.max([psf.getBBox().getMaxX() for psf in psfs.values()])
106 top = np.max([psf.getBBox().getMaxY() for psf in psfs.values()])
107 bbox = Box2I(Point2I(left, bottom), Point2I(right, top))
108
109 psf_images = [projectImage(psf, bbox) for psf in psfs.values()]
110
111 mPsf = MultibandImage.fromImages(list(psfs.keys()), psf_images)
112
113 if incomplete:
114 raise IncompleteDataError(list(psfModels.keys()), position, mPsf)
115
116 return mPsf
117
118
120 """MultibandExposure class
121
122 This class acts as a container for multiple `afw.Exposure` objects.
123 All exposures must have the same bounding box, and the associated
124 images must all have the same data type.
125
126 See `MultibandTripleBase` for parameter definitions.
127 """
128 def __init__(self, filters, image, mask, variance, psfs=None):
129 super().__init__(filters, image, mask, variance)
130 if psfs is not None:
131 for psf, exposure in zip(psfs, self.singlessingles):
132 exposure.setPsf(psf)
133
134 @staticmethod
135 def fromExposures(filters, singles):
136 """Construct a MultibandImage from a collection of single band images
137
138 see `tripleFromExposures` for a description of parameters
139 """
140 psfs = [s.getPsf() for s in singles]
141 return tripleFromSingles(MultibandExposure, filters, singles, psfs=psfs)
142
143 @staticmethod
144 def fromArrays(filters, image, mask, variance, bbox=None):
145 """Construct a MultibandExposure from a collection of arrays
146
147 see `tripleFromArrays` for a description of parameters
148 """
149 return tripleFromArrays(MultibandExposure, filters, image, mask, variance, bbox)
150
151 @staticmethod
152 def fromKwargs(filters, filterKwargs, singleType=ExposureF, **kwargs):
153 """Build a MultibandImage from a set of keyword arguments
154
155 see `makeTripleFromKwargs` for a description of parameters
156 """
157 return makeTripleFromKwargs(MultibandExposure, filters, filterKwargs, singleType, **kwargs)
158
159 def _buildSingles(self, image=None, mask=None, variance=None):
160 """Make a new list of single band objects
161
162 Parameters
163 ----------
164 image: `list`
165 List of `Image` objects that represent the image in each band.
166 mask: `list`
167 List of `Mask` objects that represent the mask in each band.
168 variance: `list`
169 List of `Image` objects that represent the variance in each band.
170
171 Returns
172 -------
173 singles: tuple
174 Tuple of `MaskedImage` objects for each band,
175 where the `image`, `mask`, and `variance` of each `single`
176 point to the multiband objects.
177 """
178 singles = []
179 if image is None:
180 image = self.image
181 if mask is None:
182 mask = self.mask
183 if variance is None:
184 variance = self.variance
185
186 dtype = image.array.dtype
187 for f in self.filtersfiltersfilters:
188 maskedImage = MaskedImage(image=image[f], mask=mask[f], variance=variance[f], dtype=dtype)
189 single = Exposure(maskedImage, dtype=dtype)
190 singles.append(single)
191 return tuple(singles)
192
193 @staticmethod
194 def fromButler(butler, bands, *args, **kwargs):
195 """Load a multiband exposure from a butler
196
197 Because each band is stored in a separate exposure file,
198 this method can be used to load all of the exposures for
199 a given set of bands
200
201 Parameters
202 ----------
203 butler: `lsst.daf.butler.Butler`
204 Butler connection to use to load the single band
205 calibrated images
206 bands: `list` or `str`
207 List of names for each band
208 args: `list`
209 Arguments to the Butler.
210 kwargs: `dict`
211 Keyword arguments to pass to the Butler
212 that are the same in all bands.
213
214 Returns
215 -------
216 result: `MultibandExposure`
217 The new `MultibandExposure` created by combining all of the
218 single band exposures.
219 """
220 # Load the Exposure in each band
221 exposures = []
222 for band in bands:
223 exposures.append(butler.get(*args, band=band, **kwargs))
224 return MultibandExposure.fromExposures(bands, exposures)
225
226 def computePsfKernelImage(self, position):
227 """Get a multiband PSF image
228
229 The PSF Kernel Image is computed for each band
230 and combined into a (filter, y, x) array and stored
231 as `self._psfImage`.
232 The result is not cached, so if the same PSF is expected
233 to be used multiple times it is a good idea to store the
234 result in another variable.
235
236 Parameters
237 ----------
238 position: `Point2D` or `tuple`
239 Coordinates to evaluate the PSF.
240
241 Returns
242 -------
243 self._psfImage: array
244 The multiband PSF image.
245 """
246 return computePsfImage(
247 psfModels=self.getPsfs(),
248 position=position,
249 useKernelImage=True,
250 )
251
252 def computePsfImage(self, position=None):
253 """Get a multiband PSF image
254
255 The PSF Kernel Image is computed for each band
256 and combined into a (filter, y, x) array and stored
257 as `self._psfImage`.
258 The result is not cached, so if the same PSF is expected
259 to be used multiple times it is a good idea to store the
260 result in another variable.
261
262 Parameters
263 ----------
264 position: `Point2D` or `tuple`
265 Coordinates to evaluate the PSF. If `position` is `None`
266 then `Psf.getAveragePosition()` is used.
267
268 Returns
269 -------
270 self._psfImage: array
271 The multiband PSF image.
272 """
273 return computePsfImage(
274 psfModels=self.getPsfs(),
275 position=position,
276 useKernelImage=True,
277 )
278
279 def getPsfs(self):
280 """Extract the PSF model in each band
281
282 Returns
283 -------
284 psfs : `dict` of `lsst.afw.detection.Psf`
285 The PSF in each band
286 """
287 return {band: self[band].getPsf() for band in self.filtersfiltersfilters}
288
289 def _slice(self, filters, filterIndex, indices):
290 """Slice the current object and return the result
291
292 See `Multiband._slice` for a list of the parameters.
293 This overwrites the base method to attach the PSF to
294 each individual exposure.
295 """
296 image = self.image._slice(filters, filterIndex, indices)
297 if self.mask is not None:
298 mask = self._mask._slice(filters, filterIndex, indices)
299 else:
300 mask = None
301 if self.variance is not None:
302 variance = self._variance._slice(filters, filterIndex, indices)
303 else:
304 variance = None
305
306 # If only a single pixel is selected, return the tuple of MultibandPixels
307 if isinstance(image, MultibandPixel):
308 if mask is not None:
309 assert isinstance(mask, MultibandPixel)
310 if variance is not None:
311 assert isinstance(variance, MultibandPixel)
312 return (image, mask, variance)
313
314 _psfs = self.getPsfs()
315 psfs = [_psfs[band] for band in filters]
316
317 result = MultibandExposure(
318 filters=filters,
319 image=image,
320 mask=mask,
321 variance=variance,
322 psfs=psfs,
323 )
324
325 assert all([r.getBBox() == result._bbox for r in [result._mask, result._variance]])
326 return result
_buildSingles(self, image=None, mask=None, variance=None)
__init__(self, filters, image, mask, variance, psfs=None)
fromArrays(filters, image, mask, variance, bbox=None)
fromKwargs(filters, filterKwargs, singleType=ExposureF, **kwargs)
_slice(self, filters, filterIndex, indices)
An integer coordinate rectangle.
Definition Box.h:55
computePsfImage(psfModels, position, useKernelImage=True)
Definition _multiband.py:65