LSST Applications g0f08755f38+9c285cab97,g1635faa6d4+13f3999e92,g1653933729+a8ce1bb630,g1a0ca8cf93+bf6eb00ceb,g28da252d5a+0829b12dee,g29321ee8c0+5700dc9eac,g2bbee38e9b+9634bc57db,g2bc492864f+9634bc57db,g2cdde0e794+c2c89b37c4,g3156d2b45e+41e33cbcdc,g347aa1857d+9634bc57db,g35bb328faa+a8ce1bb630,g3a166c0a6a+9634bc57db,g3e281a1b8c+9f2c4e2fc3,g414038480c+077ccc18e7,g41af890bb2+fde0dd39b6,g5fbc88fb19+17cd334064,g781aacb6e4+a8ce1bb630,g80478fca09+55a9465950,g82479be7b0+d730eedb7d,g858d7b2824+9c285cab97,g9125e01d80+a8ce1bb630,g9726552aa6+10f999ec6a,ga5288a1d22+2a84bb7594,gacf8899fa4+c69c5206e8,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+9634bc57db,gcf0d15dbbd+4b7d09cae4,gda3e153d99+9c285cab97,gda6a2b7d83+4b7d09cae4,gdaeeff99f8+1711a396fd,ge2409df99d+5e831397f4,ge79ae78c31+9634bc57db,gf0baf85859+147a0692ba,gf3967379c6+41c94011de,gf3fb38a9a8+8f07a9901b,gfb92a5be7c+9c285cab97,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | Public Attributes | Protected Member Functions | List of all members
lsst.scarlet.lite.bbox.Box Class Reference

Public Member Functions

 __init__ (self, tuple[int,...] shape, tuple[int,...]|None origin=None)
 
bool contains (self, Sequence[int] p)
 
int ndim (self)
 
tuple[int,...] start (self)
 
tuple[int,...] stop (self)
 
tuple[float,...] center (self)
 
tuple[tuple[int, int],...] bounds (self)
 
tuple[slice,...] slices (self)
 
Box grow (self, int|tuple[int,...] radius)
 
Box shifted_by (self, Sequence[int] shift)
 
bool intersects (self, Box other)
 
tuple[tuple[slice,...], tuple[slice,...]] overlapped_slices (self, Box other)
 
Box __or__ (self, Box other)
 
Box __and__ (self, Box other)
 
Box __getitem__ (self, int|slice|tuple[int,...] index)
 
str __repr__ (self)
 
Box __add__ (self, int|Sequence[int] offset)
 
Box __sub__ (self, int|Sequence[int] offset)
 
Box __matmul__ (self, Box bbox)
 
Box __copy__ (self)
 
Box copy (self)
 
bool __eq__ (self, object other)
 
int __hash__ (self)
 

Static Public Member Functions

Box from_bounds (*tuple[int,...] bounds)
 
Box from_data (np.ndarray x, float threshold=0)
 

Public Attributes

 shape
 
 origin
 
 ndim
 

Protected Member Functions

tuple[int,...] _offset_to_tuple (self, int|Sequence[int] offset)
 

Detailed Description

Bounding Box for an object

A Bounding box describes the location of a data unit in the
global/model coordinate system, using the row-major
(default numpy/C++) ordering convention.
So, for example, a 2D image will have shape ``(height, width)``,
however the bounding `Box` code is agnostic as to number of dimensions
or the meaning of those dimensions.

Examples
--------

At a minimum a new `Box` can be initialized using the ``shape`` of the
region it describes:

>>> from lsst.scarlet.lite import Box
>>> bbox = Box((3, 4, 5, 6))
>>> print(bbox)
Box(shape=(3, 4, 5, 6), origin=(0, 0, 0, 0))

If the region described by the `Box` is offset from the zero origin,
a new ``origin`` can be passed to the constructor

>>> bbox = Box((3, 4, 5, 6), (2, 4, 7, 9))
>>> print(bbox)
Box(shape=(3, 4, 5, 6), origin=(2, 4, 7, 9))

