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.
arrayarray.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).
92 singles = np.copy(self.
singlessingles)
93 position = self.
getBBoxgetBBox().getMin()
97 result._bbox = self.
getBBoxgetBBox()
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.
arrayarray[filterIndex[0]]
119 result._filters = filters
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_singles_singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype)
for n
in range(len(array))])
171 assert isinstance(self.
_bbox_bbox_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.
arrayarray[:] = 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.
arrayarray)
202 array = self.
arrayarray
204 result =
type(self)(self.
filtersfilters, array, bbox)
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_array[tuple(allSlices)]
226 if not isinstance(filterIndex, slice)
and len(filterIndex) == 1:
235 array = self.
_array_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_array[filterIndex, sy, sx] = value.array
267 self.
_array_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.
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])
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.
508 for single
in self.
singlessingles:
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.
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]
532 result = [others]*len(self.
singlessingles)
537 for s, o
in zip(self.
singlessingles, _others):
543 for s, o
in zip(self.
singlessingles, _others):
549 for s, o
in zip(self.
singlessingles, _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)
589 self.
_mask_mask = mask
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.
maskmask
is not None:
609 if self.
variancevariance
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")
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()
640 """Make a copy of the current instance
643 if self.
maskmask
is not None:
647 if self.
variancevariance
is not None:
651 return type(self)(self.
filtersfilters, 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.
imageimage._slice(filters, filterIndex, indices)
659 if self.
maskmask
is not None:
660 mask = self.
_mask_mask._slice(filters, filterIndex, indices)
663 if self.
variancevariance
is not None:
664 variance = self.
_variance_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]:
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"""
703 return self.
_mask_mask
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.
861 image = self.
imageimage
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 getBBox(self, origin=PARENT)
def __setitem__(self, args, value)
def __init__(self, filters, array, singleType, bbox=None)
def clone(self, deep=True)
def fromImages(filters, singles)
def __init__(self, filters, array, bbox=None)
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
def fromMasks(filters, singles)
def getMaskPlaneDict(self)
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
def getPlaneBitMask(self, names)
def __ior__(self, others)
def getNumPlanesMax(self)
def __init__(self, filters, array, bbox=None)
def getNumPlanesUsed(self)
def __iand__(self, others)
def removeMaskPlane(name)
def _getOtherMasks(self, others)
def removeAndClearMaskPlane(self, name, removeFromDefault=False)
def __ixor__(self, others)
def clearAllMaskPlanes(self)
def getMaskPlane(self, key)
def fromArrays(filters, image, mask, variance, bbox=None)
def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs)
def __init__(self, filters, image=None, mask=None, variance=None)
def fromImages(filters, singles)
def __getitem__(self, indices)
def __init__(self, filters, singles, position)
def clone(self, deep=True)
def clone(self, deep=True)
def getBBox(self, origin=PARENT)
def __init__(self, filters, image, mask, variance)
def _filterNamesToIndex(self, filterIndex)
def clone(self, deep=True)
An integer coordinate rectangle.
def makeImageFromSingles(cls, filters, singles)
def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs)
def tripleFromSingles(cls, filters, singles, **kwargs)
def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs)
def tripleFromArrays(cls, filters, image, mask, variance, bbox=None)
def imageIndicesToNumpy(sliceArgs, bboxGetter)
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