Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+9666464c73,g0fba68d861+d6c6f70ffa,g1fd858c14a+94f68680cf,g208c678f98+1ca806343c,g271391ec13+ac98094cfc,g2c84ff76c0+120a924478,g2c9e612ef2+a92a2e6025,g35bb328faa+fcb1d3bbc8,g4d2262a081+7c332456db,g4e0f332c67+c58e4b632d,g53246c7159+fcb1d3bbc8,g60b5630c4e+a92a2e6025,g67b6fd64d1+9d1b2ab50a,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8852436030+506db7da85,g89139ef638+9d1b2ab50a,g8d6b6b353c+a92a2e6025,g9125e01d80+fcb1d3bbc8,g989de1cb63+9d1b2ab50a,g9f33ca652e+d1749da127,ga2b97cdc51+a92a2e6025,gabe3b4be73+1e0a283bba,gb1101e3267+6ecbd0580e,gb58c049af0+f03b321e39,gb89ab40317+9d1b2ab50a,gb90eeb9370+384e1fc23b,gcf25f946ba+506db7da85,gd315a588df+382ef11c06,gd6cbbdb0b4+75aa4b1db4,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+9984fb7d9e,ge278dab8ac+c61fbefdff,ge410e46f29+9d1b2ab50a,ge82c20c137+e12a08b75a,gf67bdafdda+9d1b2ab50a,gfd5510ef7b+864ead200a,v29.0.0.rc2
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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__ = ["MultibandBase"]
23
24from abc import ABC, abstractmethod
25
26from deprecated.sphinx import deprecated
27
28from lsst.geom import Box2I
29
30
31class MultibandBase(ABC):
32 """Base class for multiband objects
33
34 The LSST stack has a number of image-like classes that have
35 data in multiple bands that are stored as separate objects.
36 Analyzing the data can be easier using a Multiband object that
37 wraps the underlying data as a single data cube that can be sliced and
38 updated as a single object.
39
40 `MultibandBase` is designed to contain the most important universal
41 methods for initializing, slicing, and extracting common parameters
42 (such as the bounding box or XY0 position) to all of the single band classes,
43 as long as derived classes either call the base class `__init__`
44 or set the `_bands`, `_singles`, and `_bbox`.
45
46 Parameters
47 ----------
48 bands: `list`
49 List of band names.
50 singles: `list`
51 List of single band objects
52 bbox: `Box2I`
53 By default `MultibandBase` uses `singles[0].getBBox()` to set
54 the bounding box of the multiband
55 """
56 def __init__(self, bands, singles, bbox=None):
57 self._bands = tuple([f for f in bands])
58 self._singles = tuple(singles)
59
60 if bbox is None:
61 self._bbox = self._singles[0].getBBox()
62 if not all([s.getBBox() == self.getBBox() for s in self.singles]):
63 bboxes = [s.getBBox() == self.getBBox() for s in self.singles]
64 err = "`singles` are required to have the same bounding box, received {0}"
65 raise ValueError(err.format(bboxes))
66 else:
67 self._bbox = bbox
68
69 @abstractmethod
70 def clone(self, deep=True):
71 """Copy the current object
72
73 This must be overloaded in a subclass of `MultibandBase`
74
75 Parameters
76 ----------
77 deep: `bool`
78 Whether or not to make a deep copy
79
80 Returns
81 -------
82 result: `MultibandBase`
83 copy of the instance that inherits from `MultibandBase`
84 """
85 pass
86
87 @property
88 @deprecated(reason="This has been replaced with `bands`. Will be removed after v29.",
89 version="v29.0", category=FutureWarning)
90 def filters(self):
91 """List of filter names for the single band objects (deprecated)
92
93 Use `bands` instead.
94 """
95 return self._bands
96
97 @property
98 def bands(self):
99 """List of band names for the single band objects
100 """
101 return self._bands
102
103 @property
104 def singles(self):
105 """List of single band objects
106 """
107 return self._singles
108
109 def getBBox(self):
110 """Bounding box
111 """
112 return self._bbox
113
114 def getXY0(self):
115 """Minimum coordinate in the bounding box
116 """
117 return self.getBBox().getMin()
118
119 @property
120 def x0(self):
121 """X0
122
123 X component of XY0 `Point2I.getX()`
124 """
125 return self.getBBox().getMinX()
126
127 @property
128 def y0(self):
129 """Y0
130
131 Y component of XY0 `Point2I.getY()`
132 """
133 return self.getBBox().getMinY()
134
135 @property
136 def origin(self):
137 """Minimum (y,x) position
138
139 This is the position of `self.getBBox().getMin()`,
140 but available as a tuple for numpy array indexing.
141 """
142 return (self.y0, self.x0)
143
144 @property
145 def width(self):
146 """Width of the images
147 """
148 return self.getBBox().getWidth()
149
150 @property
151 def height(self):
152 """Height of the images
153 """
154 return self.getBBox().getHeight()
155
156 def __len__(self):
157 return len(self.bands)
158
159 def __getitem__(self, args):
160 """Get a slice of the underlying array
161
162 If only a single band is specified,
163 return the single band object sliced
164 appropriately.
165 """
166 if not isinstance(args, tuple):
167 indices = (args,)
168 else:
169 indices = args
170
171 # Return the single band object if the first
172 # index is not a list or slice.
173 bands, bandIndex = self._bandNamesToIndex(indices[0])
174 if not isinstance(bandIndex, slice) and len(bandIndex) == 1:
175 if len(indices) > 2:
176 return self.singles[bandIndex[0]][indices[1:]]
177 elif len(indices) == 2:
178 return self.singles[bandIndex[0]][indices[1]]
179 else:
180 return self.singles[bandIndex[0]]
181
182 return self._slice(bands=bands, bandIndex=bandIndex, indices=indices[1:])
183
184 def __iter__(self):
185 self._bandIndex = 0
186 return self
187
188 def __next__(self):
189 if self._bandIndex < len(self.bands):
190 result = self.singles[self._bandIndex]
191 self._bandIndex += 1
192 else:
193 raise StopIteration
194 return result
195
196 def _bandNamesToIndex(self, bandIndex):
197 """Convert a list of band names to an index or a slice
198
199 Parameters
200 ----------
201 bandIndex: iterable or `object`
202 Index to specify a band or list of bands,
203 usually a string or enum.
204 For example `bandIndex` can be
205 `"R"` or `["R", "G", "B"]` or `[Band.R, Band.G, Band.B]`,
206 if `Band` is an enum.
207
208 Returns
209 -------
210 bandNames: `list`
211 Names of the bands in the slice
212 bandIndex: `slice` or `list` of `int`
213 Index of each band in `bandNames` in
214 `self.bands`.
215 """
216 if isinstance(bandIndex, slice):
217 if bandIndex.start is not None:
218 start = self.bands.index(bandIndex.start)
219 else:
220 start = None
221 if bandIndex.stop is not None:
222 stop = self.bands.index(bandIndex.stop)
223 else:
224 stop = None
225 bandIndices = slice(start, stop, bandIndex.step)
226 bandNames = self.bands[bandIndices]
227 else:
228 if isinstance(bandIndex, str):
229 bandNames = [bandIndex]
230 bandIndices = [self.bands.index(bandIndex)]
231 else:
232 try:
233 # Check to see if the bandIndex is an iterable
234 bandNames = [f for f in bandIndex]
235 except TypeError:
236 bandNames = [bandIndex]
237 bandIndices = [self.bands.index(f) for f in bandNames]
238 return tuple(bandNames), bandIndices
239
240 def setXY0(self, xy0):
241 """Shift the bounding box but keep the same Extent
242
243 Parameters
244 ----------
245 xy0: `Point2I`
246 New minimum bounds of the bounding box
247 """
248 self._bbox = Box2I(xy0, self._bbox.getDimensions())
249 for singleObj in self.singles:
250 singleObj.setXY0(xy0)
251
252 def shiftedTo(self, xy0):
253 """Shift the bounding box but keep the same Extent
254
255 This method is broken until DM-10781 is completed.
256
257 Parameters
258 ----------
259 xy0: `Point2I`
260 New minimum bounds of the bounding box
261
262 Returns
263 -------
264 result: `MultibandBase`
265 A copy of the object, shifted to `xy0`.
266 """
267 raise NotImplementedError("shiftedBy not implemented until DM-10781")
268 result = self.clone(False)
269 result._bbox = Box2I(xy0, result._bbox.getDimensions())
270 for singleObj in result.singles:
271 singleObj.setXY0(xy0)
272 return result
273
274 def shiftedBy(self, offset):
275 """Shift a bounding box by an offset, but keep the same Extent
276
277 This method is broken until DM-10781 is completed.
278
279 Parameters
280 ----------
281 offset: `Extent2I`
282 Amount to shift the bounding box in x and y.
283
284 Returns
285 -------
286 result: `MultibandBase`
287 A copy of the object, shifted by `offset`
288 """
289 raise NotImplementedError("shiftedBy not implemented until DM-10781")
290 xy0 = self._bbox.getMin() + offset
291 return self.shiftedTo(xy0)
292
293 @abstractmethod
294 def _slice(self, bands, bandIndex, indices):
295 """Slice the current object and return the result
296
297 Different inherited classes will handling slicing differently,
298 so this method must be overloaded in inherited classes.
299
300 Parameters
301 ----------
302 bands: `list` of `str`
303 List of band names for the slice. This is a subset of the
304 bands in the parent multiband object
305 bandIndex: `list` of `int` or `slice`
306 Index along the band dimension
307 indices: `tuple` of remaining indices
308 `MultibandBase.__getitem__` separates the first (band)
309 index from the remaining indices, so `indices` is a tuple
310 of all of the indices that come after `band` in the
311 `args` passed to `MultibandBase.__getitem__`.
312
313 Returns
314 -------
315 result: `object`
316 Sliced version of the current object, which could be the
317 same class or a different class depending on the
318 slice being made.
319 """
320 pass
321
322 def __repr__(self):
323 result = "<{0}, bands={1}, bbox={2}>".format(
324 self.__class__.__name__, self.bands, self.getBBox().__repr__())
325 return result
326
327 def __str__(self):
328 if hasattr(self, "array"):
329 return str(self.array)
330 return self.__repr__()
_slice(self, bands, bandIndex, indices)
Definition multiband.py:294
_bandNamesToIndex(self, bandIndex)
Definition multiband.py:196
__init__(self, bands, singles, bbox=None)
Definition multiband.py:56
An integer coordinate rectangle.
Definition Box.h:55