22 __all__ = [
"MultibandBase"]
 
   24 from abc 
import ABC, abstractmethod
 
   30     """Base class for multiband objects 
   32     The LSST stack has a number of image-like classes that have 
   33     data in multiple bands that are stored as separate objects. 
   34     Analyzing the data can be easier using a Multiband object that 
   35     wraps the underlying data as a single data cube that can be sliced and 
   36     updated as a single object. 
   38     `MultibandBase` is designed to contain the most important universal 
   39     methods for initializing, slicing, and extracting common parameters 
   40     (such as the bounding box or XY0 position) to all of the single band classes, 
   41     as long as derived classes either call the base class `__init__` 
   42     or set the `_filters`, `_singles`, and `_bbox`. 
   49         List of single band objects 
   51         By default `MultibandBase` uses `singles[0].getBBox()` to set 
   52         the bounding box of the multiband 
   54     def __init__(self, filters, singles, bbox=None):
 
   55         self.
_filters = tuple([f 
for f 
in filters])
 
   62                 err = 
"`singles` are required to have the same bounding box, received {0}" 
   63                 raise ValueError(err.format(bboxes))
 
   69         """Copy the current object 
   71         This must be overloaded in a subclass of `MultibandBase` 
   76             Whether or not to make a deep copy 
   80         result: `MultibandBase` 
   81             copy of the instance that inherits from `MultibandBase` 
   87         """List of filter names for the single band objects 
   93         """List of single band objects 
  103         """Minimum coordinate in the bounding box 
  111         X component of XY0 `Point2I.getX()` 
  113         return self.
getBBox().getMinX()
 
  119         Y component of XY0 `Point2I.getY()` 
  121         return self.
getBBox().getMinY()
 
  125         """Minimum (y,x) position 
  127         This is the position of `self.getBBox().getMin()`, 
  128         but available as a tuple for numpy array indexing. 
  130         return (self.
y0, self.
x0)
 
  134         """Width of the images 
  136         return self.
getBBox().getWidth()
 
  140         """Height of the images 
  142         return self.
getBBox().getHeight()
 
  148         """Get a slice of the underlying array 
  150         If only a single filter is specified, 
  151         return the single band object sliced 
  154         if not isinstance(args, tuple):
 
  162         if not isinstance(filterIndex, slice) 
and len(filterIndex) == 1:
 
  164                 return self.
singles[filterIndex[0]][indices[1:]]
 
  165             elif len(indices) == 2:
 
  166                 return self.
singles[filterIndex[0]][indices[1]]
 
  168                 return self.
singles[filterIndex[0]]
 
  170         return self.
_slice(filters=filters, filterIndex=filterIndex, indices=indices[1:])
 
  184     def _filterNamesToIndex(self, filterIndex):
 
  185         """Convert a list of filter names to an index or a slice 
  189         filterIndex: iterable or `object` 
  190             Index to specify a filter or list of filters, 
  191             usually a string or enum. 
  192             For example `filterIndex` can be 
  193             `"R"` or `["R", "G", "B"]` or `[Filter.R, Filter.G, Filter.B]`, 
  194             if `Filter` is an enum. 
  199             Names of the filters in the slice 
  200         filterIndex: `slice` or `list` of `int` 
  201             Index of each filter in `filterNames` in 
  204         if isinstance(filterIndex, slice):
 
  205             if filterIndex.start 
is not None:
 
  206                 start = self.
filters.index(filterIndex.start)
 
  209             if filterIndex.stop 
is not None:
 
  210                 stop = self.
filters.index(filterIndex.stop)
 
  213             filterIndices = slice(start, stop, filterIndex.step)
 
  214             filterNames = self.
filters[filterIndices]
 
  216             if isinstance(filterIndex, str):
 
  217                 filterNames = [filterIndex]
 
  218                 filterIndices = [self.
filters.index(filterIndex)]
 
  222                     filterNames = [f 
for f 
in filterIndex]
 
  224                     filterNames = [filterIndex]
 
  225                 filterIndices = [self.
filters.index(f) 
for f 
in filterNames]
 
  226         return tuple(filterNames), filterIndices
 
  229         """Shift the bounding box but keep the same Extent 
  234             New minimum bounds of the bounding box 
  238             singleObj.setXY0(xy0)
 
  241         """Shift the bounding box but keep the same Extent 
  243         This method is broken until DM-10781 is completed. 
  248             New minimum bounds of the bounding box 
  252         result: `MultibandBase` 
  253             A copy of the object, shifted to `xy0`. 
  255         raise NotImplementedError(
"shiftedBy not implemented until DM-10781")
 
  256         result = self.
clone(
False)
 
  257         result._bbox = 
Box2I(xy0, result._bbox.getDimensions())
 
  258         for singleObj 
in result.singles:
 
  259             singleObj.setXY0(xy0)
 
  263         """Shift a bounding box by an offset, but keep the same Extent 
  265         This method is broken until DM-10781 is completed. 
  270             Amount to shift the bounding box in x and y. 
  274         result: `MultibandBase` 
  275             A copy of the object, shifted by `offset` 
  277         raise NotImplementedError(
"shiftedBy not implemented until DM-10781")
 
  278         xy0 = self.
_bbox.getMin() + offset
 
  282     def _slice(self, filters, filterIndex, indices):
 
  283         """Slice the current object and return the result 
  285         Different inherited classes will handling slicing differently, 
  286         so this method must be overloaded in inherited classes. 
  290         filters: `list` of `str` 
  291             List of filter names for the slice. This is a subset of the 
  292             filters in the parent multiband object 
  293         filterIndex: `list` of `int` or `slice` 
  294             Index along the filter dimension 
  295         indices: `tuple` of remaining indices 
  296             `MultibandBase.__getitem__` separates the first (filter) 
  297             index from the remaining indices, so `indices` is a tuple 
  298             of all of the indices that come after `filter` in the 
  299             `args` passed to `MultibandBase.__getitem__`. 
  304             Sliced version of the current object, which could be the 
  305             same class or a different class depending on the 
  311         result = 
"<{0}, filters={1}, bbox={2}>".
format(
 
  312             self.__class__.__name__, self.filters, self.getBBox().
__repr__())
 
  316         if hasattr(self, 
"array"):
 
  317             return str(self.array)