It is also possible to initialize a `Box` from a collection of tuples,
where tuple is a pair of integers representing the
first and last index in each dimension. For example:

>>> bbox = Box.from_bounds((3, 6), (11, 21))
>>> print(bbox)
Box(shape=(3, 10), origin=(3, 11))

It is also possible to initialize a `Box` by thresholding a numpy array
and including only the region of the image above the threshold in the
resulting `Box`. For example

>>> from lsst.scarlet.lite.utils import integrated_circular_gaussian
>>> data = integrated_circular_gaussian(sigma=1.0)
>>> bbox = Box.from_data(data, 1e-2)
>>> print(bbox)
Box(shape=(5, 5), origin=(5, 5))

The `Box` class contains a number of convenience methods that can be used
to extract subsets of an array, combine bounding boxes, etc.

For example, using the ``data`` and ``bbox`` from the end of the previous
section, the portion of the data array that is contained in the bounding
box can be extraced usng the `Box.slices` method:

>>> subset = data[bbox.slices]

The intersection of two boxes can be calcualted using the ``&`` operator,
for example

>>> bbox = Box((5, 5)) & Box((5, 5), (2, 2))
>>> print(bbox)
Box(shape=(3, 3), origin=(2, 2))

Similarly, the union of two boxes can be calculated using the ``|``
operator:

>>> bbox = Box((5, 5)) | Box((5, 5), (2, 2))
>>> print(bbox)
Box(shape=(7, 7), origin=(0, 0))

To find out of a point is located in a `Box` use

>>> contains = bbox.contains((3, 3))
>>> print(contains)
True

To find out if two boxes intersect (in other words ``box1 & box2`` has a
non-zero size) use

>>> intersects = bbox.intersects(Box((10, 10), (100, 100)))
>>> print(intersects)
False

It is also possible to shift a box by a vector (sequence):

>>> bbox = bbox + (50, 60)
>>> print(bbox)
Box(shape=(7, 7), origin=(50, 60))

which can also be negative

>>> bbox = bbox - (5, -5)
>>> print(bbox)
Box(shape=(7, 7), origin=(45, 65))

Boxes can also be converted into higher dimensions using the
``@`` operator:

>>> bbox1 = Box((10,), (3, ))
>>> bbox2 = Box((101, 201), (18, 21))
>>> bbox = bbox1 @ bbox2
>>> print(bbox)
Box(shape=(10, 101, 201), origin=(3, 18, 21))

Boxes are equal when they have the same shape and the same origin, so

>>> print(Box((10, 10), (5, 5)) == Box((10, 10), (5, 5)))
True

>>> print(Box((10, 10), (5, 5)) == Box((10, 10), (4, 4)))
False

Finally, it is common to insert one array into another when their bounding
boxes only partially overlap.
In order to correctly insert the overlapping portion of the array it is
convenient to calculate the slices from each array that overlap.
For example:

>>> import numpy as np
>>> x = np.arange(12).reshape(3, 4)
>>> y = np.arange(9).reshape(3, 3)
>>> print(x)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> print(y)
[[0 1 2]
 [3 4 5]
 [6 7 8]]
