LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
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 
24 import numpy as np
25 
26 from lsst.geom import Point2I
27 from lsst.afw.geom import SpanSet
28 from lsst.afw.image.image import Mask, Image, MultibandImage, MultibandMaskedImage
29 from lsst.afw.image.maskedImage import MaskedImage
30 
31 from lsst.afw.multiband import MultibandBase
32 from . import Footprint, makeHeavyFootprint
33 
34 
35 def 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 
80 def 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  -------
98  image : `lsst.afw.image.MaskedImage` or `lsst.afw.image.Image`
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.
197  image : `lsst.afw.image.MultibandImage`, or list of `lsst.afw.image.Image`
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
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
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
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
Point< int, 2 > Point2I
Definition: Point.h:321