Coverage for python/lsst/afw/detection/multiband.py : 17%

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