22__all__ = [
"MultibandPixel",
"MultibandImage",
"MultibandMask",
"MultibandMaskedImage"]
26from lsst.geom import Point2I, Box2I, Extent2I
27from .
import Image, ImageF, Mask, MaskPixel, PARENT, LOCAL
28from ..maskedImage
import MaskedImage, MaskedImageF
29from ._slicing
import imageIndicesToNumpy
30from ...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)
63 super().
__init__(filters, singles, bbox=
Box2I(position, Extent2I(1, 1)))
68 assert self.
getBBox().getDimensions() == Extent2I(1, 1)
71 """Data cube array in multiple bands
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]]
119 result._filters = filters
124 def _slice(self, filters, filterIndex, indices):
128class 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")
163 bbox =
Box2I(Point2I(0, 0), Extent2I(array.shape[2], array.shape[1]))
168 self.
_singles_singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype)
for n
in range(len(array))])
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:
231 position=Point2I(sx + self.
x0, sy + self.
y0)
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):
261 sy, sx, bbox = imageIndicesToNumpy(indices[1:], self.
getBBoxgetBBox)
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
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")
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]:
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")
720def tripleFromSingles(cls, filters, singles, **kwargs):
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)
739def tripleFromArrays(cls, filters, image, mask, variance, bbox=None):
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`.
757 bbox =
Box2I(Point2I(0, 0), Extent2I(image.shape[1], image.shape[0]))
763 if variance
is not None:
767 return cls(filters, mImage, mMask, mVariance)
770def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs):
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))
798 return tripleFromSingles(cls, filters, singles)
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
822 return tripleFromSingles(MultibandMaskedImage, filters, singles)
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
838 return makeTripleFromKwargs(MultibandMaskedImage, filters, filterKwargs, singleType, **kwargs)
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 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 makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs)