Coverage for python/lsst/afw/image/_image/_multiband.py: 17%
325 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-15 02:29 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-15 02:29 -0700
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22__all__ = ["MultibandPixel", "MultibandImage", "MultibandMask", "MultibandMaskedImage"]
24import numpy as np
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
33class MultibandPixel(MultibandBase):
34 """Multiband Pixel class
36 This represent acts as a container for a single pixel
37 (scalar) in multiple bands.
39 Parameters
40 ----------
41 singles : `sequence`
42 Either a list of single band objects or an array of values.
43 filters : `list`
44 List of filter names. If `singles` is an `OrderedDict` or
45 a `MultibandPixel` then this argument is ignored,
46 otherwise it is required.
47 position : `Point2I`
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
53 list of `singles`.
54 """
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)
60 # Make sure that singles is an array
61 singles = np.array(singles, copy=False)
63 super().__init__(filters, singles, bbox=Box2I(position, Extent2I(1, 1)))
64 # In this case we want self.singles to be an array
65 self._singles = singles
67 # Make sure that the bounding box has been setup properly
68 assert self.getBBox().getDimensions() == Extent2I(1, 1)
70 def _getArray(self):
71 """Data cube array in multiple bands
73 Since `self._singles` is just a 1D array,
74 `array` just returns `self._singles`.
75 """
76 return self.singles
78 def _setArray(self, value):
79 assert value.shape == self.array.shape
80 self._singles[:] = value
82 array = property(_getArray, _setArray)
84 def clone(self, deep=True):
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).
90 """
91 if deep:
92 singles = np.copy(self.singles)
93 position = self.getBBox().getMin()
94 return MultibandPixel(filters=self.filters, singles=singles, position=position)
96 result = MultibandPixel(filters=self.filters, singles=self.singles, position=self.getBBox().getMin())
97 result._bbox = self.getBBox()
98 return result
100 def __getitem__(self, indices):
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.
106 """
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)
112 # Make the index into a valid numpy index or slice
113 filters, filterIndex = self._filterNamesToIndex(indices)
114 if len(filters) == 1 and not isinstance(filterIndex, slice):
115 # The user only requested a pixel in a single band, so return it
116 return self.array[filterIndex[0]]
118 result = self.clone(False)
119 result._filters = filters
120 result._singles = self._singles[filterIndex]
121 # No need to update the bounding box, since pixels can only be sliced in the filter dimension
122 return result
124 def _slice(self, filters, filterIndex, indices):
125 pass
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.
139 Parameters
140 ----------
141 filters : `list`
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.
147 singleType : `type`
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.
151 bbox : `Box2I`
152 Location of the array in a larger single band image.
153 If `bbox` is `None` then the bounding box is initialized
154 at the origin.
155 """
156 def __init__(self, filters, array, singleType, bbox=None):
157 # Create the single band objects from the array
158 if len(array) != len(filters):
159 raise ValueError("`array` and `filters` must have the same length")
160 self._array = array
161 self._filters = tuple(filters)
162 if bbox is None:
163 bbox = Box2I(Point2I(0, 0), Extent2I(array.shape[2], array.shape[1]))
164 self._bbox = bbox
166 xy0 = self.getXY0()
167 dtype = array.dtype
168 self._singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype) for n in range(len(array))])
170 # Make sure that all of the parameters have been setup appropriately
171 assert isinstance(self._bbox, Box2I)
172 assert len(self.singles) == len(self.filters)
174 def _getArray(self):
175 """Data cube array in multiple bands
177 Returns
178 -------
179 self._array : array
180 The resulting 3D data cube with shape (filters, y, x).
181 """
182 return self._array
184 def _setArray(self, value):
185 """Set the values of the array"""
186 self.array[:] = value
188 array = property(_getArray, _setArray)
190 def clone(self, deep=True):
191 """Copy the current object
193 Parameters
194 ----------
195 deep : `bool`
196 Whether or not to make a deep copy
197 """
198 if deep:
199 array = np.copy(self.array)
200 bbox = Box2I(self.getBBox())
201 else:
202 array = self.array
203 bbox = self.getBBox()
204 result = type(self)(self.filters, array, bbox)
205 return result
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.
211 """
212 if len(indices) > 0:
213 if len(indices) == 1:
214 indices = indices[0]
215 allSlices = [filterIndex, slice(None), slice(None)]
216 sy, sx, bbox = imageIndicesToNumpy(indices, self.getBBox)
217 if sy is not None:
218 allSlices[-2] = sy
219 if sx is not None:
220 allSlices[-1] = sx
221 array = self._array[tuple(allSlices)]
223 # Return a scalar or MultibandPixel
224 # if the image indices are integers
225 if bbox is None:
226 if not isinstance(filterIndex, slice) and len(filterIndex) == 1:
227 return array[0]
228 result = MultibandPixel(
229 singles=array,
230 filters=filters,
231 position=Point2I(sx + self.x0, sy + self.y0)
232 )
233 return result
234 else:
235 array = self._array[filterIndex]
236 bbox = self.getBBox()
237 result = type(self)(filters, array, bbox)
239 # Check that the image and array shapes agree
240 imageShape = (
241 len(result.filters),
242 result.getBBox().getHeight(),
243 result.getBBox().getWidth()
244 )
245 assert result.array.shape == imageShape
247 return result
249 def __setitem__(self, args, value):
250 """Set a subset of the MultibandImage
251 """
252 if not isinstance(args, tuple):
253 indices = (args,)
254 else:
255 indices = args
257 # Return the single band object if the first
258 # index is not a list or slice.
259 filters, filterIndex = self._filterNamesToIndex(indices[0])
260 if len(indices) > 1:
261 sy, sx, bbox = imageIndicesToNumpy(indices[1:], self.getBBox)
262 else:
263 sy = sx = slice(None)
264 if hasattr(value, "array"):
265 self._array[filterIndex, sy, sx] = value.array
266 else:
267 self._array[filterIndex, sy, sx] = value
269 def getBBox(self, origin=PARENT):
270 """Bounding box
271 """
272 if origin == PARENT:
273 return self._bbox
274 elif origin == LOCAL:
275 return Box2I(Point2I(0, 0), self._bbox.getDimensions())
276 raise ValueError("Unrecognized origin, expected either PARENT or LOCAL")
279def makeImageFromSingles(cls, filters, singles):
280 """Construct a MultibandImage from a collection of single band images
282 Parameters
283 ----------
284 filters : `list`
285 List of filter names.
286 singles : `list`
287 A list of single band objects.
288 If `array` is not `None`, then `singles` is ignored
289 """
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)
297def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs):
298 """Build a MultibandImage from a set of keyword arguments
300 Parameters
301 ----------
302 filters : `list`
303 List of filter names.
304 singleType : class
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.
314 kwargs : `dict`
315 Keyword arguments to initialize a new instance of an
316 inherited class that are the same in all bands.
317 """
318 # Attempt to load a set of images
319 singles = []
320 for f in filters:
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)
328class MultibandImage(MultibandImageBase):
329 """Multiband Image class
331 See `MultibandImageBase` for a description of the parameters.
332 """
333 def __init__(self, filters, array, bbox=None):
334 super().__init__(filters, array, Image, bbox)
336 @staticmethod
337 def fromImages(filters, singles):
338 """Construct a MultibandImage from a collection of single band images
340 see `fromSingles` for a description of parameters
341 """
342 return makeImageFromSingles(MultibandImage, filters, singles)
344 @staticmethod
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
349 """
350 return makeImageFromKwargs(MultibandImage, filters, filterKwargs, singleType, **kwargs)
353class MultibandMask(MultibandImageBase):
354 """Multiband Mask class
356 See `MultibandImageBase` for a description of the parameters.
357 """
358 def __init__(self, filters, array, bbox=None):
359 super().__init__(filters, array, Mask, bbox)
360 # Set Mask specific properties
361 self._refMask = self._singles[0]
362 refMask = self._refMask
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])
367 @staticmethod
368 def fromMasks(filters, singles):
369 """Construct a MultibandImage from a collection of single band images
371 see `fromSingles` for a description of parameters
372 """
373 return makeImageFromSingles(MultibandMask, filters, singles)
375 @staticmethod
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
380 """
381 return makeImageFromKwargs(MultibandMask, filters, filterKwargs, singleType, **kwargs)
383 def getMaskPlane(self, key):
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`
393 returns `256`.
395 Parameters
396 ----------
397 key : `str`
398 Name of the key in the `MaskPlaneDict`
400 Returns
401 -------
402 bit : `int`
403 Bit number for mask `key`
404 """
405 return self._refMask.getMaskPlaneDict()[key]
407 def getPlaneBitMask(self, names):
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`
417 returns `256`.
419 Parameters
420 ----------
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`
430 Returns
431 -------
432 bit value : `int`
433 Bit value for all of the combined bits described by `names`.
434 """
435 return self._refMask.getPlaneBitMask(names)
437 def getNumPlanesMax(self):
438 """Maximum number of mask planes available
440 This is required to be the same for all of the single
441 band `Mask` objects.
442 """
443 return self._refMask.getNumPlanesMax()
445 def getNumPlanesUsed(self):
446 """Number of mask planes used
448 This is required to be the same for all of the single
449 band `Mask` objects.
450 """
451 return self._refMask.getNumPlanesUsed()
453 def getMaskPlaneDict(self):
454 """Dictionary of Mask Plane bit values
455 """
456 return self._refMask.getMaskPlaneDict()
458 @staticmethod
459 def clearMaskPlaneDict():
460 """Reset the mask plane dictionary
461 """
462 Mask[MaskPixel].clearMaskPlaneDict()
464 @staticmethod
465 def addMaskPlane(name):
466 """Add a mask to the mask plane
468 Parameters
469 ----------
470 name : `str`
471 Name of the new mask plane
473 Returns
474 -------
475 index : `int`
476 Bit value of the mask in the mask plane.
477 """
478 idx = Mask[MaskPixel].addMaskPlane(name)
479 return idx
481 @staticmethod
482 def removeMaskPlane(name):
483 """Remove a mask from the mask plane
485 Parameters
486 ----------
487 name : `str`
488 Name of the mask plane to remove
489 """
490 Mask[MaskPixel].removeMaskPlane(name)
492 def removeAndClearMaskPlane(self, name, removeFromDefault=False):
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
497 default dictionary.
499 Parameters
500 ----------
501 name : `str`
502 Name of the mask plane to remove
503 removeFromDefault : `bool`, optional
504 Whether to remove the mask plane from the default dictionary.
505 Default is `False`.
506 """
507 # Clear all masks in MultibandMask but leave in default dict for now
508 for single in self.singles:
509 single.removeAndClearMaskPlane(name, removeFromDefault=False)
510 # Now remove from default dict according to removeFromDefault
511 self._refMask.removeAndClearMaskPlane(name, removeFromDefault)
513 def clearAllMaskPlanes(self):
514 """Clear all the pixels
515 """
516 self._refMask.clearAllMaskPlanes()
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`.
525 """
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]
531 else:
532 result = [others]*len(self.singles)
533 return result
535 def __ior__(self, others):
536 _others = self._getOtherMasks(others)
537 for s, o in zip(self.singles, _others):
538 s |= o
539 return self
541 def __iand__(self, others):
542 _others = self._getOtherMasks(others)
543 for s, o in zip(self.singles, _others):
544 s &= o
545 return self
547 def __ixor__(self, others):
548 _others = self._getOtherMasks(others)
549 for s, o in zip(self.singles, _others):
550 s ^= o
551 return self
554class MultibandTripleBase(MultibandBase):
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`.
561 Parameters
562 ----------
563 filters : `list`
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
568 a `MultibandImage`.
569 Ignored if `singles` is not `None`.
570 mask : `list` or `MultibandMask`
571 List of `Mask` objects that represent the mask in each bandor
572 a `MultibandMask`.
573 Ignored if `singles` is not `None`.
574 variance : `list` or `MultibandImage`
575 List of `Image` objects that represent the variance in each bandor
576 a `MultibandImage`.
577 Ignored if `singles` is not `None`.
578 """
579 def __init__(self, filters, image, mask, variance):
580 self._filters = tuple(filters)
581 # Convert single band images into multiband images
582 if not isinstance(image, MultibandBase):
583 image = MultibandImage.fromImages(filters, image)
584 if mask is not None:
585 mask = MultibandMask.fromMasks(filters, mask)
586 if variance is not None:
587 variance = MultibandImage.fromImages(filters, variance)
588 self._image = image
589 self._mask = mask
590 self._variance = variance
592 self._singles = self._buildSingles(self._image, self._mask, self._variance)
593 self._bbox = self.singles[0].getBBox()
595 def setXY0(self, xy0):
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.
600 Parameters
601 ----------
602 xy0 : `Point2I`
603 New minimum bounds of the bounding box
604 """
605 super().setXY0(xy0)
606 self.image.setXY0(xy0)
607 if self.mask is not None:
608 self.mask.setXY0(xy0)
609 if self.variance is not None:
610 self.variance.setXY0(xy0)
612 def shiftedTo(self, xy0):
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.
619 Parameters
620 ----------
621 xy0 : `Point2I`
622 New minimum bounds of the bounding box
624 Returns
625 -------
626 result : `MultibandBase`
627 A copy of the object, shifted to `xy0`.
628 """
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)
634 if self.variance is not None:
635 result._variance = result.variance.shiftedTo(xy0)
636 result._bbox = result.image.getBBox()
637 return result
639 def clone(self, deep=True):
640 """Make a copy of the current instance
641 """
642 image = self.image.clone(deep)
643 if self.mask is not None:
644 mask = self.mask.clone(deep)
645 else:
646 mask = None
647 if self.variance is not None:
648 variance = self.variance.clone(deep)
649 else:
650 variance = 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.
657 """
658 image = self.image._slice(filters, filterIndex, indices)
659 if self.mask is not None:
660 mask = self._mask._slice(filters, filterIndex, indices)
661 else:
662 mask = None
663 if self.variance is not None:
664 variance = self._variance._slice(filters, filterIndex, indices)
665 else:
666 variance = None
668 # If only a single pixel is selected, return the tuple of MultibandPixels
669 if isinstance(image, MultibandPixel):
670 if mask is not None:
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]])
678 return result
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.
686 """
687 for prop in [image, mask, variance]:
688 if prop is not None:
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)
695 @property
696 def image(self):
697 """The image of the MultibandMaskedImage"""
698 return self._image
700 @property
701 def mask(self):
702 """The mask of the MultibandMaskedImage"""
703 return self._mask
705 @property
706 def variance(self):
707 """The variance of the MultibandMaskedImage"""
708 return self._variance
710 def getBBox(self, origin=PARENT):
711 """Bounding box
712 """
713 if origin == PARENT:
714 return self._bbox
715 elif origin == LOCAL:
716 return Box2I(Point2I(0, 0), self._bbox.getDimensions())
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
723 Parameters
724 ----------
725 filters : `list`
726 List of filter names.
727 singles : `list`
728 A list of single band objects.
729 If `array` is not `None`, then `singles` is ignored
730 """
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
742 Parameters
743 ----------
744 filters : `list`
745 List of filter names.
746 image : array
747 Array of image values
748 mask : array
749 Array of mask values
750 variance : array
751 Array of variance values
752 bbox : `Box2I`
753 Location of the array in a larger single band image.
754 This argument is ignored if `singles` is not `None`.
755 """
756 if bbox is None:
757 bbox = Box2I(Point2I(0, 0), Extent2I(image.shape[1], image.shape[0]))
758 mImage = MultibandImage(filters, image, bbox)
759 if mask is not None:
760 mMask = MultibandMask(filters, mask, bbox)
761 else:
762 mMask = None
763 if variance is not None:
764 mVariance = MultibandImage(filters, variance, bbox)
765 else:
766 mVariance = 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
773 Parameters
774 ----------
775 filters : `list`
776 List of filter names.
777 singleType : `class`
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.
787 kwargs : `dict`
788 Keyword arguments to initialize a new instance of an inherited
789 class that are the same in all bands.
790 """
791 # Attempt to load a set of images
792 singles = []
793 for f in filters:
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)
801class MultibandMaskedImage(MultibandTripleBase):
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.
812 """
813 def __init__(self, filters, image=None, mask=None, variance=None):
814 super().__init__(filters, image, mask, variance)
816 @staticmethod
817 def fromImages(filters, singles):
818 """Construct a MultibandImage from a collection of single band images
820 see `tripleFromImages` for a description of parameters
821 """
822 return tripleFromSingles(MultibandMaskedImage, filters, singles)
824 @staticmethod
825 def fromArrays(filters, image, mask, variance, bbox=None):
826 """Construct a MultibandMaskedImage from a collection of arrays
828 see `tripleFromArrays` for a description of parameters
829 """
830 return tripleFromArrays(MultibandMaskedImage, filters, image, mask, variance, bbox)
832 @staticmethod
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
837 """
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
843 Parameters
844 ----------
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.
852 Returns
853 -------
854 singles : `tuple`
855 Tuple of `MaskedImage` objects for each band,
856 where the `image`, `mask`, and `variance` of each `single`
857 point to the multiband objects.
858 """
859 singles = []
860 if image is None:
861 image = self.image
862 if mask is None:
863 mask = self.mask
864 if variance is None:
865 variance = self.variance
867 dtype = image.array.dtype
868 singles = []
869 for f in self.filters:
870 _image = image[f]
871 if mask is not None:
872 _mask = mask[f]
873 else:
874 _mask = None
875 if variance is not None:
876 _variance = variance[f]
877 else:
878 _variance = None
879 singles.append(MaskedImage(image=_image, mask=_mask, variance=_variance, dtype=dtype))
880 return tuple(singles)