22 __all__ = [
"MultibandPixel",
"MultibandImage",
"MultibandMask",
"MultibandMaskedImage"]
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
34 """Multiband Pixel class 36 This represent acts as a container for a single pixel 37 (scalar) in multiple bands. 42 Either a list of single band objects or an array of values. 44 List of filter names. If `singles` is an `OrderedDict` or 45 a `MultibandPixel` then this argument is ignored, 46 otherwise it is required. 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 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)
61 singles = np.array(singles, copy=
False)
71 """Data cube array in multiple bands 73 Since `self._singles` is just a 1D array, 74 `array` just returns `self._singles`. 78 def _setArray(self, value):
79 assert value.shape == self.
array.shape
82 array = property(_getArray, _setArray)
85 """Make a copy of the current instance 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). 93 position = self.
getBBox().getMin()
101 """Get a slice of the underlying array 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. 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)
114 if len(filters) == 1
and not isinstance(filterIndex, slice):
116 return self.
array[filterIndex[0]]
118 result = self.
clone(
False)
119 result._filters = filters
120 result._singles = self.
_singles[filterIndex]
124 def _slice(self, filters, filterIndex, indices):
128 class MultibandImageBase(MultibandBase):
129 """Multiband Image class 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. 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. 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. 152 Location of the array in a larger single band image. 153 If `bbox` is `None` then the bounding box is initialized 156 def __init__(self, filters, array, singleType, bbox=None):
158 if len(array) != len(filters):
159 raise ValueError(
"`array` and `filters` must have the same length")
168 self.
_singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype)
for n
in range(len(array))])
171 assert isinstance(self.
_bbox, Box2I)
175 """Data cube array in multiple bands 180 The resulting 3D data cube with shape (filters, y, x). 184 def _setArray(self, value):
185 """Set the values of the array""" 186 self.
array[:] = value
188 array = property(_getArray, _setArray)
191 """Copy the current object 196 Whether or not to make a deep copy 199 array = np.copy(self.
array)
207 def _slice(self, filters, filterIndex, indices):
208 """Slice the current object and return the result 210 See `MultibandBase._slice` for a list of the parameters. 213 if len(indices) == 1:
215 allSlices = [filterIndex, slice(
None), slice(
None)]
221 array = self.
_array[tuple(allSlices)]
226 if not isinstance(filterIndex, slice)
and len(filterIndex) == 1:
235 array = self.
_array[filterIndex]
237 result =
type(self)(filters, array, bbox)
242 result.getBBox().getHeight(),
243 result.getBBox().getWidth()
245 assert result.array.shape == imageShape
250 """Set a subset of the MultibandImage 252 if not isinstance(args, tuple):
263 sy = sx = slice(
None)
264 if hasattr(value,
"array"):
265 self.
_array[filterIndex, sy, sx] = value.array
267 self.
_array[filterIndex, sy, sx] = value
274 elif origin == LOCAL:
276 raise ValueError(
"Unrecognized origin, expected either PARENT or LOCAL")
280 """Construct a MultibandImage from a collection of single band images 285 List of filter names. 287 A list of single band objects. 288 If `array` is not `None`, then `singles` is ignored 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)
298 """Build a MultibandImage from a set of keyword arguments 303 List of filter names. 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. 315 Keyword arguments to initialize a new instance of an 316 inherited class that are the same in all bands. 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)
329 """Multiband Image class 331 See `MultibandImageBase` for a description of the parameters. 334 super().
__init__(filters, array, Image, bbox)
338 """Construct a MultibandImage from a collection of single band images 340 see `fromSingles` for a description of parameters 345 def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
346 """Build a MultibandImage from a set of keyword arguments 348 see `makeImageFromKwargs` for a description of parameters 354 """Multiband Mask class 356 See `MultibandImageBase` for a description of the parameters. 359 super().
__init__(filters, array, Mask, bbox)
363 assert np.all([refMask.getMaskPlaneDict() == m.getMaskPlaneDict()
for m
in self.
singles])
364 assert np.all([refMask.getNumPlanesMax() == m.getNumPlanesMax()
for m
in self.
singles])
365 assert np.all([refMask.getNumPlanesUsed() == m.getNumPlanesUsed()
for m
in self.
singles])
369 """Construct a MultibandImage from a collection of single band images 371 see `fromSingles` for a description of parameters 376 def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
377 """Build a MultibandImage from a set of keyword arguments 379 see `makeImageFromKwargs` for a description of parameters 384 """Get the bit number of a mask in the `MaskPlaneDict` 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. 392 For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask` 398 Name of the key in the `MaskPlaneDict` 403 Bit number for mask `key` 408 """Get the bit number of a mask in the `MaskPlaneDict` 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. 416 For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask` 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`. 426 For example if `MaskPlaneDict("CR")=3` and 427 `MaskPlaneDict("NO_DATA)=8`, then 428 `getPlaneBitMask(("CR", "NO_DATA"))=264` 433 Bit value for all of the combined bits described by `names`. 438 """Maximum number of mask planes available 440 This is required to be the same for all of the single 446 """Number of mask planes used 448 This is required to be the same for all of the single 454 """Dictionary of Mask Plane bit values 460 """Reset the mask plane dictionary 466 """Add a mask to the mask plane 471 Name of the new mask plane 476 Bit value of the mask in the mask plane. 483 """Remove a mask from the mask plane 488 Name of the mask plane to remove 493 """Remove and clear a mask from the mask plane 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 502 Name of the mask plane to remove 503 removeFromDefault : `bool`, optional 504 Whether to remove the mask plane from the default dictionary. 509 single.removeAndClearMaskPlane(name, removeFromDefault=
False)
514 """Clear all the pixels 518 def _getOtherMasks(self, others):
519 """Check if two masks can be combined 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`. 526 if isinstance(others, MultibandMask):
527 if len(self.
singles) != len(others.singles)
or self.
filters != 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]
532 result = [others]*len(self.
singles)
537 for s, o
in zip(self.
singles, _others):
543 for s, o
in zip(self.
singles, _others):
549 for s, o
in zip(self.
singles, _others):
555 """MultibandTripleBase class 557 This is a base class inherited by multiband classes 558 with `image`, `mask`, and `variance` objects, 559 such as `MultibandMaskedImage` and `MultibandExposure`. 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 569 Ignored if `singles` is not `None`. 570 mask : `list` or `MultibandMask` 571 List of `Mask` objects that represent the mask in each bandor 573 Ignored if `singles` is not `None`. 574 variance : `list` or `MultibandImage` 575 List of `Image` objects that represent the variance in each bandor 577 Ignored if `singles` is not `None`. 579 def __init__(self, filters, image, mask, variance):
582 if not isinstance(image, MultibandBase):
583 image = MultibandImage.fromImages(filters, image)
585 mask = MultibandMask.fromMasks(filters, mask)
586 if variance
is not None:
587 variance = MultibandImage.fromImages(filters, variance)
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. 603 New minimum bounds of the bounding box 607 if self.
mask is not None:
613 """Shift the bounding box but keep the same Extent 615 This is different than `MultibandBase.shiftedTo` 616 because the multiband `image`, `mask`, and `variance` objects 617 must all have their bounding boxes updated. 622 New minimum bounds of the bounding box 626 result : `MultibandBase` 627 A copy of the object, shifted to `xy0`. 629 raise NotImplementedError(
"shiftedTo not implemented until DM-10781")
630 result = self.
clone(
False)
631 result._image = result.image.shiftedTo(xy0)
632 if self.
mask is not None:
633 result._mask = result.mask.shiftedTo(xy0)
635 result._variance = result.variance.shiftedTo(xy0)
636 result._bbox = result.image.getBBox()
640 """Make a copy of the current instance 643 if self.
mask is not None:
651 return type(self)(self.
filters, image, mask, variance)
653 def _slice(self, filters, filterIndex, indices):
654 """Slice the current object and return the result 656 See `Multiband._slice` for a list of the parameters. 658 image = self.
image._slice(filters, filterIndex, indices)
659 if self.
mask is not None:
660 mask = self.
_mask._slice(filters, filterIndex, indices)
664 variance = self.
_variance._slice(filters, filterIndex, indices)
669 if isinstance(image, MultibandPixel):
671 assert isinstance(mask, MultibandPixel)
672 if variance
is not None:
673 assert isinstance(variance, MultibandPixel)
674 return (image, mask, variance)
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]])
680 def _verifyUpdate(self, image=None, mask=None, variance=None):
681 """Check that the new image, mask, or variance is valid 683 This basically means checking that the update to the 684 property matches the current bounding box and inherits 685 from the `MultibandBase` class. 687 for prop
in [image, mask, variance]:
689 if prop.getBBox() != self.
getBBox():
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)
697 """The image of the MultibandMaskedImage""" 702 """The mask of the MultibandMaskedImage""" 707 """The variance of the MultibandMaskedImage""" 715 elif origin == LOCAL:
717 raise ValueError(
"Unrecognized origin, expected either PARENT or LOCAL")
721 """Construct a MultibandTriple from a collection of single band objects 726 List of filter names. 728 A list of single band objects. 729 If `array` is not `None`, then `singles` is ignored 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)
740 """Construct a MultibandTriple from a set of arrays 745 List of filter names. 747 Array of image values 751 Array of variance values 753 Location of the array in a larger single band image. 754 This argument is ignored if `singles` is not `None`. 763 if variance
is not None:
767 return cls(filters, mImage, mMask, mVariance)
771 """Build a MultibandImage from a set of keyword arguments 776 List of filter names. 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. 788 Keyword arguments to initialize a new instance of an inherited 789 class that are the same in all bands. 794 if filterKwargs
is not None:
795 for key, value
in filterKwargs:
796 kwargs[key] = value[f]
797 singles.append(singleType(**kwargs))
802 """MultibandMaskedImage class 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). 811 See `MultibandTripleBase` for parameter definitions. 813 def __init__(self, filters, image=None, mask=None, variance=None):
814 super().
__init__(filters, image, mask, variance)
818 """Construct a MultibandImage from a collection of single band images 820 see `tripleFromImages` for a description of parameters 826 """Construct a MultibandMaskedImage from a collection of arrays 828 see `tripleFromArrays` for a description of parameters 830 return tripleFromArrays(MultibandMaskedImage, filters, image, mask, variance, bbox)
833 def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs):
834 """Build a MultibandImage from a set of keyword arguments 836 see `makeTripleFromKwargs` for a description of parameters 840 def _buildSingles(self, image=None, mask=None, variance=None):
841 """Make a new list of single band objects 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. 855 Tuple of `MaskedImage` objects for each band, 856 where the `image`, `mask`, and `variance` of each `single` 857 point to the multiband objects. 867 dtype = image.array.dtype
875 if variance
is not None:
876 _variance = variance[f]
879 singles.append(
MaskedImage(image=_image, mask=_mask, variance=_variance, dtype=dtype))
880 return tuple(singles)
def _filterNamesToIndex(self, filterIndex)
def _getOtherMasks(self, others)
def getPlaneBitMask(self, names)
def __init__(self, filters, array, singleType, bbox=None)
def getMaskPlaneDict(self)
def __init__(self, filters, image, mask, variance)
def fromArrays(filters, image, mask, variance, bbox=None)
def removeMaskPlane(name)
def fromKwargs(filters, filterKwargs, singleType=ImageF, kwargs)
def __ixor__(self, others)
def __iand__(self, others)
def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, kwargs)
def __init__(self, filters, singles, position)
def __init__(self, filters, array, bbox=None)
def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, kwargs)
def tripleFromSingles(cls, filters, singles, kwargs)
def getBBox(self, origin=PARENT)
def fromKwargs(filters, filterKwargs, singleType=ImageF, kwargs)
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
def getBBox(self, origin=PARENT)
def __init__(self, filters, image=None, mask=None, variance=None)
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, kwargs)
def clone(self, deep=True)
def fromImages(filters, singles)
def getMaskPlane(self, key)
def clearAllMaskPlanes(self)
def clone(self, deep=True)
Extent< int, 2 > Extent2I
def __init__(self, filters, array, bbox=None)
def __ior__(self, others)
def fromImages(filters, singles)
def getNumPlanesMax(self)
def __setitem__(self, args, value)
def removeAndClearMaskPlane(self, name, removeFromDefault=False)
def makeImageFromSingles(cls, filters, singles)
def getNumPlanesUsed(self)
def imageIndicesToNumpy(sliceArgs, bboxGetter)
An integer coordinate rectangle.
def __getitem__(self, indices)
def tripleFromArrays(cls, filters, image, mask, variance, bbox=None)
def clone(self, deep=True)
def fromMasks(filters, singles)