Coverage for python / lsst / cell_coadds / _stitched_image_planes.py: 40%

68 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-07 08:27 +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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("StitchedImagePlanes",) 

25 

26from abc import abstractmethod 

27from collections.abc import Callable, Iterator, Sequence, Set 

28from functools import partial 

29from typing import TYPE_CHECKING, TypeVar 

30 

31import numpy as np 

32 

33from lsst.afw.image import ImageF, Mask 

34 

35from . import typing_helpers 

36from ._image_planes import ImagePlanes 

37from ._uniform_grid import UniformGrid 

38 

39_T = TypeVar("_T", bound=typing_helpers.ImageLike) 

40 

41 

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 

44 

45 

46class StitchedImagePlanes(ImagePlanes): 

47 """An ImagePlanes intermediate base class that stitches together per-cell 

48 images. 

49 

50 Parameters 

51 ---------- 

52 bbox : `Box2I` 

53 The region over which contiguous piecewise images are desired. 

54 

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 """ 

61 

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 

68 

69 @property 

70 @abstractmethod 

71 def grid(self) -> UniformGrid: 

72 """Object that defines the piecewise grid this object stitches 

73 together. 

74 

75 This may include cells outside the region covered by these image 

76 planes. 

77 """ 

78 raise NotImplementedError() 

79 

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() 

85 

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. 

91 

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() 

96 

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 

103 

104 def uncache_image(self) -> None: 

105 """Remove any cached `image` plane.""" 

106 self._image = None 

107 

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 

116 

117 def uncache_mask(self) -> None: 

118 """Remove any cached `mask` plane.""" 

119 self._mask = None 

120 

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 

127 

128 def uncache_variance(self) -> None: 

129 """Remove any cached `variance` plane.""" 

130 self._variance = None 

131 

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) 

137 

138 return self._mask_fractions 

139 

140 def uncache_mask_fraction(self) -> None: 

141 """Remove any cached `mask_fraction` planes.""" 

142 self._mask_fractions = None 

143 

144 @staticmethod 

145 def _noise_plane_getter(planes: ImagePlanes, i: int) -> ImageF: 

146 return planes.noise_realizations[i] 

147 

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 

160 

161 def uncache_noise_realizations(self) -> None: 

162 """Remove any cached `noise_realization` planes.""" 

163 self._noise_realizations = None 

164 

165 def _make_plane(self, result: _T, getter: Callable[[ImagePlanes], _T | None]) -> _T: 

166 """Stitch together a single image plane. 

167 

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. 

177 

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 

192 

193 @abstractmethod 

194 def _iter_cell_planes(self) -> Iterator[ImagePlanes]: 

195 """Iterate over all cell image planes.""" 

196 raise NotImplementedError()