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)