lsst.afw 22.0.1-43-gf2a78aa89+d93e5fd551
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
28from lsst.afw.image.image import Mask, Image, MultibandImage, MultibandMaskedImage
29from lsst.afw.image.maskedImage import MaskedImage
30
31from lsst.afw.multiband import MultibandBase
32from . import Footprint, makeHeavyFootprint
33
34
35def getSpanSetFromImages(images, thresh=0, xy0=None):
36 """Create a Footprint from a set of Images
37
38 Parameters
39 ----------
40 images : `lsst.afw.image.MultibandImage` or list of `lsst.afw.image.Image`, array
41 Images to extract the footprint from
42 thresh : `float`
43 All pixels above `thresh` will be included in the footprint
44 xy0 : `lsst.geom.Point2I`
45 Location of the minimum value of the images bounding box
46 (if images is an array, otherwise the image bounding box is used).
47
48 Returns
49 -------
50 spans : `lsst.afw.geom.SpanSet`
51 Union of all spans in the images above the threshold
52 imageBBox : `lsst.afw.detection.Box2I`
53 Bounding box for the input images.
54 """
55 # Set the threshold for each band
56 if not hasattr(thresh, "__len__"):
57 thresh = [thresh] * len(images)
58
59 # If images is a list of `afw Image` objects then
60 # merge the SpanSet in each band into a single Footprint
61 if isinstance(images, MultibandBase) or isinstance(images[0], Image):
62 spans = SpanSet()
63 for n, image in enumerate(images):
64 mask = image.array > thresh[n]
65 mask = Mask(mask.astype(np.int32), xy0=image.getBBox().getMin())
66 spans = spans.union(SpanSet.fromMask(mask))
67 imageBBox = images[0].getBBox()
68 else:
69 # Use thresh to detect the pixels above the threshold in each band
70 thresh = np.array(thresh)
71 if xy0 is None:
72 xy0 = Point2I(0, 0)
73 mask = np.any(images > thresh[:, None, None], axis=0)
74 mask = Mask(mask.astype(np.int32), xy0=xy0)
75 spans = SpanSet.fromMask(mask)
76 imageBBox = mask.getBBox()
77 return spans, imageBBox
78
79
80def heavyFootprintToImage(heavy, fill=np.nan, bbox=None, imageType=MaskedImage):
81 """Create an image of a HeavyFootprint
82
83 Parameters
84 ----------
85 heavy : `HeavyFootprint`
86 The HeavyFootprint to insert into the image
87 fill: number
88 Number to fill the pixels in the image that are not
89 contained in `heavy`.
90 bbox : `Box2I`
91 Bounding box of the output image.
92 imageType : `type`
93 This should be either a `MaskedImage` or `Image` and describes
94 the type of the output image.
95
96 Returns
97 -------
99 An image defined by `bbox` and padded with `fill` that
100 contains the projected flux in `heavy`.
101 """
102 if bbox is None:
103 bbox = heavy.getBBox()
104 image = imageType(bbox, dtype=heavy.getImageArray().dtype)
105 image.set(fill)
106 heavy.insert(image)
107 return image
108
109
111 """Multiband Footprint class
112
113 A `MultibandFootprint` is a collection of HeavyFootprints that have
114 the same `SpanSet` and `peakCatalog` but different flux in each band.
115
116 Parameters
117 ----------
118 filters : `list`
119 List of filter names.
120 singles : `list`
121 A list of single band `HeavyFootprint` objects.
122 Each `HeavyFootprint` should have the same `PeakCatalog`
123 and the same `SpanSet`, however to save CPU cycles there
124 is no internal check for consistency of the peak catalog.
125 """
126 def __init__(self, filters, singles):
127 super().__init__(filters, singles)
128 # Ensure that all HeavyFootprints have the same SpanSet
129 spans = singles[0].getSpans()
130 if not all([heavy.getSpans() == spans for heavy in singles]):
131 raise ValueError("All HeavyFootprints in singles are expected to have the same SpanSet")
132
133 # Assume that all footprints have the same SpanSet and PeakCatalog
134 footprint = Footprint(spans)
135 footprint.setPeakCatalog(singles[0].getPeaks())
136 self._footprint_footprint = footprint
137
138 @staticmethod
139 def fromArrays(filters, image, mask=None, variance=None, footprint=None, xy0=None, thresh=0, peaks=None):
140 """Create a `MultibandFootprint` from an `image`, `mask`, `variance`
141
142 Parameters
143 ----------
144 filters : `list`
145 List of filter names.
146 image: array
147 An array to convert into `lsst.afw.detection.HeavyFootprint` objects.
148 Only pixels above the `thresh` value for at least one band
149 will be included in the `SpanSet` and resulting footprints.
150 mask : array
151 Mask for the `image` array.
152 variance : array
153 Variance of the `image` array.
154 footprint : `Footprint`
155 `Footprint` that contains the `SpanSet` and `PeakCatalog`
156 to use for the `HeavyFootprint` in each band.
157 If `footprint` is `None` then the `thresh` is used to create a
158 `Footprint` based on the pixels above the `thresh` value.
159 xy0 : `Point2I`
160 If `image` is an array and `footprint` is `None` then specifying
161 `xy0` gives the location of the minimum `x` and `y` value of the
162 `images`.
163 thresh : `float` or list of floats
164 Threshold in each band (or the same threshold to be used in all bands)
165 to include a pixel in the `SpanSet` of the `MultibandFootprint`.
166 If `Footprint` is not `None` then `thresh` is ignored.
167 peaks : `PeakCatalog`
168 Catalog containing information about the peaks located in the
169 footprints.
170
171 Returns
172 -------
173 result : `MultibandFootprint`
174 MultibandFootprint created from the arrays
175 """
176 # Generate a new Footprint if one has not been specified
177 if footprint is None:
178 spans, imageBBox = getSpanSetFromImages(image, thresh, xy0)
179 footprint = Footprint(spans)
180 else:
181 imageBBox = footprint.getBBox()
182
183 if peaks is not None:
184 footprint.setPeakCatalog(peaks)
185 mMaskedImage = MultibandMaskedImage.fromArrays(filters, image, mask, variance, imageBBox)
186 singles = [makeHeavyFootprint(footprint, maskedImage) for maskedImage in mMaskedImage]
187 return MultibandFootprint(filters, singles)
188
189 @staticmethod
190 def fromImages(filters, image, mask=None, variance=None, footprint=None, thresh=0, peaks=None):
191 """Create a `MultibandFootprint` from an `image`, `mask`, `variance`
192
193 Parameters
194 ----------
195 filters : `list`
196 List of filter names.
198 A `lsst.afw.image.MultibandImage` (or collection of images in each band)
199 to convert into `HeavyFootprint` objects.
200 Only pixels above the `thresh` value for at least one band
201 will be included in the `SpanSet` and resulting footprints.
202 mask : `MultibandMask` or list of `Mask`
203 Mask for the `image`.
204 variance : `lsst.afw.image.MultibandImage`, or list of `lsst.afw.image.Image`
205 Variance of the `image`.
206 thresh : `float` or `list` of floats
207 Threshold in each band (or the same threshold to be used in all bands)
208 to include a pixel in the `SpanSet` of the `MultibandFootprint`.
209 If `Footprint` is not `None` then `thresh` is ignored.
210 peaks : `PeakCatalog`
211 Catalog containing information about the peaks located in the
212 footprints.
213
214 Returns
215 -------
216 result : `MultibandFootprint`
217 MultibandFootprint created from the image, mask, and variance
218 """
219 # Generate a new Footprint if one has not been specified
220 if footprint is None:
221 spans, imageBBox = getSpanSetFromImages(image, thresh)
222 footprint = Footprint(spans)
223
224 if peaks is not None:
225 footprint.setPeakCatalog(peaks)
226 mMaskedImage = MultibandMaskedImage(filters, image, mask, variance)
227 singles = [makeHeavyFootprint(footprint, maskedImage) for maskedImage in mMaskedImage]
228 return MultibandFootprint(filters, singles)
229
230 @staticmethod
231 def fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None):
232 """Create a `MultibandFootprint` from a list of `MaskedImage`
233
234 See `fromImages` for a description of the parameters not listed below
235
236 Parameters
237 ----------
238 maskedImages : `list` of `lsst.afw.image.MaskedImage`
239 MaskedImages to extract the single band heavy footprints from.
240 Like `fromImages`, if a `footprint` is not specified then all
241 pixels above `thresh` will be used, and `peaks` will be added
242 to the `PeakCatalog`.
243
244 Returns
245 -------
246 result : `MultibandFootprint`
247 MultibandFootprint created from the image, mask, and variance
248 """
249 image = [maskedImage.image for maskedImage in maskedImages]
250 mask = [maskedImage.mask for maskedImage in maskedImages]
251 variance = [maskedImage.variance for maskedImage in maskedImages]
252 return MultibandFootprint.fromImages(filters, image, mask, variance, footprint, thresh, peaks)
253
254 def getSpans(self):
255 """Get the full `SpanSet`"""
256 return self._footprint_footprint.getSpans()
257
258 @property
259 def footprint(self):
260 """Common SpanSet and peak catalog for the single band footprints"""
261 return self._footprint_footprint
262
263 @property
264 def mMaskedImage(self):
265 """MultibandMaskedImage that the footprints present a view into"""
266 return self._mMaskedImage
267
268 @property
269 def spans(self):
270 """`SpanSet` of the `MultibandFootprint`"""
271 return self._footprint_footprint.getSpans()
272
273 def getPeaks(self):
274 """Get the `PeakCatalog`"""
275 return self._footprint_footprint.getPeaks()
276
277 @property
278 def peaks(self):
279 """`PeakCatalog` of the `MultibandFootprint`"""
280 return self._footprint_footprint.getPeaks()
281
282 def _slice(self, filters, filterIndex, indices):
283 """Slice the current object and return the result
284
285 `MultibandFootprint` objects cannot be sliced along the image
286 dimension, so an error is thrown if `indices` has any elements.
287
288 See `Multiband._slice` for a list of the parameters.
289 """
290 if len(indices) > 0:
291 raise IndexError("MultibandFootprints can only be sliced in the filter dimension")
292
293 if isinstance(filterIndex, slice):
294 singles = self.singlessingles[filterIndex]
295 else:
296 singles = [self.singlessingles[idx] for idx in filterIndex]
297
298 return MultibandFootprint(filters, singles)
299
300 def getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage):
301 """Convert a `MultibandFootprint` to a `MultibandImage`
302
303 This returns the heavy footprints converted into an `MultibandImage` or
304 `MultibandMaskedImage` (depending on `imageType`).
305 This might be different than the internal `mMaskedImage` property
306 of the `MultibandFootprint`, as the `mMaskedImage` might contain
307 some non-zero pixels not contained in the footprint but present in
308 the images.
309
310 Parameters
311 ----------
312 bbox : `Box2I`
313 Bounding box of the resulting image.
314 If no bounding box is specified, then the bounding box
315 of the footprint is used.
316 fill : `float`
317 Value to use for any pixel in the resulting image
318 outside of the `SpanSet`.
319 imageType : `type`
320 This should be either a `MultibandMaskedImage`
321 or `MultibandImage` and describes the type of the output image.
322
323 Returns
324 -------
325 result : `MultibandBase`
326 The resulting `MultibandImage` or `MultibandMaskedImage` created
327 from the `MultibandHeavyFootprint`.
328 """
329 if imageType == MultibandMaskedImage:
330 singleType = MaskedImage
331 elif imageType == MultibandImage:
332 singleType = Image
333 else:
334 raise TypeError("Expected imageType to be either MultibandImage or MultibandMaskedImage")
335 maskedImages = [heavyFootprintToImage(heavy, fill, bbox, singleType) for heavy in self.singlessingles]
336 mMaskedImage = imageType.fromImages(self.filtersfilters, maskedImages)
337 return mMaskedImage
338
339 def clone(self, deep=True):
340 """Copy the current object
341
342 Parameters
343 ----------
344 deep : `bool`
345 Whether or not to make a deep copy
346
347 Returns
348 -------
349 result : `MultibandFootprint`
350 The cloned footprint.
351 """
352 if deep:
353 footprint = Footprint(self.footprintfootprint.getSpans())
354 for peak in self.footprintfootprint.getPeaks():
355 footprint.addPeak(peak.getX(), peak.getY(), peak.getValue())
356 mMaskedImage = self.getImagegetImage()
357 filters = tuple([f for f in self.filtersfilters])
358 result = MultibandFootprint.fromMaskedImages(filters, mMaskedImage, footprint)
359 else:
360 result = MultibandFootprint(self.filtersfilters, self.singlessingles)
361 return result
A set of pixels in an Image, including those pixels' actual values.
def fromArrays(filters, image, mask=None, variance=None, footprint=None, xy0=None, thresh=0, peaks=None)
Definition: multiband.py:139
def fromImages(filters, image, mask=None, variance=None, footprint=None, thresh=0, peaks=None)
Definition: multiband.py:190
def fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None)
Definition: multiband.py:231
def getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage)
Definition: multiband.py:300
A compact representation of a collection of pixels.
Definition: SpanSet.h:78
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:73
def heavyFootprintToImage(heavy, fill=np.nan, bbox=None, imageType=MaskedImage)
Definition: multiband.py:80
def getSpanSetFromImages(images, thresh=0, xy0=None)
Definition: multiband.py:35
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...
lsst::afw::detection::Footprint Footprint
Definition: Source.h:57