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

70 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-10 13:35 +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 

31from lsst.afw.image import ImageF, Mask 

32 

33from . import typing_helpers 

34from ._image_planes import ImagePlanes 

35from ._uniform_grid import UniformGrid 

36 

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

38 

39 

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 

42 

43 

44class StitchedImagePlanes(ImagePlanes): 

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

46 images. 

47 

48 Parameters 

49 ---------- 

50 bbox : `Box2I` 

51 The region over which contiguous piecewise images are desired. 

52 

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

59 

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 

66 

67 @property 

68 @abstractmethod 

69 def grid(self) -> UniformGrid: 

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

71 together. 

72 

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

74 planes. 

75 """ 

76 raise NotImplementedError() 

77 

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

83 

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. 

89 

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

94 

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 

101 

102 def uncache_image(self) -> None: 

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

104 self._image = None 

105 

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 

112 

113 def uncache_mask(self) -> None: 

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

115 self._mask = None 

116 

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 

123 

124 def uncache_variance(self) -> None: 

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

126 self._variance = None 

127 

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) 

133 

134 return self._mask_fractions 

135 

136 def uncache_mask_fraction(self) -> None: 

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

138 self._mask_fractions = None 

139 

140 @staticmethod 

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

142 return planes.noise_realizations[i] 

143 

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 

156 

157 def uncache_noise_realizations(self) -> None: 

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

159 self._noise_realizations = None 

160 

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

162 """Stitch together a single image plane. 

163 

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. 

173 

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 

188 

189 @abstractmethod 

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

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

192 raise NotImplementedError()