LSSTApplications  18.0.0+53,19.0.0,19.0.0+1,19.0.0+15,19.0.0+16,19.0.0+18,19.0.0+23,19.0.0+3,19.0.0-1-g20d9b18+9,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+9,19.0.0-1-g6fe20d0+2,19.0.0-1-g8c57eb9+9,19.0.0-1-gbfe0924,19.0.0-1-gdc0e4a7+14,19.0.0-1-ge272bc4+9,19.0.0-1-ge3aa853+1,19.0.0-14-gbb28fe44,19.0.0-16-g8258e2a,19.0.0-2-g0d9f9cd+16,19.0.0-2-g260436e,19.0.0-2-g9b11441+3,19.0.0-2-gd955cfd+22,19.0.0-3-g6513920,19.0.0-3-gc4f6e04,19.0.0-4-g41ffa1d+2,19.0.0-4-g725f80e+18,19.0.0-4-g75300c1e,19.0.0-4-ga8eba22,19.0.0-5-g0745e3f,19.0.0-6-g6637c4fb6,19.0.0-6-gb6b8b0a,19.0.0-7-gea0a0fe+5,w.2020.03
LSSTDataManagementBasePackage
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 import Mask, Image, MaskedImage, MultibandImage, MultibandMaskedImage
29 from lsst.afw.multiband import MultibandBase
30 from . import Footprint, makeHeavyFootprint
31 
32 
33 def getSpanSetFromImages(images, thresh=0, xy0=None):
34  """Create a Footprint from a set of Images
35 
36  Parameters
37  ----------
38  images: `MultibandImage` or list of `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: `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).
45 
46  Returns
47  -------
48  spans: `SpanSet`
49  Union of all spans in the images above the threshold
50  imageBBox: `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)
56 
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
76 
77 
78 def heavyFootprintToImage(heavy, fill=np.nan, bbox=None, imageType=MaskedImage):
79  """Create an image of a HeavyFootprint
80 
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.
93 
94  Returns
95  -------
96  image: `MaskedImage` or `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
106 
107 
109  """Multiband Footprint class
110 
111  A `MultibandFootprint` is a collection of HeavyFootprints that have
112  the same `SpanSet` and `peakCatalog` but different flux in each band.
113 
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")
130 
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
135 
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`
139 
140  Parameters
141  ----------
142  filters: `list`
143  List of filter names.
144  image: array
145  An array to convert into `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.
168 
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()
180 
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)
186 
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`
190 
191  Parameters
192  ----------
193  filters: list
194  List of filter names.
195  image: `MultibandImage`, or list of `Image`
196  A `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: `MultibandImage`, or list of `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.
211 
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)
221 
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)
227 
228  @staticmethod
229  def fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None):
230  """Create a `MultibandFootprint` from a list of `MaskedImage`
231 
232  See `fromImages` for a description of the parameters not listed below
233 
234  Parameters
235  ----------
236  maskedImages: `list` of `MaskedImage`
237  MaskedImages to extract the single band heavy footprints from.
238  Like `fromImage`, if a `footprint` is not specified then all
239  pixels above `thresh` will be used, and `peaks` will be added
240  to the `PeakCatalog`.
241 
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)
251 
252  def getSpans(self):
253  """Get the full `SpanSet`"""
254  return self._footprint.getSpans()
255 
256  @property
257  def footprint(self):
258  """Common SpanSet and peak catalog for the single band footprints"""
259  return self._footprint
260 
261  @property
262  def mMaskedImage(self):
263  """MultibandMaskedImage that the footprints present a view into"""
264  return self._mMaskedImage
265 
266  @property
267  def spans(self):
268  """`SpanSet` of the `MultibandFootprint`"""
269  return self._footprint.getSpans()
270 
271  def getPeaks(self):
272  """Get the `PeakCatalog`"""
273  return self._footprint.getPeaks()
274 
275  @property
276  def peaks(self):
277  """`PeakCatalog` of the `MultibandFootprint`"""
278  return self._footprint.getPeaks()
279 
280  def _slice(self, filters, filterIndex, indices):
281  """Slice the current object and return the result
282 
283  `MultibandFootprint` objects cannot be sliced along the image
284  dimension, so an error is thrown if `indices` has any elements.
285 
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")
290 
291  if isinstance(filterIndex, slice):
292  singles = self.singles[filterIndex]
293  else:
294  singles = [self.singles[idx] for idx in filterIndex]
295 
296  return MultibandFootprint(filters, singles)
297 
298  def getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage):
299  """Convert a `MultibandFootprint` to a `MultibandImage`
300 
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.
307 
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.
320 
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
336 
337  def clone(self, deep=True):
338  """Copy the current object
339 
340  Parameters
341  ----------
342  deep: `bool`
343  Whether or not to make a deep copy
344 
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
def heavyFootprintToImage(heavy, fill=np.nan, bbox=None, imageType=MaskedImage)
Definition: multiband.py:78
def getSpanSetFromImages(images, thresh=0, xy0=None)
Definition: multiband.py:33
A compact representation of a collection of pixels.
Definition: SpanSet.h:77
def fromArrays(filters, image, mask=None, variance=None, footprint=None, xy0=None, thresh=0, peaks=None)
Definition: multiband.py:137
def fromImages(filters, image, mask=None, variance=None, footprint=None, thresh=0, peaks=None)
Definition: multiband.py:188
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
Point< int, 2 > Point2I
Definition: Point.h:321
def fromMaskedImages(filters, maskedImages, footprint=None, thresh=0, peaks=None)
Definition: multiband.py:229
lsst::afw::detection::Footprint Footprint
Definition: Source.h:59
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def getImage(self, bbox=None, fill=np.nan, imageType=MultibandMaskedImage)
Definition: multiband.py:298
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...