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