LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
_slicing.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# (https://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 <https://www.gnu.org/licenses/>.
21
22from lsst.geom import Point2I, Box2I, Extent2I
23from ._imageLib import LOCAL, PARENT, ImageOrigin
24
25__all__ = ["supportSlicing"]
26
27
28def splitSliceArgs(sliceArgs):
29 """Separate the actual slice from origin arguments to __getitem__ or
30 __setitem__, using PARENT for the origin if it is not provided.
31
32 Parameter
33 ---------
34 sliceArgs : `tuple`, `Box2I`, or `Point2I`
35 The first argument passed to an image-like object's
36 ``__getitem__`` or ``__setitem__``.
37
38 Returns
39 -------
40 sliceArgs : `tuple`, `Box2I`, or `Point2I`
41 The original sliceArgs passed in, with any ImageOrigin argument
42 stripped off.
43 origin : `ImageOrigin`
44 Enum value that sets whether or not to consider xy0 in positions.
45
46 See interpretSliceArgs for more information.
47
48 Intended primarily for internal use by `supportSlicing()`.
49 """
50 defaultOrigin = PARENT
51 try:
52 if isinstance(sliceArgs[-1], ImageOrigin):
53 # Args are already a tuple that includes the origin.
54 if len(sliceArgs) == 2:
55 return sliceArgs[0], sliceArgs[-1]
56 else:
57 return sliceArgs[:-1], sliceArgs[-1]
58 else:
59 # Args are a tuple that does not include the origin; add it to make origin explicit.
60 return sliceArgs, defaultOrigin
61 except TypeError: # Arg is a scalar; return it along with the default origin.
62 return sliceArgs, defaultOrigin
63
64
65def handleNegativeIndex(index, size, origin, default):
66 """Handle negative indices passed to image accessors.
67
68 When negative indices are used in LOCAL coordinates, we interpret them as
69 relative to the upper bounds of the array, as in regular negative indexing
70 in Python.
71
72 Using negative indices in PARENT coordinates is not allowed unless passed
73 via a `Point2I` or `Box2I`; the potential for confusion between actual
74 negative indices (when ``xy0 < 0``) and offsets relative to the upper
75 bounds of the array is too great.
76
77 Parameters
78 ----------
79 index : `int` or `None`
80 1-d pixel index to interpret, as given by a caller to an image-like
81 object's ``__getitem__`` or ``__setitem__``.
82 size : `int`
83 Size of the image in the dimension corresponding to ``index``.
84 origin : `ImageOrigin`
85 Enum value that sets whether or not to consider xy0 in positions.
86 default : `int`
87 Index to return if `index` is None.
88
89 Returns
90 -------
91 index : `int`
92 If ``origin==PARENT``, either the given ``index`` or ``default``.
93 If ``origin==LOCAL``, an equivalent index guaranteed to be nonnegative.
94
95 Intended primarily for internal use by `supportSlicing()`.
96 """
97 if index is None:
98 assert default is not None
99 return default
100 if index < 0:
101 if origin == LOCAL:
102 index = size + index
103 else:
104 raise IndexError("Negative indices are not permitted with the PARENT origin. "
105 "Use LOCAL to use negative to index relative to the end, "
106 "and Point2I or Box2I indexing to access negative pixels "
107 "in PARENT coordinates.")
108 return index
109
110
111def translateSliceArgs(sliceArgs, bboxGetter):
112 """Transform a tuple of slice objects into a Box2I, correctly handling negative indices.
113
114 see `interpretSliceArgs` for a description of parameters
115
116 Returns
117 -------
118 box : `Box2I` or `None`
119 A box to use to create a subimage, or None if the slice refers to a
120 scalar.
121 index: `tuple` or `None`
122 An ``(x, y)`` tuple of integers, or None if the slice refers to a
123 box.
124 origin : `ImageOrigin`
125 Enum indicating whether to account for xy0.
126 """
127 slices, origin = splitSliceArgs(sliceArgs)
128 if isinstance(slices, Point2I):
129 return None, slices, origin
130 elif isinstance(slices, Box2I):
131 return slices, None, origin
132
133 x, y, origin = interpretSliceArgs(sliceArgs, bboxGetter)
134
135 if isinstance(x, slice):
136 assert isinstance(y, slice)
137 if x.step is not None or y.step is not None:
138 raise ValueError("Slices with steps are not supported in image indexing.")
139 begin = Point2I(x.start, y.start)
140 end = Point2I(x.stop, y.stop)
141 return Box2I(begin, end - begin), None, origin
142
143 assert not isinstance(y, slice)
144 return None, Point2I(x, y), origin
145
146
147def interpretSliceArgs(sliceArgs, bboxGetter):
148 """Transform arguments to __getitem__ or __setitem__ to a standard form.
149
150 Parameters
151 ----------
152 sliceArgs : `tuple`, `Box2I`, or `Point2I`
153 Slice arguments passed directly to `__getitem__` or `__setitem__`.
154 bboxGetter : callable
155 Callable that accepts an ImageOrigin enum value and returns the
156 appropriate image bounding box. Usually the bound getBBox method
157 of an Image, Mask, or MaskedImage object.
158
159 Returns
160 -------
161 x : int or slice
162 Index or slice in the x dimension
163 y : int or slice
164 Index or slice in the y dimension
165 origin : `ImageOrigin`
166 Either `PARENT` (coordinates respect XY0) or LOCAL
167 (coordinates do not respect XY0)
168 """
169 slices, origin = splitSliceArgs(sliceArgs)
170 if isinstance(slices, Point2I):
171 return slices.getX(), slices.getY(), origin
172 elif isinstance(slices, Box2I):
173 x0 = slices.getMinX()
174 y0 = slices.getMinY()
175 return slice(x0, x0 + slices.getWidth()), slice(y0, y0 + slices.getHeight()), origin
176 elif isinstance(slices, slice):
177 if slices.start is not None or slices.stop is not None or slices.step is not None:
178 raise TypeError("Single-dimension slices must not have bounds.")
179 x = slices
180 y = slices
181 origin = LOCAL # doesn't matter, as the slices cover the full image
182 else:
183 x, y = slices
184
185 bbox = bboxGetter(origin)
186 if isinstance(x, slice):
187 if isinstance(y, slice):
188 xSlice = slice(handleNegativeIndex(x.start, bbox.getWidth(), origin, default=bbox.getBeginX()),
189 handleNegativeIndex(x.stop, bbox.getWidth(), origin, default=bbox.getEndX()))
190 ySlice = slice(handleNegativeIndex(y.start, bbox.getHeight(), origin, default=bbox.getBeginY()),
191 handleNegativeIndex(y.stop, bbox.getHeight(), origin, default=bbox.getEndY()))
192 return xSlice, ySlice, origin
193 raise TypeError("Mixed indices of the form (slice, int) are not supported for images.")
194
195 if isinstance(y, slice):
196 raise TypeError("Mixed indices of the form (int, slice) are not supported for images.")
197 x = handleNegativeIndex(x, bbox.getWidth(), origin, default=None)
198 y = handleNegativeIndex(y, bbox.getHeight(), origin, default=None)
199 return x, y, origin
200
201
202def imageIndicesToNumpy(sliceArgs, bboxGetter):
203 """Convert slicing format to numpy
204
205 LSST `afw` image-like objects use an `[x,y]` coordinate
206 convention, accept `Point2I` and `Box2I`
207 objects for slicing, and slice relative to the
208 bounding box `XY0` location;
209 while python and numpy use the convention `[y,x]`
210 with no `XY0`, so this method converts the `afw`
211 indices or slices into numpy indices or slices
212
213 Parameters
214 ----------
215 sliceArgs: `sequence`, `Point2I` or `Box2I`
216 An `(xIndex, yIndex)` pair, or a single `(xIndex,)` tuple,
217 where `xIndex` and `yIndex` can be a `slice` or `int`,
218 or list of `int` objects, and if only a single `xIndex` is
219 given, a `Point2I` or `Box2I`.
220 bboxGetter : callable
221 Callable that accepts an ImageOrigin enum value and returns the
222 appropriate image bounding box. Usually the bound getBBox method
223 of an Image, Mask, or MaskedImage object.
224
225 Returns
226 -------
227 y: `int` or `slice`
228 Index or `slice` in the y dimension
229 x: `int` or `slice`
230 Index or `slice` in the x dimension
231 bbox: `Box2I`
232 Bounding box of the image.
233 If `bbox` is `None` then the result is a point and
234 not a subset of an image.
235 """
236 # Use a common slicing algorithm as single band images
237 x, y, origin = interpretSliceArgs(sliceArgs, bboxGetter)
238 x0 = bboxGetter().getMinX()
239 y0 = bboxGetter().getMinY()
240
241 if origin == PARENT:
242 if isinstance(x, slice):
243 assert isinstance(y, slice)
244 bbox = Box2I(Point2I(x.start, y.start), Extent2I(x.stop-x.start, y.stop-y.start))
245 x = slice(x.start - x0, x.stop - x0)
246 y = slice(y.start - y0, y.stop - y0)
247 else:
248 x = x - x0
249 y = y - y0
250 bbox = None
251 return y, x, bbox
252 elif origin != LOCAL:
253 raise ValueError("Unrecognized value for origin")
254
255 # Use a local bounding box
256 if isinstance(x, slice):
257 assert isinstance(y, slice)
258 bbox = Box2I(Point2I(x.start + x0, y.start + y0),
259 Extent2I(x.stop-x.start, y.stop-y.start))
260 else:
261 bbox = None
262 return y, x, bbox
263
264
265def supportSlicing(cls):
266 """Support image slicing
267 """
268
269 def Factory(self, *args, **kwargs):
270 """Return an object of this type
271 """
272 return cls(*args, **kwargs)
273 cls.Factory = Factory
274
275 def clone(self):
276 """Return a deep copy of self"""
277 return cls(self, True)
278 cls.clone = clone
279
280 def __getitem__(self, imageSlice): # noqa: N807
281 box, index, origin = translateSliceArgs(imageSlice, self.getBBox)
282 if box is not None:
283 return self.subset(box, origin=origin)
284 return self._get(index, origin=origin)
285 cls.__getitem__ = __getitem__
286
287 def __setitem__(self, imageSlice, rhs): # noqa: N807
288 box, index, origin = translateSliceArgs(imageSlice, self.getBBox)
289 if box is not None:
290 if self.assign(rhs, box, origin) is NotImplemented:
291 lhs = self.subset(box, origin=origin)
292 lhs.set(rhs)
293 else:
294 self._set(index, origin=origin, value=rhs)
295 cls.__setitem__ = __setitem__
An integer coordinate rectangle.
Definition: Box.h:55
def translateSliceArgs(sliceArgs, bboxGetter)
Definition: _slicing.py:111
def handleNegativeIndex(index, size, origin, default)
Definition: _slicing.py:65
def splitSliceArgs(sliceArgs)
Definition: _slicing.py:28
def interpretSliceArgs(sliceArgs, bboxGetter)
Definition: _slicing.py:147