Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+a3f7a6a005,g07dc498a13+5ab4d22ec3,g0fba68d861+870ee37b31,g1409bbee79+5ab4d22ec3,g1a7e361dbc+5ab4d22ec3,g1fd858c14a+11200c7927,g20f46db602+25d63fd678,g35bb328faa+fcb1d3bbc8,g4d2262a081+cc8af5cafb,g4d39ba7253+6b9d64fe03,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+6b9d64fe03,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8048e755c2+a1301e4c20,g8852436030+a750987b4a,g89139ef638+5ab4d22ec3,g89e1512fd8+a86d53a4aa,g8d6b6b353c+6b9d64fe03,g9125e01d80+fcb1d3bbc8,g989de1cb63+5ab4d22ec3,g9f33ca652e+38ca901d1a,ga9baa6287d+6b9d64fe03,gaaedd4e678+5ab4d22ec3,gabe3b4be73+1e0a283bba,gb1101e3267+aa269f591c,gb58c049af0+f03b321e39,gb90eeb9370+af74afe682,gc741bbaa4f+7f5db660ea,gcf25f946ba+a750987b4a,gd315a588df+b78635c672,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+5839af1903,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,w.2025.11
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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__ = ["MultibandFootprint"]
23
24import numpy as np
25
26from lsst.geom import Point2I
27from lsst.afw.geom import SpanSet
28
29# Need to use the private name of the package for imports here because
30# lsst.afw.image itself imports lsst.afw.detection
31from lsst.afw.image._image import Mask, Image, MultibandImage, MultibandMaskedImage
32from lsst.afw.image._maskedImage import MaskedImage
33
34from lsst.afw.multiband import MultibandBase
35from . import Footprint, makeHeavyFootprint
36
37
38def getSpanSetFromImages(images, thresh=0, xy0=None):
39 """Create a Footprint from a set of Images
40
41 Parameters
42 ----------
43 images : `lsst.afw.image.MultibandImage` or list of `lsst.afw.image.Image`, array
44 Images to extract the footprint from
45 thresh : `float`
46 All pixels above `thresh` will be included in the footprint
47 xy0 : `lsst.geom.Point2I`
48 Location of the minimum value of the images bounding box
49 (if images is an array, otherwise the image bounding box is used).
50
51 Returns
52 -------
53 spans : `lsst.afw.geom.SpanSet`
54 Union of all spans in the images above the threshold
55 imageBBox : `lsst.afw.detection.Box2I`
56 Bounding box for the input images.
57 """
58 # Set the threshold for each band
59 if not hasattr(thresh, "__len__"):
60 thresh = [thresh] * len(images)
61
62 # If images is a list of `afw Image` objects then
63 # merge the SpanSet in each band into a single Footprint
64 if isinstance(images, MultibandBase) or isinstance(images[0], Image):
65 spans = SpanSet()
66 for n, image in enumerate(images):
67 mask = image.array > thresh[n]
68 mask = Mask(mask.astype(np.int32), xy0=image.getBBox().getMin())
69 spans = spans.union(SpanSet.fromMask(mask))
70 imageBBox = images[0].getBBox()
71 else:
72 # Use thresh to detect the pixels above the threshold in each band
73 thresh = np.array(thresh)
74 if xy0 is None:
75 xy0 = Point2I(0, 0)
76 mask = np.any(images > thresh[:, None, None], axis=0)
77 mask = Mask(mask.astype(np.int32), xy0=xy0)
78 spans = SpanSet.fromMask(mask)
79 imageBBox = mask.getBBox()
80 return spans, imageBBox
81
82
84 """Multiband Footprint class
85
86 A `MultibandFootprint` is a collection of HeavyFootprints that have
87 the same `SpanSet` and `peakCatalog` but different flux in each band.
88
89 Parameters
90 ----------
91 filters : `list`
92 List of filter names.
93 singles : `list`
94 A list of single band `HeavyFootprint` objects.
95 Each `HeavyFootprint` should have the same `PeakCatalog`
96 and the same `SpanSet`, however to save CPU cycles there
97 is no internal check for consistency of the peak catalog.
98 """
99 def __init__(self, filters, singles):
100 super().__init__(filters, singles)
101 # Ensure that all HeavyFootprints have the same SpanSet
102 spans = singles[0].getSpans()
103 if not all([heavy.getSpans() == spans for heavy in singles]):
104 raise ValueError("All HeavyFootprints in singles are expected to have the same SpanSet")
105
106 # Assume that all footprints have the same SpanSet and PeakCatalog
107 footprint = Footprint(spans)
108 footprint.setPeakCatalog(singles[0].getPeaks())
109 self._footprint = footprint
110
111 @staticmethod
112 def fromArrays(filters, image, mask=None, variance=None, footprint=None, xy0=None, thresh=0, peaks=None):
113 """Create a `MultibandFootprint` from an `image`, `mask`, `variance`
114
115 Parameters
116 ----------
117 filters : `list`
118 List of filter names.
119 image: array
120 An array to convert into `lsst.afw.detection.HeavyFootprint` objects.
121 Only pixels above the `thresh` value for at least one band
122 will be included in the `SpanSet` and resulting footprints.
123 mask : array
124 Mask for the `image` array.
125 variance : array
126 Variance of the `image` array.
127 footprint : `Footprint`
128 `Footprint` that contains the `SpanSet` and `PeakCatalog`
129 to use for the `HeavyFootprint` in each band.
130 If `footprint` is `None` then the `thresh` is used to create a
131 `Footprint` based on the pixels above the `thresh` value.
132 xy0 : `Point2I`
133 If `image` is an array and `footprint` is `None` then specifying
134 `xy0` gives the location of the minimum `x` and `y` value of the
135 `images`.
136 thresh : `float` or list of floats
137 Threshold in each band (or the same threshold to be used in all bands)
138 to include a pixel in the `SpanSet` of the `MultibandFootprint`.
139 If `Footprint` is not `None` then `thresh` is ignored.
140 peaks : `PeakCatalog`
141 Catalog containing information about the peaks located in the
142 footprints.
143
144 Returns
145 -------
146 result : `MultibandFootprint`
147 MultibandFootprint created from the arrays
148 """
149 # Generate a new Footprint if one has not been specified
150 if footprint is None:
151 spans, imageBBox = getSpanSetFromImages(image, thresh, xy0)
152 footprint = Footprint(spans)
153 else:
154 imageBBox = footprint.getBBox()
155
156 if peaks is not None:
157 footprint.setPeakCatalog(peaks)
158 mMaskedImage = MultibandMaskedImage.fromArrays(filters, image, mask, variance, imageBBox)
159 singles = [makeHeavyFootprint(footprint, maskedImage) for maskedImage in mMaskedImage]
160 return MultibandFootprint(filters, singles)
161
162 @staticmethod
163 def fromImages(filters, image, mask=None, variance=None, footprint=None, thresh=0, peaks=None):
164 """Create a `MultibandFootprint` from an `image`, `mask`, `variance`
165
166 Parameters
167 ----------
168 filters : `list`
169 List of filter names.
170 image : `lsst.afw.image.MultibandImage`, or list of `lsst.afw.image.Image`
171 A `lsst.afw.image.MultibandImage` (or collection of images in each band)
172 to convert into `HeavyFootprint` objects.
173 Only pixels above the `thresh` value for at least one band
174 will be included in the `SpanSet` and resulting footprints.
175 mask : `MultibandMask` or list of `Mask`
176 Mask for the `image`.
177 variance : `lsst.afw.image.MultibandImage`, or list of `lsst.afw.image.Image`
178 Variance of the `image`.
179 thresh : `float` or `list` of floats
180 Threshold in each band (or the same threshold to be used in all bands)
181 to include a pixel in the `SpanSet` of the `MultibandFootprint`.
182 If `Footprint` is not `None` then `thresh` is ignored.
183 peaks : `PeakCatalog`
184 Catalog containing information about the peaks located in the
185 footprints.
186
187 Returns
188 -------
189 result : `MultibandFootprint`
190 MultibandFootprint created from the image, mask, and variance
191 """
192 # Generate a new Footprint if one has not been specified
193 if footprint is None:
194 spans, imageBBox = getSpanSetFromImages(image, thresh)
195 footprint = Footprint(spans)
196
197 if peaks is not None:
198 footprint.setPeakCatalog(peaks)
199 mMaskedImage = MultibandMaskedImage(filters, image, mask, variance)
200 singles = [makeHeavyFootprint(footprint, maskedImage) for maskedImage in mMaskedImage]
201 return MultibandFootprint(filters, singles)
202
203 @staticmethod
204 def fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None):
205 """Create a `MultibandFootprint` from a list of `MaskedImage`
206
207 See `fromImages` for a description of the parameters not listed below
208
209 Parameters
210 ----------
211 maskedImages : `list` of `lsst.afw.image.MaskedImage`
212 MaskedImages to extract the single band heavy footprints from.
213 Like `fromImages`, if a `footprint` is not specified then all
214 pixels above `thresh` will be used, and `peaks` will be added
215 to the `PeakCatalog`.
216
217 Returns
218 -------
219 result : `MultibandFootprint`
220 MultibandFootprint created from the image, mask, and variance
221 """
222 image = [maskedImage.image for maskedImage in maskedImages]
223 mask = [maskedImage.mask for maskedImage in maskedImages]
224 variance = [maskedImage.variance for maskedImage in maskedImages]
225 return MultibandFootprint.fromImages(filters, image, mask, variance, footprint, thresh, peaks)
226
227 def getSpans(self):
228 """Get the full `SpanSet`"""
229 return self._footprint.getSpans()
230
231 @property
232 def footprint(self):
233 """Common SpanSet and peak catalog for the single band footprints"""
234 return self._footprint
235
236 @property
237 def mMaskedImage(self):
238 """MultibandMaskedImage that the footprints present a view into"""
239 return self._mMaskedImage
240
241 @property
242 def spans(self):
243 """`SpanSet` of the `MultibandFootprint`"""
244 return self._footprint.getSpans()
245
246 def getPeaks(self):
247 """Get the `PeakCatalog`"""
248 return self._footprint.getPeaks()
249
250 @property
251 def peaks(self):
252 """`PeakCatalog` of the `MultibandFootprint`"""
253 return self._footprint.getPeaks()
254
255 def _slice(self, filters, filterIndex, indices):
256 """Slice the current object and return the result
257
258 `MultibandFootprint` objects cannot be sliced along the image
259 dimension, so an error is thrown if `indices` has any elements.
260
261 See `Multiband._slice` for a list of the parameters.
262 """
263 if len(indices) > 0:
264 raise IndexError("MultibandFootprints can only be sliced in the filter dimension")
265
266 if isinstance(filterIndex, slice):
267 singles = self.singles[filterIndex]
268 else:
269 singles = [self.singles[idx] for idx in filterIndex]
270
271 return MultibandFootprint(filters, singles)
272
273 def getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage):
274 """Convert a `MultibandFootprint` to a `MultibandImage`
275
276 This returns the heavy footprints converted into an `MultibandImage` or
277 `MultibandMaskedImage` (depending on `imageType`).
278 This might be different than the internal `mMaskedImage` property
279 of the `MultibandFootprint`, as the `mMaskedImage` might contain
280 some non-zero pixels not contained in the footprint but present in
281 the images.
282
283 Parameters
284 ----------
285 bbox : `Box2I`
286 Bounding box of the resulting image.
287 If no bounding box is specified, then the bounding box
288 of the footprint is used.
289 fill : `float`
290 Value to use for any pixel in the resulting image
291 outside of the `SpanSet`.
292 imageType : `type`
293 This should be either a `MultibandMaskedImage`
294 or `MultibandImage` and describes the type of the output image.
295
296 Returns
297 -------
298 result : `MultibandBase`
299 The resulting `MultibandImage` or `MultibandMaskedImage` created
300 from the `MultibandHeavyFootprint`.
301 """
302 if imageType == MultibandMaskedImage:
303 singleType = MaskedImage
304 elif imageType == MultibandImage:
305 singleType = Image
306 else:
307 raise TypeError("Expected imageType to be either MultibandImage or MultibandMaskedImage")
308 maskedImages = [heavy.extractImage(fill, bbox, singleType) for heavy in self.singles]
309 mMaskedImage = imageType.fromImages(self.filters, maskedImages)
310 return mMaskedImage
311
312 def clone(self, deep=True):
313 """Copy the current object
314
315 Parameters
316 ----------
317 deep : `bool`
318 Whether or not to make a deep copy
319
320 Returns
321 -------
322 result : `MultibandFootprint`
323 The cloned footprint.
324 """
325 if deep:
326 footprint = Footprint(self.footprint.getSpans())
327 for peak in self.footprint.getPeaks():
328 footprint.addPeak(peak.getX(), peak.getY(), peak.getValue())
329 mMaskedImage = self.getImage()
330 filters = tuple([f for f in self.filters])
331 result = MultibandFootprint.fromMaskedImages(filters, mMaskedImage, footprint)
332 else:
333 result = MultibandFootprint(self.filters, self.singles)
334 return result
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
fromImages(filters, image, mask=None, variance=None, footprint=None, thresh=0, peaks=None)
Definition multiband.py:163
_slice(self, filters, filterIndex, indices)
Definition multiband.py:255
getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage)
Definition multiband.py:273
fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None)
Definition multiband.py:204
fromArrays(filters, image, mask=None, variance=None, footprint=None, xy0=None, thresh=0, peaks=None)
Definition multiband.py:112
A compact representation of a collection of pixels.
Definition SpanSet.h:78
getSpanSetFromImages(images, thresh=0, xy0=None)
Definition multiband.py:38
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...