>>> x_box = Box.from_data(x) + (3, 4)
>>> y_box = Box.from_data(y) + (1, 3)
>>> slices = x_box.overlapped_slices(y_box)
>>> x[slices[0]] += y[slices[1]]
>>> print(x)
[[ 7  9  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Parameters
----------
shape:
    Size of the box in each dimension.
origin:
    Minimum corner coordinate of the box.
    This defaults to ``(0, ...)``.

Definition at line 31 of file bbox.py.

Constructor & Destructor Documentation

◆ __init__()

lsst.scarlet.lite.bbox.Box.__init__ ( self,
tuple[int, ...] shape,
tuple[int, ...] | None origin = None )

Definition at line 177 of file bbox.py.

177 def __init__(self, shape: tuple[int, ...], origin: tuple[int, ...] | None = None):
178 self.shape = tuple(shape)
179 if origin is None:
180 origin = (0,) * len(shape)
181 if len(origin) != len(shape):
182 msg = "Mismatched origin and shape dimensions. "
183 msg += f"Received {len(origin)} and {len(shape)}"
184 raise ValueError(msg)
185 self.origin = tuple(origin)
186

Member Function Documentation

◆ __add__()

Box lsst.scarlet.lite.bbox.Box.__add__ ( self,
int | Sequence[int] offset )
Generate a new Box with a shifted offset

Parameters
----------
offset:
    The amount to shift the current offset

Returns
-------
result:
    The shifted box.

Definition at line 416 of file bbox.py.

416 def __add__(self, offset: int | Sequence[int]) -> Box:
417 """Generate a new Box with a shifted offset
418
419 Parameters
420 ----------
421 offset:
422 The amount to shift the current offset
423
424 Returns
425 -------
426 result:
427 The shifted box.
428 """
429 return self.shifted_by(self._offset_to_tuple(offset))
430

◆ __and__()

Box lsst.scarlet.lite.bbox.Box.__and__ ( self,
Box other )
Intersection of two bounding boxes

If there is no intersection between the two bounding
boxes then an empty bounding box is returned.

Parameters
----------
other:
    The other bounding box in the intersection

Returns
-------
result:
    The rectangular box that is in the overlap region
    of both boxes.

Definition at line 355 of file bbox.py.

355 def __and__(self, other: Box) -> Box:
356 """Intersection of two bounding boxes
357
358 If there is no intersection between the two bounding
359 boxes then an empty bounding box is returned.
360
361 Parameters
362 ----------
363 other:
364 The other bounding box in the intersection
365
366 Returns
367 -------
368 result:
369 The rectangular box that is in the overlap region
370 of both boxes.
371 """
372 if other.ndim != self.ndim:
373 raise ValueError(f"Dimension mismatch in the boxes {other=} and {self=}")
374
375 bounds = []
376 for d in range(self.ndim):
377 bounds.append((max(self.start[d], other.start[d]), min(self.stop[d], other.stop[d])))
378 return Box.from_bounds(*bounds)
379
int min
int max

◆ __copy__()

Box lsst.scarlet.lite.bbox.Box.__copy__ ( self)
Copy of the box

Definition at line 465 of file bbox.py.

465 def __copy__(self) -> Box:
466 """Copy of the box"""
467 return Box(self.shape, origin=self.origin)
468

◆ __eq__()

bool lsst.scarlet.lite.bbox.Box.__eq__ ( self,
object other )
Check for equality.

Two boxes are equal when they have the same shape and origin.

Definition at line 473 of file bbox.py.

473 def __eq__(self, other: object) -> bool:
474 """Check for equality.
475
476 Two boxes are equal when they have the same shape and origin.
477 """
478 if not hasattr(other, "shape") and not hasattr(other, "origin"):
479 return False
480 return self.shape == other.shape and self.origin == other.origin # type: ignore
481

◆ __getitem__()

Box lsst.scarlet.lite.bbox.Box.__getitem__ ( self,
int | slice | tuple[int, ...] index )

Definition at line 380 of file bbox.py.

380 def __getitem__(self, index: int | slice | tuple[int, ...]) -> Box:
381 if isinstance(index, int) or isinstance(index, slice):
382 s_ = self.shape[index]
383 o_ = self.origin[index]
384 if isinstance(s_, int):
385 s_ = (s_,)
386 o_ = (cast(int, o_),) # type: ignore
387 else:
388 iter(index)
389 # If I is a Sequence then select the indices in `index`, in order
390 s_ = tuple(self.shape[i] for i in index)
391 o_ = tuple(self.origin[i] for i in index)
392 return Box(s_, origin=cast(tuple[int, ...], o_))
393

◆ __hash__()

int lsst.scarlet.lite.bbox.Box.__hash__ ( self)

Definition at line 482 of file bbox.py.

482 def __hash__(self) -> int:
483 return hash((self.shape, self.origin))
484
485

◆ __matmul__()

Box lsst.scarlet.lite.bbox.Box.__matmul__ ( self,
Box bbox )
Combine two Boxes into a higher dimensional box

Parameters
----------
bbox:
    The box to append to this box.

Returns
-------
result:
    The combined Box.

Definition at line 448 of file bbox.py.

448 def __matmul__(self, bbox: Box) -> Box:
449 """Combine two Boxes into a higher dimensional box
450
451 Parameters
452 ----------
453 bbox:
454 The box to append to this box.
455
456 Returns
457 -------
458 result:
459 The combined Box.
460 """
461 bounds = self.bounds + bbox.bounds
462 result = Box.from_bounds(*bounds)
463 return result
464

◆ __or__()

Box lsst.scarlet.lite.bbox.Box.__or__ ( self,
Box other )
Union of two bounding boxes

Parameters
----------
other:
    The other bounding box in the union

Returns
-------
result:
    The smallest rectangular box that contains *both* boxes.

Definition at line 335 of file bbox.py.

335 def __or__(self, other: Box) -> Box:
336 """Union of two bounding boxes
337
338 Parameters
339 ----------
340 other:
341 The other bounding box in the union
342
343 Returns
344 -------
345 result:
346 The smallest rectangular box that contains *both* boxes.
347 """
348 if other.ndim != self.ndim:
349 raise ValueError(f"Dimension mismatch in the boxes {other} and {self}")
350 bounds = []
351 for d in range(self.ndim):
352 bounds.append((min(self.start[d], other.start[d]), max(self.stop[d], other.stop[d])))
353 return Box.from_bounds(*bounds)
354

◆ __repr__()

str lsst.scarlet.lite.bbox.Box.__repr__ ( self)

Definition at line 394 of file bbox.py.

394 def __repr__(self) -> str:
395 return f"Box(shape={self.shape}, origin={self.origin})"
396

◆ __sub__()

Box lsst.scarlet.lite.bbox.Box.__sub__ ( self,
int | Sequence[int] offset )
Generate a new Box with a shifted offset in the negative direction

Parameters
----------
offset:
    The amount to shift the current offset

Returns
-------
result:
    The shifted box.

Definition at line 431 of file bbox.py.

431 def __sub__(self, offset: int | Sequence[int]) -> Box:
432 """Generate a new Box with a shifted offset in the negative direction
433
434 Parameters
435 ----------
436 offset:
437 The amount to shift the current offset
438
439 Returns
440 -------
441 result:
442 The shifted box.
443 """
444 offset = self._offset_to_tuple(offset)
445 offset = tuple(-o for o in offset)
446 return self.shifted_by(offset)
447

◆ _offset_to_tuple()

tuple[int, ...] lsst.scarlet.lite.bbox.Box._offset_to_tuple ( self,
int | Sequence[int] offset )
protected
Expand an integer offset into a tuple

Parameters
----------
offset:
    The offset to (potentially) convert into a tuple.

Returns
-------
offset:
    The offset as a tuple.

Definition at line 397 of file bbox.py.

397 def _offset_to_tuple(self, offset: int | Sequence[int]) -> tuple[int, ...]:
398 """Expand an integer offset into a tuple
399
400 Parameters
401 ----------
402 offset:
403 The offset to (potentially) convert into a tuple.
404
405 Returns
406 -------
407 offset:
408 The offset as a tuple.
409 """
410 if isinstance(offset, int):
411 _offset = (offset,) * self.ndim
412 else:
413 _offset = tuple(offset)
414 return _offset
415

◆ bounds()

tuple[tuple[int, int], ...] lsst.scarlet.lite.bbox.Box.bounds ( self)
Bounds of the box

Definition at line 267 of file bbox.py.

267 def bounds(self) -> tuple[tuple[int, int], ...]:
268 """Bounds of the box"""
269 return tuple((o, o + s) for o, s in zip(self.origin, self.shape))
270

◆ center()

tuple[float, ...] lsst.scarlet.lite.bbox.Box.center ( self)
Tuple of center coordinates

Definition at line 262 of file bbox.py.

262 def center(self) -> tuple[float, ...]:
263 """Tuple of center coordinates"""
264 return tuple(o + s / 2 for o, s in zip(self.origin, self.shape))
265

◆ contains()

bool lsst.scarlet.lite.bbox.Box.contains ( self,
Sequence[int] p )
Whether the box contains a given coordinate `p`

Definition at line 236 of file bbox.py.

236 def contains(self, p: Sequence[int]) -> bool:
237 """Whether the box contains a given coordinate `p`"""
238 if len(p) != self.ndim:
239 raise ValueError(f"Dimension mismatch in {p} and {self.ndim}")
240
241 for d in range(self.ndim):
242 if not (p[d] >= self.origin[d] and (p[d] < (self.origin[d] + self.shape[d]))):
243 return False
244 return True
245

◆ copy()

Box lsst.scarlet.lite.bbox.Box.copy ( self)
Copy of the box

Definition at line 469 of file bbox.py.

469 def copy(self) -> Box:
470 """Copy of the box"""
471 return self.__copy__()
472

◆ from_bounds()

Box lsst.scarlet.lite.bbox.Box.from_bounds ( *tuple[int, ...] bounds)
static
Initialize a box from its bounds

Parameters
----------
bounds:
    Min/Max coordinate for every dimension

Returns
-------
bbox:
    A new box bounded by the input bounds.

Definition at line 188 of file bbox.py.

188 def from_bounds(*bounds: tuple[int, ...]) -> Box:
189 """Initialize a box from its bounds
190
191 Parameters
192 ----------
193 bounds:
194 Min/Max coordinate for every dimension
195
196 Returns
197 -------
198 bbox:
199 A new box bounded by the input bounds.
200 """
201 shape = tuple(max(0, cmax - cmin) for cmin, cmax in bounds)
202 origin = tuple(cmin for cmin, cmax in bounds)
203 return Box(shape, origin=origin)
204

◆ from_data()

Box lsst.scarlet.lite.bbox.Box.from_data ( np.ndarray x,
float threshold = 0 )
static
Define range of `x` above `min_value`.

This method creates the smallest `Box` that contains all of the
elements in `x` that are above `min_value`.

Parameters
----------
x:
    Data to threshold to specify the shape/dimensionality of `x`.
threshold:
    Threshold for the data.
    The box is trimmed so that all elements bordering `x` smaller than
    `min_value` are ignored.

Returns
-------
bbox:
    Bounding box for the thresholded `x`

Definition at line 206 of file bbox.py.

206 def from_data(x: np.ndarray, threshold: float = 0) -> Box:
207 """Define range of `x` above `min_value`.
208
209 This method creates the smallest `Box` that contains all of the
210 elements in `x` that are above `min_value`.
211
212 Parameters
213 ----------
214 x:
215 Data to threshold to specify the shape/dimensionality of `x`.
216 threshold:
217 Threshold for the data.
218 The box is trimmed so that all elements bordering `x` smaller than
219 `min_value` are ignored.
220
221 Returns
222 -------
223 bbox:
224 Bounding box for the thresholded `x`
225 """
226 sel = x > threshold
227 if sel.any():
228 nonzero = np.where(sel)
229 bounds = []
230 for dim in range(len(x.shape)):
231 bounds.append((nonzero[dim].min(), nonzero[dim].max() + 1))
232 else:
233 bounds = [(0, 0)] * len(x.shape)
234 return Box.from_bounds(*bounds)
235

◆ grow()

Box lsst.scarlet.lite.bbox.Box.grow ( self,
int | tuple[int, ...] radius )
Grow the Box by the given radius in each direction

Definition at line 278 of file bbox.py.

278 def grow(self, radius: int | tuple[int, ...]) -> Box:
279 """Grow the Box by the given radius in each direction"""
280 if isinstance(radius, int):
281 radius = tuple([radius] * self.ndim)
282 origin = tuple([self.origin[d] - radius[d] for d in range(self.ndim)])
283 shape = tuple([self.shape[d] + 2 * radius[d] for d in range(self.ndim)])
284 return Box(shape, origin=origin)
285

◆ intersects()

bool lsst.scarlet.lite.bbox.Box.intersects ( self,
Box other )
Check if two boxes overlap

Parameters
----------
other:
    The boxes to check for overlap

Returns
-------
result:
    True when the two boxes overlap.

Definition at line 302 of file bbox.py.

302 def intersects(self, other: Box) -> bool:
303 """Check if two boxes overlap
304
305 Parameters
306 ----------
307 other:
308 The boxes to check for overlap
309
310 Returns
311 -------
312 result:
313 True when the two boxes overlap.
314 """
315 overlap = self & other
316 return np.all(np.array(overlap.shape) != 0) # type: ignore
317

◆ ndim()

int lsst.scarlet.lite.bbox.Box.ndim ( self)
Dimensionality of this BBox

Definition at line 247 of file bbox.py.

247 def ndim(self) -> int:
248 """Dimensionality of this BBox"""
249 return len(self.shape)
250
table::Key< int > ndim
Definition PsfexPsf.cc:329

◆ overlapped_slices()

tuple[tuple[slice, ...], tuple[slice, ...]] lsst.scarlet.lite.bbox.Box.overlapped_slices ( self,
Box other )
Return `slice` for the box that contains the overlap of this and
another `Box`

Parameters
----------
other:

Returns
-------
slices:
    The slice of an array bounded by `self` and
    the slice of an array bounded by `other` in the
    overlapping region.

Definition at line 318 of file bbox.py.

318 def overlapped_slices(self, other: Box) -> tuple[tuple[slice, ...], tuple[slice, ...]]:
319 """Return `slice` for the box that contains the overlap of this and
320 another `Box`
321
322 Parameters
323 ----------
324 other:
325
326 Returns
327 -------
328 slices:
329 The slice of an array bounded by `self` and
330 the slice of an array bounded by `other` in the
331 overlapping region.
332 """
333 return overlapped_slices(self, other)
334

◆ shifted_by()

Box lsst.scarlet.lite.bbox.Box.shifted_by ( self,
Sequence[int] shift )
Generate a shifted copy of this box

Parameters
----------
shift:
    The amount to shift each axis to create the new box

Returns
-------
result:
    The resulting bounding box.

Definition at line 286 of file bbox.py.

286 def shifted_by(self, shift: Sequence[int]) -> Box:
287 """Generate a shifted copy of this box
288
289 Parameters
290 ----------
291 shift:
292 The amount to shift each axis to create the new box
293
294 Returns
295 -------
296 result:
297 The resulting bounding box.
298 """
299 origin = tuple(o + shift[i] for i, o in enumerate(self.origin))
300 return Box(self.shape, origin=origin)
301

◆ slices()

tuple[slice, ...] lsst.scarlet.lite.bbox.Box.slices ( self)
Bounds of the box as slices

Definition at line 272 of file bbox.py.

272 def slices(self) -> tuple[slice, ...]:
273 """Bounds of the box as slices"""
274 if np.any(self.origin) < 0:
275 raise ValueError("Cannot get slices for a box with negative indices")
276 return tuple([slice(o, o + s) for o, s in zip(self.origin, self.shape)])
277

◆ start()

tuple[int, ...] lsst.scarlet.lite.bbox.Box.start ( self)
Tuple of start coordinates

Definition at line 252 of file bbox.py.

252 def start(self) -> tuple[int, ...]:
253 """Tuple of start coordinates"""
254 return self.origin
255

◆ stop()

tuple[int, ...] lsst.scarlet.lite.bbox.Box.stop ( self)
Tuple of stop coordinates

Definition at line 257 of file bbox.py.

257 def stop(self) -> tuple[int, ...]:
258 """Tuple of stop coordinates"""
259 return tuple(o + s for o, s in zip(self.origin, self.shape))
260

Member Data Documentation

◆ ndim

lsst.scarlet.lite.bbox.Box.ndim

Definition at line 241 of file bbox.py.

◆ origin

lsst.scarlet.lite.bbox.Box.origin

Definition at line 185 of file bbox.py.

◆ shape

lsst.scarlet.lite.bbox.Box.shape

Definition at line 178 of file bbox.py.


The documentation for this class was generated from the following file: