LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
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__ = ["MultibandPixel", "MultibandImage", "MultibandMask", "MultibandMaskedImage"]
23 
24 import numpy as np
25 
26 from lsst.geom import Point2I, Box2I, Extent2I
27 from . import Image, ImageF, Mask, MaskPixel, PARENT, LOCAL
28 from ..maskedImage import MaskedImage, MaskedImageF
29 from ._slicing import imageIndicesToNumpy
30 from ...multiband import MultibandBase
31 
32 
34  """Multiband Pixel class
35 
36  This represent acts as a container for a single pixel
37  (scalar) in multiple bands.
38 
39  Parameters
40  ----------
41  singles : `sequence`
42  Either a list of single band objects or an array of values.
43  filters : `list`
44  List of filter names. If `singles` is an `OrderedDict` or
45  a `MultibandPixel` then this argument is ignored,
46  otherwise it is required.
47  position : `Point2I`
48  Location of the pixel in the parent image.
49  Unlike other objects that inherit from `MultibandBase`,
50  `MultibandPixel` objects don't have a full `Box2I`
51  bounding box, since they only contain a single pixel,
52  so the bounding box cannot be inherited from the
53  list of `singles`.
54  """
55  def __init__(self, filters, singles, position):
56  if any([arg is None for arg in [singles, filters, position]]):
57  err = "Expected an array of `singles`, a list of `filters, and a `bbox`"
58  raise NotImplementedError(err)
59 
60  # Make sure that singles is an array
61  singles = np.array(singles, copy=False)
62 
63  super().__init__(filters, singles, bbox=Box2I(position, Extent2I(1, 1)))
64  # In this case we want self.singles to be an array
65  self._singles_singles_singles = singles
66 
67  # Make sure that the bounding box has been setup properly
68  assert self.getBBoxgetBBox().getDimensions() == Extent2I(1, 1)
69 
70  def _getArray(self):
71  """Data cube array in multiple bands
72 
73  Since `self._singles` is just a 1D array,
74  `array` just returns `self._singles`.
75  """
76  return self.singlessingles
77 
78  def _setArray(self, value):
79  assert value.shape == self.arrayarray.shape
80  self._singles_singles_singles[:] = value
81 
82  array = property(_getArray, _setArray)
83 
84  def clone(self, deep=True):
85  """Make a copy of the current instance
86 
87  `MultibandPixel.singles` is an array,
88  so this just makes a copy of the array
89  (as opposed to a view of the parent array).
90  """
91  if deep:
92  singles = np.copy(self.singlessingles)
93  position = self.getBBoxgetBBox().getMin()
94  return MultibandPixel(filters=self.filtersfilters, singles=singles, position=position)
95 
96  result = MultibandPixel(filters=self.filtersfilters, singles=self.singlessingles, position=self.getBBoxgetBBox().getMin())
97  result._bbox = self.getBBoxgetBBox()
98  return result
99 
100  def __getitem__(self, indices):
101  """Get a slice of the underlying array
102 
103  Since a `MultibandPixel` is a scalar in the
104  spatial dimensions, it can only be indexed with
105  a filter name, number, or slice.
106  """
107  if isinstance(indices, tuple):
108  err = ("Too many indices: "
109  "`MultibandPixel has no spatial dimensions and "
110  "only accepts a filterIndex")
111  raise IndexError(err)
112  # Make the index into a valid numpy index or slice
113  filters, filterIndex = self._filterNamesToIndex_filterNamesToIndex(indices)
114  if len(filters) == 1 and not isinstance(filterIndex, slice):
115  # The user only requested a pixel in a single band, so return it
116  return self.arrayarray[filterIndex[0]]
117 
118  result = self.clonecloneclone(False)
119  result._filters = filters
120  result._singles = self._singles_singles_singles[filterIndex]
121  # No need to update the bounding box, since pixels can only be sliced in the filter dimension
122  return result
123 
124  def _slice(self, filters, filterIndex, indices):
125  pass
126 
127 
128 class MultibandImageBase(MultibandBase):
129  """Multiband Image class
130 
131  This class acts as a container for multiple `afw.Image` objects.
132  All images must be contained in the same bounding box,
133  and have the same data type.
134  The data is stored in a 3D array (filters, y, x), and the single
135  band `Image` instances have an internal array that points to the
136  3D multiband array, so that the single band objects and multiband
137  array are always in agreement.
138 
139  Parameters
140  ----------
141  filters : `list`
142  List of filter names.
143  array : 3D numpy array
144  Array (filters, y, x) of multiband data.
145  If this is used to initialize a `MultibandImage`,
146  either `bbox` or `singles` is also required.
147  singleType : `type`
148  Type of the single band object (eg. `Image`, `Mask`) to
149  convert the array into a tuple of single band objects
150  that point to the image array.
151  bbox : `Box2I`
152  Location of the array in a larger single band image.
153  If `bbox` is `None` then the bounding box is initialized
154  at the origin.
155  """
156  def __init__(self, filters, array, singleType, bbox=None):
157  # Create the single band objects from the array
158  if len(array) != len(filters):
159  raise ValueError("`array` and `filters` must have the same length")
160  self._array_array = array
161  self._filters_filters_filters = tuple(filters)
162  if bbox is None:
163  bbox = Box2I(Point2I(0, 0), Extent2I(array.shape[2], array.shape[1]))
164  self._bbox_bbox_bbox = bbox
165 
166  xy0 = self.getXY0getXY0()
167  dtype = array.dtype
168  self._singles_singles_singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype) for n in range(len(array))])
169 
170  # Make sure that all of the parameters have been setup appropriately
171  assert isinstance(self._bbox_bbox_bbox, Box2I)
172  assert len(self.singlessingles) == len(self.filtersfilters)
173 
174  def _getArray(self):
175  """Data cube array in multiple bands
176 
177  Returns
178  -------
179  self._array : array
180  The resulting 3D data cube with shape (filters, y, x).
181  """
182  return self._array_array
183 
184  def _setArray(self, value):
185  """Set the values of the array"""
186  self.arrayarray[:] = value
187 
188  array = property(_getArray, _setArray)
189 
190  def clone(self, deep=True):
191  """Copy the current object
192 
193  Parameters
194  ----------
195  deep : `bool`
196  Whether or not to make a deep copy
197  """
198  if deep:
199  array = np.copy(self.arrayarray)
200  bbox = Box2I(self.getBBoxgetBBoxgetBBox())
201  else:
202  array = self.arrayarray
203  bbox = self.getBBoxgetBBoxgetBBox()
204  result = type(self)(self.filtersfilters, array, bbox)
205  return result
206 
207  def _slice(self, filters, filterIndex, indices):
208  """Slice the current object and return the result
209 
210  See `MultibandBase._slice` for a list of the parameters.
211  """
212  if len(indices) > 0:
213  if len(indices) == 1:
214  indices = indices[0]
215  allSlices = [filterIndex, slice(None), slice(None)]
216  sy, sx, bbox = imageIndicesToNumpy(indices, self.getBBoxgetBBoxgetBBox)
217  if sy is not None:
218  allSlices[-2] = sy
219  if sx is not None:
220  allSlices[-1] = sx
221  array = self._array_array[tuple(allSlices)]
222 
223  # Return a scalar or MultibandPixel
224  # if the image indices are integers
225  if bbox is None:
226  if not isinstance(filterIndex, slice) and len(filterIndex) == 1:
227  return array[0]
228  result = MultibandPixel(
229  singles=array,
230  filters=filters,
231  position=Point2I(sx + self.x0x0, sy + self.y0y0)
232  )
233  return result
234  else:
235  array = self._array_array[filterIndex]
236  bbox = self.getBBoxgetBBoxgetBBox()
237  result = type(self)(filters, array, bbox)
238 
239  # Check that the image and array shapes agree
240  imageShape = (
241  len(result.filters),
242  result.getBBox().getHeight(),
243  result.getBBox().getWidth()
244  )
245  assert result.array.shape == imageShape
246 
247  return result
248 
249  def __setitem__(self, args, value):
250  """Set a subset of the MultibandImage
251  """
252  if not isinstance(args, tuple):
253  indices = (args,)
254  else:
255  indices = args
256 
257  # Return the single band object if the first
258  # index is not a list or slice.
259  filters, filterIndex = self._filterNamesToIndex_filterNamesToIndex(indices[0])
260  if len(indices) > 1:
261  sy, sx, bbox = imageIndicesToNumpy(indices[1:], self.getBBoxgetBBoxgetBBox)
262  else:
263  sy = sx = slice(None)
264  if hasattr(value, "array"):
265  self._array_array[filterIndex, sy, sx] = value.array
266  else:
267  self._array_array[filterIndex, sy, sx] = value
268 
269  def getBBox(self, origin=PARENT):
270  """Bounding box
271  """
272  if origin == PARENT:
273  return self._bbox_bbox_bbox
274  elif origin == LOCAL:
275  return Box2I(Point2I(0, 0), self._bbox_bbox_bbox.getDimensions())
276  raise ValueError("Unrecognized origin, expected either PARENT or LOCAL")
277 
278 
279 def makeImageFromSingles(cls, filters, singles):
280  """Construct a MultibandImage from a collection of single band images
281 
282  Parameters
283  ----------
284  filters : `list`
285  List of filter names.
286  singles : `list`
287  A list of single band objects.
288  If `array` is not `None`, then `singles` is ignored
289  """
290  array = np.array([image.array for image in singles], dtype=singles[0].array.dtype)
291  if not np.all([image.getBBox() == singles[0].getBBox() for image in singles[1:]]):
292  raise ValueError("Single band images did not all have the same bounding box")
293  bbox = singles[0].getBBox()
294  return cls(filters, array, bbox)
295 
296 
297 def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs):
298  """Build a MultibandImage from a set of keyword arguments
299 
300  Parameters
301  ----------
302  filters : `list`
303  List of filter names.
304  singleType : class
305  Class of the single band objects.
306  This is ignored unless `singles` and `array`
307  are both `None`, in which case it is required.
308  filterKwargs : `dict`
309  Keyword arguments to initialize a new instance of an inherited class
310  that are different for each filter.
311  The keys are the names of the arguments and the values
312  should also be dictionaries, with filter names as keys
313  and the value of the argument for a given filter as values.
314  kwargs : `dict`
315  Keyword arguments to initialize a new instance of an
316  inherited class that are the same in all bands.
317  """
318  # Attempt to load a set of images
319  singles = []
320  for f in filters:
321  if filterKwargs is not None:
322  for key, value in filterKwargs:
323  kwargs[key] = value[f]
324  singles.append(singleType(**kwargs))
325  return cls.makeImageFromSingles(filters, singles)
326 
327 
329  """Multiband Image class
330 
331  See `MultibandImageBase` for a description of the parameters.
332  """
333  def __init__(self, filters, array, bbox=None):
334  super().__init__(filters, array, Image, bbox)
335 
336  @staticmethod
337  def fromImages(filters, singles):
338  """Construct a MultibandImage from a collection of single band images
339 
340  see `fromSingles` for a description of parameters
341  """
342  return makeImageFromSingles(MultibandImage, filters, singles)
343 
344  @staticmethod
345  def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
346  """Build a MultibandImage from a set of keyword arguments
347 
348  see `makeImageFromKwargs` for a description of parameters
349  """
350  return makeImageFromKwargs(MultibandImage, filters, filterKwargs, singleType, **kwargs)
351 
352 
354  """Multiband Mask class
355 
356  See `MultibandImageBase` for a description of the parameters.
357  """
358  def __init__(self, filters, array, bbox=None):
359  super().__init__(filters, array, Mask, bbox)
360  # Set Mask specific properties
361  self._refMask_refMask = self._singles_singles_singles[0]
362  refMask = self._refMask_refMask
363  assert np.all([refMask.getMaskPlaneDict() == m.getMaskPlaneDict() for m in self.singlessingles])
364  assert np.all([refMask.getNumPlanesMax() == m.getNumPlanesMax() for m in self.singlessingles])
365  assert np.all([refMask.getNumPlanesUsed() == m.getNumPlanesUsed() for m in self.singlessingles])
366 
367  @staticmethod
368  def fromMasks(filters, singles):
369  """Construct a MultibandImage from a collection of single band images
370 
371  see `fromSingles` for a description of parameters
372  """
373  return makeImageFromSingles(MultibandMask, filters, singles)
374 
375  @staticmethod
376  def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
377  """Build a MultibandImage from a set of keyword arguments
378 
379  see `makeImageFromKwargs` for a description of parameters
380  """
381  return makeImageFromKwargs(MultibandMask, filters, filterKwargs, singleType, **kwargs)
382 
383  def getMaskPlane(self, key):
384  """Get the bit number of a mask in the `MaskPlaneDict`
385 
386  Each `key` in the mask plane has an associated bit value
387  in the mask. This method returns the bit number of the
388  `key` in the `MaskPlaneDict`.
389  This is in contrast to `getPlaneBitMask`, which returns the
390  value of the bit number.
391 
392  For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask`
393  returns `256`.
394 
395  Parameters
396  ----------
397  key : `str`
398  Name of the key in the `MaskPlaneDict`
399 
400  Returns
401  -------
402  bit : `int`
403  Bit number for mask `key`
404  """
405  return self._refMask_refMask.getMaskPlaneDict()[key]
406 
407  def getPlaneBitMask(self, names):
408  """Get the bit number of a mask in the `MaskPlaneDict`
409 
410  Each `key` in the mask plane has an associated bit value
411  in the mask. This method returns the bit number of the
412  `key` in the `MaskPlaneDict`.
413  This is in contrast to `getPlaneBitMask`, which returns the
414  value of the bit number.
415 
416  For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask`
417  returns `256`.
418 
419  Parameters
420  ----------
421  names : `str` or list of `str`
422  Name of the key in the `MaskPlaneDict` or a list of keys.
423  If multiple keys are used, the value returned is the integer
424  value of the number with all of the bit values in `names`.
425 
426  For example if `MaskPlaneDict("CR")=3` and
427  `MaskPlaneDict("NO_DATA)=8`, then
428  `getPlaneBitMask(("CR", "NO_DATA"))=264`
429 
430  Returns
431  -------
432  bit value : `int`
433  Bit value for all of the combined bits described by `names`.
434  """
435  return self._refMask_refMask.getPlaneBitMask(names)
436 
437  def getNumPlanesMax(self):
438  """Maximum number of mask planes available
439 
440  This is required to be the same for all of the single
441  band `Mask` objects.
442  """
443  return self._refMask_refMask.getNumPlanesMax()
444 
445  def getNumPlanesUsed(self):
446  """Number of mask planes used
447 
448  This is required to be the same for all of the single
449  band `Mask` objects.
450  """
451  return self._refMask_refMask.getNumPlanesUsed()
452 
453  def getMaskPlaneDict(self):
454  """Dictionary of Mask Plane bit values
455  """
456  return self._refMask_refMask.getMaskPlaneDict()
457 
458  @staticmethod
460  """Reset the mask plane dictionary
461  """
462  Mask[MaskPixel].clearMaskPlaneDict()
463 
464  @staticmethod
465  def addMaskPlane(name):
466  """Add a mask to the mask plane
467 
468  Parameters
469  ----------
470  name : `str`
471  Name of the new mask plane
472 
473  Returns
474  -------
475  index : `int`
476  Bit value of the mask in the mask plane.
477  """
478  idx = Mask[MaskPixel].addMaskPlane(name)
479  return idx
480 
481  @staticmethod
482  def removeMaskPlane(name):
483  """Remove a mask from the mask plane
484 
485  Parameters
486  ----------
487  name : `str`
488  Name of the mask plane to remove
489  """
490  Mask[MaskPixel].removeMaskPlane(name)
491 
492  def removeAndClearMaskPlane(self, name, removeFromDefault=False):
493  """Remove and clear a mask from the mask plane
494 
495  Clear all pixels of the specified mask and remove the plane from the
496  mask plane dictionary. Also optionally remove the plane from the
497  default dictionary.
498 
499  Parameters
500  ----------
501  name : `str`
502  Name of the mask plane to remove
503  removeFromDefault : `bool`, optional
504  Whether to remove the mask plane from the default dictionary.
505  Default is `False`.
506  """
507  # Clear all masks in MultibandMask but leave in default dict for now
508  for single in self.singlessingles:
509  single.removeAndClearMaskPlane(name, removeFromDefault=False)
510  # Now remove from default dict according to removeFromDefault
511  self._refMask_refMask.removeAndClearMaskPlane(name, removeFromDefault)
512 
514  """Clear all the pixels
515  """
516  self._refMask_refMask.clearAllMaskPlanes()
517 
518  def _getOtherMasks(self, others):
519  """Check if two masks can be combined
520 
521  This method checks that `self` and `others`
522  have the same number of bands, or if
523  others is a single value, creates a list
524  to use for updating all of the `singles`.
525  """
526  if isinstance(others, MultibandMask):
527  if len(self.singlessingles) != len(others.singles) or self.filtersfilters != others.filters:
528  msg = "Both `MultibandMask` objects must have the same number of bands to combine"
529  raise ValueError(msg)
530  result = [s for s in others.singles]
531  else:
532  result = [others]*len(self.singlessingles)
533  return result
534 
535  def __ior__(self, others):
536  _others = self._getOtherMasks_getOtherMasks(others)
537  for s, o in zip(self.singlessingles, _others):
538  s |= o
539  return self
540 
541  def __iand__(self, others):
542  _others = self._getOtherMasks_getOtherMasks(others)
543  for s, o in zip(self.singlessingles, _others):
544  s &= o
545  return self
546 
547  def __ixor__(self, others):
548  _others = self._getOtherMasks_getOtherMasks(others)
549  for s, o in zip(self.singlessingles, _others):
550  s ^= o
551  return self
552 
553 
555  """MultibandTripleBase class
556 
557  This is a base class inherited by multiband classes
558  with `image`, `mask`, and `variance` objects,
559  such as `MultibandMaskedImage` and `MultibandExposure`.
560 
561  Parameters
562  ----------
563  filters : `list`
564  List of filter names. If `singles` is an `OrderedDict`
565  then this argument is ignored, otherwise it is required.
566  image : `list` or `MultibandImage`
567  List of `Image` objects that represent the image in each band or
568  a `MultibandImage`.
569  Ignored if `singles` is not `None`.
570  mask : `list` or `MultibandMask`
571  List of `Mask` objects that represent the mask in each bandor
572  a `MultibandMask`.
573  Ignored if `singles` is not `None`.
574  variance : `list` or `MultibandImage`
575  List of `Image` objects that represent the variance in each bandor
576  a `MultibandImage`.
577  Ignored if `singles` is not `None`.
578  """
579  def __init__(self, filters, image, mask, variance):
580  self._filters_filters_filters = tuple(filters)
581  # Convert single band images into multiband images
582  if not isinstance(image, MultibandBase):
583  image = MultibandImage.fromImages(filters, image)
584  if mask is not None:
585  mask = MultibandMask.fromMasks(filters, mask)
586  if variance is not None:
587  variance = MultibandImage.fromImages(filters, variance)
588  self._image_image = image
589  self._mask_mask = mask
590  self._variance_variance = variance
591 
592  self._singles_singles_singles = self._buildSingles(self._image_image, self._mask_mask, self._variance_variance)
593  self._bbox_bbox_bbox = self.singlessingles[0].getBBox()
594 
595  def setXY0(self, xy0):
596  """Shift the bounding box but keep the same Extent
597  This is different than `MultibandBase.setXY0`
598  because the multiband `image`, `mask`, and `variance` objects
599  must all have their bounding boxes updated.
600  Parameters
601  ----------
602  xy0 : `Point2I`
603  New minimum bounds of the bounding box
604  """
605  super().setXY0(xy0)
606  self.imageimage.setXY0(xy0)
607  if self.maskmask is not None:
608  self.maskmask.setXY0(xy0)
609  if self.variancevariance is not None:
610  self.variancevariance.setXY0(xy0)
611 
612  def shiftedTo(self, xy0):
613  """Shift the bounding box but keep the same Extent
614 
615  This is different than `MultibandBase.shiftedTo`
616  because the multiband `image`, `mask`, and `variance` objects
617  must all have their bounding boxes updated.
618 
619  Parameters
620  ----------
621  xy0 : `Point2I`
622  New minimum bounds of the bounding box
623 
624  Returns
625  -------
626  result : `MultibandBase`
627  A copy of the object, shifted to `xy0`.
628  """
629  raise NotImplementedError("shiftedTo not implemented until DM-10781")
630  result = self.clonecloneclone(False)
631  result._image = result.image.shiftedTo(xy0)
632  if self.maskmask is not None:
633  result._mask = result.mask.shiftedTo(xy0)
634  if self.variancevariance is not None:
635  result._variance = result.variance.shiftedTo(xy0)
636  result._bbox = result.image.getBBox()
637  return result
638 
639  def clone(self, deep=True):
640  """Make a copy of the current instance
641  """
642  image = self.imageimage.clone(deep)
643  if self.maskmask is not None:
644  mask = self.maskmask.clone(deep)
645  else:
646  mask = None
647  if self.variancevariance is not None:
648  variance = self.variancevariance.clone(deep)
649  else:
650  variance = None
651  return type(self)(self.filtersfilters, image, mask, variance)
652 
653  def _slice(self, filters, filterIndex, indices):
654  """Slice the current object and return the result
655 
656  See `Multiband._slice` for a list of the parameters.
657  """
658  image = self.imageimage._slice(filters, filterIndex, indices)
659  if self.maskmask is not None:
660  mask = self._mask_mask._slice(filters, filterIndex, indices)
661  else:
662  mask = None
663  if self.variancevariance is not None:
664  variance = self._variance_variance._slice(filters, filterIndex, indices)
665  else:
666  variance = None
667 
668  # If only a single pixel is selected, return the tuple of MultibandPixels
669  if isinstance(image, MultibandPixel):
670  if mask is not None:
671  assert isinstance(mask, MultibandPixel)
672  if variance is not None:
673  assert isinstance(variance, MultibandPixel)
674  return (image, mask, variance)
675 
676  result = type(self)(filters=filters, image=image, mask=mask, variance=variance)
677  assert all([r.getBBox() == result._bbox for r in [result._mask, result._variance]])
678  return result
679 
680  def _verifyUpdate(self, image=None, mask=None, variance=None):
681  """Check that the new image, mask, or variance is valid
682 
683  This basically means checking that the update to the
684  property matches the current bounding box and inherits
685  from the `MultibandBase` class.
686  """
687  for prop in [image, mask, variance]:
688  if prop is not None:
689  if prop.getBBox() != self.getBBoxgetBBoxgetBBox():
690  raise ValueError("Bounding box does not match the current class")
691  if not isinstance(prop, MultibandBase):
692  err = "image, mask, and variance should all inherit from the MultibandBase class"
693  raise ValueError(err)
694 
695  @property
696  def image(self):
697  """The image of the MultibandMaskedImage"""
698  return self._image_image
699 
700  @property
701  def mask(self):
702  """The mask of the MultibandMaskedImage"""
703  return self._mask_mask
704 
705  @property
706  def variance(self):
707  """The variance of the MultibandMaskedImage"""
708  return self._variance_variance
709 
710  def getBBox(self, origin=PARENT):
711  """Bounding box
712  """
713  if origin == PARENT:
714  return self._bbox_bbox_bbox
715  elif origin == LOCAL:
716  return Box2I(Point2I(0, 0), self._bbox_bbox_bbox.getDimensions())
717  raise ValueError("Unrecognized origin, expected either PARENT or LOCAL")
718 
719 
720 def tripleFromSingles(cls, filters, singles, **kwargs):
721  """Construct a MultibandTriple from a collection of single band objects
722 
723  Parameters
724  ----------
725  filters : `list`
726  List of filter names.
727  singles : `list`
728  A list of single band objects.
729  If `array` is not `None`, then `singles` is ignored
730  """
731  if not np.all([single.getBBox() == singles[0].getBBox() for single in singles[1:]]):
732  raise ValueError("Single band images did not all have the same bounding box")
733  image = MultibandImage.fromImages(filters, [s.image for s in singles])
734  mask = MultibandMask.fromMasks(filters, [s.mask for s in singles])
735  variance = MultibandImage.fromImages(filters, [s.variance for s in singles])
736  return cls(filters, image, mask, variance, **kwargs)
737 
738 
739 def tripleFromArrays(cls, filters, image, mask, variance, bbox=None):
740  """Construct a MultibandTriple from a set of arrays
741 
742  Parameters
743  ----------
744  filters : `list`
745  List of filter names.
746  image : array
747  Array of image values
748  mask : array
749  Array of mask values
750  variance : array
751  Array of variance values
752  bbox : `Box2I`
753  Location of the array in a larger single band image.
754  This argument is ignored if `singles` is not `None`.
755  """
756  if bbox is None:
757  bbox = Box2I(Point2I(0, 0), Extent2I(image.shape[1], image.shape[0]))
758  mImage = MultibandImage(filters, image, bbox)
759  if mask is not None:
760  mMask = MultibandMask(filters, mask, bbox)
761  else:
762  mMask = None
763  if variance is not None:
764  mVariance = MultibandImage(filters, variance, bbox)
765  else:
766  mVariance = None
767  return cls(filters, mImage, mMask, mVariance)
768 
769 
770 def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs):
771  """Build a MultibandImage from a set of keyword arguments
772 
773  Parameters
774  ----------
775  filters : `list`
776  List of filter names.
777  singleType : `class`
778  Class of the single band objects.
779  This is ignored unless `singles` and `array`
780  are both `None`, in which case it is required.
781  filterKwargs : `dict`
782  Keyword arguments to initialize a new instance of an inherited class
783  that are different for each filter.
784  The keys are the names of the arguments and the values
785  should also be dictionaries, with filter names as keys
786  and the value of the argument for a given filter as values.
787  kwargs : `dict`
788  Keyword arguments to initialize a new instance of an inherited
789  class that are the same in all bands.
790  """
791  # Attempt to load a set of images
792  singles = []
793  for f in filters:
794  if filterKwargs is not None:
795  for key, value in filterKwargs:
796  kwargs[key] = value[f]
797  singles.append(singleType(**kwargs))
798  return tripleFromSingles(cls, filters, singles)
799 
800 
802  """MultibandMaskedImage class
803 
804  This class acts as a container for multiple `afw.MaskedImage` objects.
805  All masked images must have the same bounding box, and the associated
806  images must all have the same data type.
807  The `image`, `mask`, and `variance` are all stored separately into
808  a `MultibandImage`, `MultibandMask`, and `MultibandImage` respectively,
809  which each have their own internal 3D arrays (filter, y, x).
810 
811  See `MultibandTripleBase` for parameter definitions.
812  """
813  def __init__(self, filters, image=None, mask=None, variance=None):
814  super().__init__(filters, image, mask, variance)
815 
816  @staticmethod
817  def fromImages(filters, singles):
818  """Construct a MultibandImage from a collection of single band images
819 
820  see `tripleFromImages` for a description of parameters
821  """
822  return tripleFromSingles(MultibandMaskedImage, filters, singles)
823 
824  @staticmethod
825  def fromArrays(filters, image, mask, variance, bbox=None):
826  """Construct a MultibandMaskedImage from a collection of arrays
827 
828  see `tripleFromArrays` for a description of parameters
829  """
830  return tripleFromArrays(MultibandMaskedImage, filters, image, mask, variance, bbox)
831 
832  @staticmethod
833  def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs):
834  """Build a MultibandImage from a set of keyword arguments
835 
836  see `makeTripleFromKwargs` for a description of parameters
837  """
838  return makeTripleFromKwargs(MultibandMaskedImage, filters, filterKwargs, singleType, **kwargs)
839 
840  def _buildSingles(self, image=None, mask=None, variance=None):
841  """Make a new list of single band objects
842 
843  Parameters
844  ----------
845  image : `MultibandImage`
846  `MultibandImage` object that represent the image in each band.
847  mask : `MultibandMask`
848  `MultibandMask` object that represent the mask in each band.
849  variance : `MultibandImage`
850  `MultibandImage` object that represent the variance in each band.
851 
852  Returns
853  -------
854  singles : `tuple`
855  Tuple of `MaskedImage` objects for each band,
856  where the `image`, `mask`, and `variance` of each `single`
857  point to the multiband objects.
858  """
859  singles = []
860  if image is None:
861  image = self.imageimage
862  if mask is None:
863  mask = self.maskmask
864  if variance is None:
865  variance = self.variancevariance
866 
867  dtype = image.array.dtype
868  singles = []
869  for f in self.filtersfilters:
870  _image = image[f]
871  if mask is not None:
872  _mask = mask[f]
873  else:
874  _mask = None
875  if variance is not None:
876  _variance = variance[f]
877  else:
878  _variance = None
879  singles.append(MaskedImage(image=_image, mask=_mask, variance=_variance, dtype=dtype))
880  return tuple(singles)
table::Key< int > type
Definition: Detector.cc:163
def __init__(self, filters, array, singleType, bbox=None)
Definition: _multiband.py:156
def __init__(self, filters, array, bbox=None)
Definition: _multiband.py:333
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:345
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:376
def __init__(self, filters, array, bbox=None)
Definition: _multiband.py:358
def removeAndClearMaskPlane(self, name, removeFromDefault=False)
Definition: _multiband.py:492
def fromArrays(filters, image, mask, variance, bbox=None)
Definition: _multiband.py:825
def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs)
Definition: _multiband.py:833
def __init__(self, filters, image=None, mask=None, variance=None)
Definition: _multiband.py:813
def __init__(self, filters, singles, position)
Definition: _multiband.py:55
def __init__(self, filters, image, mask, variance)
Definition: _multiband.py:579
def _filterNamesToIndex(self, filterIndex)
Definition: multiband.py:184
def clone(self, deep=True)
Definition: multiband.py:68
An integer coordinate rectangle.
Definition: Box.h:55
def makeImageFromSingles(cls, filters, singles)
Definition: _multiband.py:279
def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs)
Definition: _multiband.py:770
def tripleFromSingles(cls, filters, singles, **kwargs)
Definition: _multiband.py:720
def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:297
def tripleFromArrays(cls, filters, image, mask, variance, bbox=None)
Definition: _multiband.py:739
def imageIndicesToNumpy(sliceArgs, bboxGetter)
Definition: _slicing.py:202
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
Extent< int, 2 > Extent2I
Definition: Extent.h:397
Point< int, 2 > Point2I
Definition: Point.h:321