Coverage for python / lsst / cell_coadds / _stitched_image_planes.py: 40%
68 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:54 +0000
1# This file is part of cell_coadds.
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/>.
22from __future__ import annotations
24__all__ = ("StitchedImagePlanes",)
26from abc import abstractmethod
27from collections.abc import Callable, Iterator, Sequence, Set
28from functools import partial
29from typing import TYPE_CHECKING, TypeVar
31import numpy as np
33from lsst.afw.image import ImageF, Mask
35from . import typing_helpers
36from ._image_planes import ImagePlanes
37from ._uniform_grid import UniformGrid
39_T = TypeVar("_T", bound=typing_helpers.ImageLike)
42if TYPE_CHECKING: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true
43 from .typing_helpers import ImageLike
46class StitchedImagePlanes(ImagePlanes):
47 """An ImagePlanes intermediate base class that stitches together per-cell
48 images.
50 Parameters
51 ----------
52 bbox : `Box2I`
53 The region over which contiguous piecewise images are desired.
55 Notes
56 -----
57 This class simply inserts subimages from each cell into the full image,
58 doing so when an attribute is first accessed to avoid stitching together
59 planes that may never be accessed.
60 """
62 def __init__(self) -> None:
63 self._image: ImageLike | None = None
64 self._mask: Mask | None = None
65 self._variance: ImageLike | None = None
66 self._mask_fractions: ImageLike | None = None
67 self._noise_realizations: Sequence[ImageLike] | None = None
69 @property
70 @abstractmethod
71 def grid(self) -> UniformGrid:
72 """Object that defines the piecewise grid this object stitches
73 together.
75 This may include cells outside the region covered by these image
76 planes.
77 """
78 raise NotImplementedError()
80 @property
81 @abstractmethod
82 def n_noise_realizations(self) -> int:
83 """The number of noise realizations cells are guaranteed to have."""
84 raise NotImplementedError()
86 @property
87 @abstractmethod
88 def mask_fraction_names(self) -> Set[str]:
89 """The names of all mask planes whose fractions were propagated in any
90 cell.
92 Cells that do not have a mask fraction for a particular name may be
93 assumed to have the fraction for that mask plane uniformly zero.
94 """
95 raise NotImplementedError()
97 @property
98 def image(self) -> ImageLike:
99 # Docstring inherited.
100 if self._image is None:
101 self._image = self._make_plane(ImageF(self.bbox), lambda planes: planes.image)
102 return self._image
104 def uncache_image(self) -> None:
105 """Remove any cached `image` plane."""
106 self._image = None
108 @property
109 def mask(self) -> Mask:
110 # Docstring inherited.
111 if self._mask is None:
112 self._mask = self._make_plane(
113 Mask(self.bbox, Mask.getPlaneBitMask("NO_DATA")), lambda planes: planes.mask
114 )
115 return self._mask
117 def uncache_mask(self) -> None:
118 """Remove any cached `mask` plane."""
119 self._mask = None
121 @property
122 def variance(self) -> ImageLike:
123 # Docstring inherited.
124 if self._variance is None:
125 self._variance = self._make_plane(ImageF(self.bbox, np.inf), lambda planes: planes.variance)
126 return self._variance
128 def uncache_variance(self) -> None:
129 """Remove any cached `variance` plane."""
130 self._variance = None
132 @property
133 def mask_fractions(self) -> ImageLike | None:
134 # Docstring inherited.
135 if self._mask_fractions is None:
136 self._mask_fractions = self._make_plane(ImageF(self.bbox), lambda planes: planes.mask_fractions)
138 return self._mask_fractions
140 def uncache_mask_fraction(self) -> None:
141 """Remove any cached `mask_fraction` planes."""
142 self._mask_fractions = None
144 @staticmethod
145 def _noise_plane_getter(planes: ImagePlanes, i: int) -> ImageF:
146 return planes.noise_realizations[i]
148 @property
149 def noise_realizations(self) -> Sequence[ImageF]:
150 # Docstring inherited.
151 if self._noise_realizations is None:
152 # Could make this lazier with a custom Sequence class (only stitch
153 # a noise plane if that plane is requested), but not clear it's
154 # worth the effort.
155 self._noise_realizations = tuple(
156 self._make_plane(ImageF(self.bbox), partial(self._noise_plane_getter, i=i))
157 for i in range(self.n_noise_realizations)
158 )
159 return self._noise_realizations
161 def uncache_noise_realizations(self) -> None:
162 """Remove any cached `noise_realization` planes."""
163 self._noise_realizations = None
165 def _make_plane(self, result: _T, getter: Callable[[ImagePlanes], _T | None]) -> _T:
166 """Stitch together a single image plane.
168 Parameters
169 ----------
170 result : ImageLike
171 The out `~lsst.afw.image.Image` or `~lsst.afw.image.Mask` instance
172 covering the full area, to be assigned to.
173 getter : `Callable`
174 Callable that obtains the appropriate image-like object to assign
175 a subimage from, given an `ImagePlanes` instance from a cell inner
176 region.
178 Returns
179 -------
180 result : image-like
181 The same result object passed in.
182 """
183 for cell_planes in self._iter_cell_planes():
184 common_bbox = cell_planes.bbox.clippedTo(self.bbox)
185 if not common_bbox.isEmpty():
186 input_plane = getter(cell_planes)
187 if input_plane is None:
188 result[common_bbox] = 0
189 else:
190 result[common_bbox] = input_plane[common_bbox]
191 return result
193 @abstractmethod
194 def _iter_cell_planes(self) -> Iterator[ImagePlanes]:
195 """Iterate over all cell image planes."""
196 raise NotImplementedError()