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