Coverage for python / lsst / images / _concrete_bounds.py: 18%
70 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-06 08:48 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-06 08:48 +0000
1# This file is part of lsst-images.
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# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = ("SerializableBounds", "deserialize_bounds")
16import pydantic
18from ._cell_grid import CellGridBounds
19from ._geom import Bounds, Box, NoOverlapError
20from ._intersection_bounds import IntersectionBounds
23# The cyclic dependencies prevent this from going in _intersection_bounds.py.
24class IntersectionBoundsSerializationModel(pydantic.BaseModel):
25 """Serialization model for `IntersectionBounds`."""
27 a: SerializableBounds
28 b: SerializableBounds
31type SerializableBounds = Box | CellGridBounds | IntersectionBoundsSerializationModel
34IntersectionBoundsSerializationModel.model_rebuild()
37def deserialize_bounds(serialized: SerializableBounds) -> Bounds:
38 """Convert a serialized bounds object into its in-memory form."""
39 match serialized:
40 case Box() | CellGridBounds():
41 return serialized # type: ignore[return-value]
42 case IntersectionBoundsSerializationModel():
43 return IntersectionBounds.deserialize(serialized)
44 raise RuntimeError(f"Cannot deserialize {serialized!r}.")
47def _intersect_box(lhs: Box, rhs: Bounds) -> Bounds:
48 """Return the intersection between a `Box` and an arbitrary `Bounds`
49 object.
51 When there is no overlap, `NoOverlapError` is raised.
52 """
53 match rhs:
54 case Box():
55 return _intersect_box_box(lhs, rhs)
56 case CellGridBounds():
57 return _intersect_box_cgb(lhs, rhs)
58 case IntersectionBounds():
59 return _intersect_ib(rhs, lhs)
60 case _:
61 raise TypeError(f"Unrecognized bounds type: {rhs}.")
64def _intersect_cgb(lhs: CellGridBounds, rhs: Bounds) -> Bounds:
65 """Return the intersection between a `cellsCellGridBounds` and an
66 arbitrary `Bounds` object.
68 When there is no overlap, `NoOverlapError` is raised.
69 """
70 match rhs:
71 case Box():
72 return _intersect_box_cgb(rhs, lhs)
73 case CellGridBounds():
74 return _intersect_cgb_cgb(lhs, rhs)
75 case IntersectionBounds():
76 return _intersect_ib(rhs, lhs)
77 case _:
78 raise TypeError(f"Unrecognized bounds type: {rhs}.")
81def _intersect_ib(lhs: IntersectionBounds, rhs: Bounds) -> Bounds:
82 """Return the intersection between an `IntersectionBounds` and an
83 arbitrary `Bounds` object.
85 When there is no overlap, `NoOverlapError` is raised.
86 """
87 a_intersection = lhs._a.intersection(rhs)
88 if isinstance(a_intersection, IntersectionBounds):
89 # Intersection with the 'a' operand didn't simplify; try the 'b'
90 # operand instead.
91 return lhs._a.intersection(lhs._b.intersection(rhs))
92 else:
93 return a_intersection.intersection(lhs._b)
96def _intersect_box_box(lhs: Box, rhs: Box) -> Box:
97 """Return the intersection of two boxes.
99 When there is no overlap between the boxes, `NoOverlapError` is raised.
100 """
101 intervals = []
102 for a, b in zip(lhs._intervals, rhs._intervals, strict=True):
103 try:
104 intervals.append(a.intersection(b))
105 except NoOverlapError as err:
106 err.add_note(f"In intersection between {a} and {b}.")
107 raise
108 return Box(*intervals)
111def _intersect_cgb_cgb(lhs: CellGridBounds, rhs: CellGridBounds) -> CellGridBounds | IntersectionBounds:
112 """Return the intersection of two `cells.CellGridBounds`.
114 When there is no overlap, `NoOverlapError` is raised.
115 """
116 bbox = _intersect_box_box(lhs.bbox, rhs.bbox) # will raise if they don't overlap
117 if lhs.grid == rhs.grid:
118 sliced_lhs = lhs[bbox]
119 sliced_rhs = rhs[bbox]
120 assert sliced_lhs.bbox == sliced_rhs.bbox, "Should be guaranteed by the common grid."
121 return CellGridBounds(
122 grid=sliced_lhs.grid, bbox=bbox, missing=frozenset(sliced_lhs.missing | sliced_rhs.missing)
123 )
124 # When the grids don't align, we just return a lazy intersection.
125 return IntersectionBounds(lhs, rhs)
128def _intersect_box_cgb(lhs: Box, rhs: CellGridBounds) -> Box | CellGridBounds | IntersectionBounds:
129 """Return the intersection of a `Box` and a `cells.CellGridBounds`.
131 When there is no overlap, `NoOverlapError` is raised.
132 """
133 bbox = _intersect_box_box(lhs, rhs.bbox) # will raise if they don't overlap
134 if bbox == rhs.bbox:
135 # lhs wholly contains rhs
136 return rhs
137 sliced_rhs = rhs[bbox]
138 if not sliced_rhs.missing:
139 # There are no missing cells in the intersection, so the intersection
140 # is just the bbox intersection.
141 return bbox
142 if bbox == sliced_rhs.bbox:
143 # The bbox intersection happens to be snapped to the cell grid.
144 return sliced_rhs
145 # General case: the box intersection is not snapped to the cell grid, so
146 # we need to use an IntersectionBounds to apply a lazy window to the
147 # cell grid bounds.
148 return IntersectionBounds(lhs, rhs)