LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
_multiband.py
Go to the documentation of this file.
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/>.
21
22__all__ = ["MultibandPixel", "MultibandImage", "MultibandMask", "MultibandMaskedImage"]
23
24import numpy as np
25
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
31
32
34 """Multiband Pixel class
35
36 This represent acts as a container for a single pixel
37 (scalar) in multiple bands.
38
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)
59
60 # Make sure that singles is an array
61 singles = np.array(singles, copy=False)
62
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_singles = singles
66
67 # Make sure that the bounding box has been setup properly
68 assert self.getBBoxgetBBox().getDimensions() == Extent2I(1, 1)
69
70 def _getArray(self):
71 """Data cube array in multiple bands
72
73 Since `self._singles_singles_singles` is just a 1D array,
74 `array` just returns `self._singles_singles_singles`.
75 """
76 return self.singlessingles
77
78 def _setArray(self, value):
79 assert value.shape == self.arrayarray.shape
80 self._singles_singles_singles[:] = value
81
82 array = property(_getArray, _setArray)
83
84 def clone(self, deep=True):
85 """Make a copy of the current instance
86
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.singlessingles)
93 position = self.getBBoxgetBBox().getMin()
94 return MultibandPixel(filters=self.filtersfilters, singles=singles, position=position)
95
96 result = MultibandPixel(filters=self.filtersfilters, singles=self.singlessingles, position=self.getBBoxgetBBox().getMin())
97 result._bbox = self.getBBoxgetBBox()
98 return result
99
100 def __getitem__(self, indices):
101 """Get a slice of the underlying array
102
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_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.arrayarray[filterIndex[0]]
117
118 result = self.clonecloneclone(False)
119 result._filters = filters
120 result._singles = self._singles_singles_singles[filterIndex]
121 # No need to update the bounding box, since pixels can only be sliced in the filter dimension
122 return result
123
124 def _slice(self, filters, filterIndex, indices):
125 pass
126
127
128class MultibandImageBase(MultibandBase):
129 """Multiband Image class
130
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.
138
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 = array
161 self._filters_filters_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_bbox = bbox
165
166 xy0 = self.getXY0getXY0()
167 dtype = array.dtype
168 self._singles_singles_singles = tuple([singleType(array=array[n], xy0=xy0, dtype=dtype) for n in range(len(array))])
169
170 # Make sure that all of the parameters have been setup appropriately
171 assert isinstance(self._bbox_bbox_bbox, Box2I)
172 assert len(self.singlessingles) == len(self.filtersfilters)
173
174 def _getArray(self):
175 """Data cube array in multiple bands
176
177 Returns
178 -------
179 self._array_array : array
180 The resulting 3D data cube with shape (filters, y, x).
181 """
182 return self._array_array
183
184 def _setArray(self, value):
185 """Set the values of the array"""
186 self.arrayarray[:] = value
187
188 array = property(_getArray, _setArray)
189
190 def clone(self, deep=True):
191 """Copy the current object
192
193 Parameters
194 ----------
195 deep : `bool`
196 Whether or not to make a deep copy
197 """
198 if deep:
199 array = np.copy(self.arrayarray)
200 bbox = Box2I(self.getBBoxgetBBoxgetBBox())
201 else:
202 array = self.arrayarray
203 bbox = self.getBBoxgetBBoxgetBBox()
204 result = type(self)(self.filtersfilters, array, bbox)
205 return result
206
207 def _slice(self, filters, filterIndex, indices):
208 """Slice the current object and return the result
209
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.getBBoxgetBBoxgetBBox)
217 if sy is not None:
218 allSlices[-2] = sy
219 if sx is not None:
220 allSlices[-1] = sx
221 array = self._array_array[tuple(allSlices)]
222
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.x0x0, sy + self.y0y0)
232 )
233 return result
234 else:
235 array = self._array_array[filterIndex]
236 bbox = self.getBBoxgetBBoxgetBBox()
237 result = type(self)(filters, array, bbox)
238
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
246
247 return result
248
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
256
257 # Return the single band object if the first
258 # index is not a list or slice.
259 filters, filterIndex = self._filterNamesToIndex_filterNamesToIndex(indices[0])
260 if len(indices) > 1:
261 sy, sx, bbox = imageIndicesToNumpy(indices[1:], self.getBBoxgetBBoxgetBBox)
262 else:
263 sy = sx = slice(None)
264 if hasattr(value, "array"):
265 self._array_array[filterIndex, sy, sx] = value.array
266 else:
267 self._array_array[filterIndex, sy, sx] = value
268
269 def getBBox(self, origin=PARENT):
270 """Bounding box
271 """
272 if origin == PARENT:
273 return self._bbox_bbox_bbox
274 elif origin == LOCAL:
275 return Box2I(Point2I(0, 0), self._bbox_bbox_bbox.getDimensions())
276 raise ValueError("Unrecognized origin, expected either PARENT or LOCAL")
277
278
279def makeImageFromSingles(cls, filters, singles):
280 """Construct a MultibandImage from a collection of single band images
281
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)
295
296
297def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs):
298 """Build a MultibandImage from a set of keyword arguments
299
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)
326
327
329 """Multiband Image class
330
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)
335
336 @staticmethod
337 def fromImages(filters, singles):
338 """Construct a MultibandImage from a collection of single band images
339
340 see `fromSingles` for a description of parameters
341 """
342 return makeImageFromSingles(MultibandImage, filters, singles)
343
344 @staticmethod
345 def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
346 """Build a MultibandImage from a set of keyword arguments
347
348 see `makeImageFromKwargs` for a description of parameters
349 """
350 return makeImageFromKwargs(MultibandImage, filters, filterKwargs, singleType, **kwargs)
351
352
354 """Multiband Mask class
355
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_refMask = self._singles_singles_singles[0]
362 refMask = self._refMask_refMask
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])
366
367 @staticmethod
368 def fromMasks(filters, singles):
369 """Construct a MultibandImage from a collection of single band images
370
371 see `fromSingles` for a description of parameters
372 """
373 return makeImageFromSingles(MultibandMask, filters, singles)
374
375 @staticmethod
376 def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs):
377 """Build a MultibandImage from a set of keyword arguments
378
379 see `makeImageFromKwargs` for a description of parameters
380 """
381 return makeImageFromKwargs(MultibandMask, filters, filterKwargs, singleType, **kwargs)
382
383 def getMaskPlane(self, key):
384 """Get the bit number of a mask in the `MaskPlaneDict`
385
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.
391
392 For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask`
393 returns `256`.
394
395 Parameters
396 ----------
397 key : `str`
398 Name of the key in the `MaskPlaneDict`
399
400 Returns
401 -------
402 bit : `int`
403 Bit number for mask `key`
404 """
405 return self._refMask_refMask.getMaskPlaneDict()[key]
406
407 def getPlaneBitMask(self, names):
408 """Get the bit number of a mask in the `MaskPlaneDict`
409
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.
415
416 For example, if `getMaskPlane` returns `8`, then `getPlaneBitMask`
417 returns `256`.
418
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`.
425
426 For example if `MaskPlaneDict("CR")=3` and
427 `MaskPlaneDict("NO_DATA)=8`, then
428 `getPlaneBitMask(("CR", "NO_DATA"))=264`
429
430 Returns
431 -------
432 bit value : `int`
433 Bit value for all of the combined bits described by `names`.
434 """
435 return self._refMask_refMask.getPlaneBitMask(names)
436
438 """Maximum number of mask planes available
439
440 This is required to be the same for all of the single
441 band `Mask` objects.
442 """
443 return self._refMask_refMask.getNumPlanesMax()
444
446 """Number of mask planes used
447
448 This is required to be the same for all of the single
449 band `Mask` objects.
450 """
451 return self._refMask_refMask.getNumPlanesUsed()
452
454 """Dictionary of Mask Plane bit values
455 """
456 return self._refMask_refMask.getMaskPlaneDict()
457
458 @staticmethod
460 """Reset the mask plane dictionary
461 """
462 Mask[MaskPixel].clearMaskPlaneDict()
463
464 @staticmethod
465 def addMaskPlane(name):
466 """Add a mask to the mask plane
467
468 Parameters
469 ----------
470 name : `str`
471 Name of the new mask plane
472
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
480
481 @staticmethod
483 """Remove a mask from the mask plane
484
485 Parameters
486 ----------
487 name : `str`
488 Name of the mask plane to remove
489 """
490 Mask[MaskPixel].removeMaskPlane(name)
491
492 def removeAndClearMaskPlane(self, name, removeFromDefault=False):
493 """Remove and clear a mask from the mask plane
494
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.
498
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.singlessingles:
509 single.removeAndClearMaskPlane(name, removeFromDefault=False)
510 # Now remove from default dict according to removeFromDefault
511 self._refMask_refMask.removeAndClearMaskPlane(name, removeFromDefault)
512
514 """Clear all the pixels
515 """
516 self._refMask_refMask.clearAllMaskPlanes()
517
518 def _getOtherMasks(self, others):
519 """Check if two masks can be combined
520
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.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]
531 else:
532 result = [others]*len(self.singlessingles)
533 return result
534
535 def __ior__(self, others):
536 _others = self._getOtherMasks_getOtherMasks(others)
537 for s, o in zip(self.singlessingles, _others):
538 s |= o
539 return self
540
541 def __iand__(self, others):
542 _others = self._getOtherMasks_getOtherMasks(others)
543 for s, o in zip(self.singlessingles, _others):
544 s &= o
545 return self
546
547 def __ixor__(self, others):
548 _others = self._getOtherMasks_getOtherMasks(others)
549 for s, o in zip(self.singlessingles, _others):
550 s ^= o
551 return self
552
553
555 """MultibandTripleBase class
556
557 This is a base class inherited by multiband classes
558 with `image`, `mask`, and `variance` objects,
559 such as `MultibandMaskedImage` and `MultibandExposure`.
560
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_filters_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 = image
589 self._mask_mask = mask
590 self._variance_variance = variance
591
592 self._singles_singles_singles = self._buildSingles(self._image_image, self._mask_mask, self._variance_variance)
593 self._bbox_bbox_bbox = self.singlessingles[0].getBBox()
594
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.imageimage.setXY0(xy0)
607 if self.maskmask is not None:
608 self.maskmask.setXY0(xy0)
609 if self.variancevariance is not None:
610 self.variancevariance.setXY0(xy0)
611
612 def shiftedTo(self, xy0):
613 """Shift the bounding box but keep the same Extent
614
615 This is different than `MultibandBase.shiftedTo`
616 because the multiband `image`, `mask`, and `variance` objects
617 must all have their bounding boxes updated.
618
619 Parameters
620 ----------
621 xy0 : `Point2I`
622 New minimum bounds of the bounding box
623
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.clonecloneclone(False)
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()
637 return result
638
639 def clone(self, deep=True):
640 """Make a copy of the current instance
641 """
642 image = self.imageimage.clone(deep)
643 if self.maskmask is not None:
644 mask = self.maskmask.clone(deep)
645 else:
646 mask = None
647 if self.variancevariance is not None:
648 variance = self.variancevariance.clone(deep)
649 else:
650 variance = None
651 return type(self)(self.filtersfilters, image, mask, variance)
652
653 def _slice(self, filters, filterIndex, indices):
654 """Slice the current object and return the result
655
656 See `Multiband._slice` for a list of the parameters.
657 """
658 image = self.imageimage._slice(filters, filterIndex, indices)
659 if self.maskmask is not None:
660 mask = self._mask_mask._slice(filters, filterIndex, indices)
661 else:
662 mask = None
663 if self.variancevariance is not None:
664 variance = self._variance_variance._slice(filters, filterIndex, indices)
665 else:
666 variance = None
667
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)
675
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
679
680 def _verifyUpdate(self, image=None, mask=None, variance=None):
681 """Check that the new image, mask, or variance is valid
682
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.getBBoxgetBBoxgetBBox():
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)
694
695 @property
696 def image(self):
697 """The image of the MultibandMaskedImage"""
698 return self._image_image
699
700 @property
701 def mask(self):
702 """The mask of the MultibandMaskedImage"""
703 return self._mask_mask
704
705 @property
706 def variance(self):
707 """The variance of the MultibandMaskedImage"""
708 return self._variance_variance
709
710 def getBBox(self, origin=PARENT):
711 """Bounding box
712 """
713 if origin == PARENT:
714 return self._bbox_bbox_bbox
715 elif origin == LOCAL:
716 return Box2I(Point2I(0, 0), self._bbox_bbox_bbox.getDimensions())
717 raise ValueError("Unrecognized origin, expected either PARENT or LOCAL")
718
719
720def tripleFromSingles(cls, filters, singles, **kwargs):
721 """Construct a MultibandTriple from a collection of single band objects
722
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)
737
738
739def tripleFromArrays(cls, filters, image, mask, variance, bbox=None):
740 """Construct a MultibandTriple from a set of arrays
741
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)
768
769
770def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs):
771 """Build a MultibandImage from a set of keyword arguments
772
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)
799
800
802 """MultibandMaskedImage class
803
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).
810
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)
815
816 @staticmethod
817 def fromImages(filters, singles):
818 """Construct a MultibandImage from a collection of single band images
819
820 see `tripleFromImages` for a description of parameters
821 """
822 return tripleFromSingles(MultibandMaskedImage, filters, singles)
823
824 @staticmethod
825 def fromArrays(filters, image, mask, variance, bbox=None):
826 """Construct a MultibandMaskedImage from a collection of arrays
827
828 see `tripleFromArrays` for a description of parameters
829 """
830 return tripleFromArrays(MultibandMaskedImage, filters, image, mask, variance, bbox)
831
832 @staticmethod
833 def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs):
834 """Build a MultibandImage from a set of keyword arguments
835
836 see `makeTripleFromKwargs` for a description of parameters
837 """
838 return makeTripleFromKwargs(MultibandMaskedImage, filters, filterKwargs, singleType, **kwargs)
839
840 def _buildSingles(self, image=None, mask=None, variance=None):
841 """Make a new list of single band objects
842
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.
851
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.imageimage
862 if mask is None:
863 mask = self.maskmask
864 if variance is None:
865 variance = self.variancevariance
866
867 dtype = image.array.dtype
868 singles = []
869 for f in self.filtersfilters:
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)
table::Key< int > type
Definition: Detector.cc:163
table::Key< int > a
def __init__(self, filters, array, singleType, bbox=None)
Definition: _multiband.py:156
def __init__(self, filters, array, bbox=None)
Definition: _multiband.py:333
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:345
def fromKwargs(filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:376
def __init__(self, filters, array, bbox=None)
Definition: _multiband.py:358
def removeAndClearMaskPlane(self, name, removeFromDefault=False)
Definition: _multiband.py:492
def fromArrays(filters, image, mask, variance, bbox=None)
Definition: _multiband.py:825
def fromKwargs(filters, filterKwargs, singleType=MaskedImageF, **kwargs)
Definition: _multiband.py:833
def __init__(self, filters, image=None, mask=None, variance=None)
Definition: _multiband.py:813
def __init__(self, filters, singles, position)
Definition: _multiband.py:55
def __init__(self, filters, image, mask, variance)
Definition: _multiband.py:579
def _filterNamesToIndex(self, filterIndex)
Definition: multiband.py:184
def clone(self, deep=True)
Definition: multiband.py:68
An integer coordinate rectangle.
Definition: Box.h:55
std::map< std::string, int > MaskPlaneDict
Definition: Mask.h:58
def makeImageFromSingles(cls, filters, singles)
Definition: _multiband.py:279
def makeTripleFromKwargs(cls, filters, filterKwargs, singleType, **kwargs)
Definition: _multiband.py:770
def tripleFromSingles(cls, filters, singles, **kwargs)
Definition: _multiband.py:720
def makeImageFromKwargs(cls, filters, filterKwargs, singleType=ImageF, **kwargs)
Definition: _multiband.py:297
def tripleFromArrays(cls, filters, image, mask, variance, bbox=None)
Definition: _multiband.py:739
def imageIndicesToNumpy(sliceArgs, bboxGetter)
Definition: _slicing.py:202
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
Definition: Extent.h:397
Point< int, 2 > Point2I
Definition: Point.h:321