Coverage for python / lsst / cell_coadds / _single_cell_coadd.py: 54%

82 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 09:18 +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__ = ( 

25 "CoaddInputs", 

26 "SingleCellCoadd", 

27) 

28 

29from collections.abc import Mapping, Set 

30from dataclasses import dataclass 

31from typing import TYPE_CHECKING 

32 

33from lsst.afw.geom import Quadrupole 

34from lsst.afw.image import ImageD, ImageF 

35from lsst.geom import Box2I 

36 

37from ._coadd_ap_corr_map import EMPTY_AP_CORR_MAP 

38from ._common_components import CommonComponents, CommonComponentsProperties 

39from ._image_planes import ViewImagePlanes 

40from .typing_helpers import ImageLike, SingleCellCoaddApCorrMap 

41 

42if TYPE_CHECKING: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true

43 from ._identifiers import CellIdentifiers, ObservationIdentifiers 

44 from ._image_planes import ImagePlanes, OwnedImagePlanes 

45 

46 

47@dataclass 

48class CoaddInputs: 

49 """Container for inputs to the coaddition process.""" 

50 

51 overlaps_center: bool 

52 """Whether a single (detector, visit) observation overlaps the center """ 

53 """of the cell.""" 

54 

55 overlap_fraction: float 

56 """Fraction of the cell that is covered by the overlap region.""" 

57 

58 weight: float 

59 """Weight to be used for this input.""" 

60 

61 psf_shape: Quadrupole 

62 """Second order moments of the PSF.""" 

63 

64 psf_shape_flag: bool 

65 """Flag indicating whether the PSF shape measurement was successful.""" 

66 

67 

68class SingleCellCoadd(CommonComponentsProperties): 

69 """A single coadd cell, built only from input images that completely 

70 overlap that cell. 

71 

72 Parameters 

73 ---------- 

74 outer : `OwnedImagePlanes` 

75 The actual coadded images. 

76 psf : `ImageD` 

77 The coadded PSF image. 

78 inner_bbox : `Box2I` 

79 The bounding box of the inner region of this cell; must be disjoint 

80 with but adjacent to all other cell inner regions. 

81 inputs : `Mapping` [`ObservationIdentifiers`, `CoaddInputs`] 

82 Identifiers of observations that contributed to this cell. 

83 common : `CommonComponents` 

84 Image attributes common to all cells in a patch. 

85 identifiers : `CellIdentifiers` 

86 Struct of identifiers for this cell. 

87 aperture_correction_map : `frozendict` [`str`, `float`], optional 

88 Mapping of algorithm name to aperture correction value for this cell. 

89 

90 Notes 

91 ----- 

92 At present we assume a single PSF image per cell is sufficient to capture 

93 spatial variability, which seems adequate given the results we have so far 

94 and the cell sizes we intend to use. 

95 """ 

96 

97 def __init__( 

98 self, 

99 outer: OwnedImagePlanes, 

100 *, 

101 psf: ImageD, 

102 inner_bbox: Box2I, 

103 inputs: Mapping[ObservationIdentifiers, CoaddInputs], 

104 common: CommonComponents, 

105 identifiers: CellIdentifiers, 

106 aperture_correction_map: SingleCellCoaddApCorrMap = EMPTY_AP_CORR_MAP, 

107 ): 

108 assert outer.bbox.contains( 

109 inner_bbox 

110 ), f"Cell inner bbox {inner_bbox} is not contained by outer bbox {outer.bbox}." 

111 self._outer = outer 

112 self._psf = psf 

113 self._inner_bbox = inner_bbox 

114 self._inner = ViewImagePlanes(outer, bbox=inner_bbox, make_view=self.make_view) 

115 self._common = common 

116 # Remove any duplicate elements in the input, sort them and pack them 

117 # as a dictionary. 

118 # TODO: Remove support for inputs as None when bumping to v1.0 . 

119 self._inputs: Mapping[ObservationIdentifiers, CoaddInputs] 

120 if inputs: 

121 self._inputs = dict.fromkeys( 

122 sorted(set(inputs)), CoaddInputs(False, 0.0, 0.0, Quadrupole(), True) 

123 ) 

124 self._inputs.update(inputs) 

125 else: 

126 self._inputs = {} 

127 self._identifiers = identifiers 

128 self._aperture_correction_map = aperture_correction_map 

129 

130 def _set_cell_edges( 

131 self, 

132 *, 

133 edge_width: int = 1, 

134 edge_mask_name: str = "CELL_EDGE", 

135 ) -> None: 

136 """Set a mask bit indicating the inner cell edges. 

137 

138 Parameters 

139 ---------- 

140 edge_width : `int`, optional 

141 The width of the edge region to flag, in pixels. 

142 edge_mask_name : `str`, optional 

143 The name of the mask plane to add for the edge region. 

144 """ 

145 self._inner.mask.addMaskPlane(edge_mask_name) 

146 self._inner.mask.array[:, :edge_width] |= self._inner.mask.getPlaneBitMask(edge_mask_name) 

147 self._inner.mask.array[:, -edge_width:] |= self._inner.mask.getPlaneBitMask(edge_mask_name) 

148 self._inner.mask.array[:edge_width, :] |= self._inner.mask.getPlaneBitMask(edge_mask_name) 

149 self._inner.mask.array[-edge_width:, :] |= self._inner.mask.getPlaneBitMask(edge_mask_name) 

150 

151 @property 

152 def inner(self) -> ImagePlanes: 

153 """Image planes within the inner region of this cell that is disjoint 

154 with all other cell inner regions. 

155 """ 

156 return self._inner 

157 

158 @property 

159 def outer(self) -> ImagePlanes: 

160 """Image planes within the full outer region of this cell.""" 

161 return self._outer 

162 

163 @property 

164 def psf_image(self) -> ImageF: 

165 """The coadded PSF image.""" 

166 return self._psf 

167 

168 @property 

169 # TODO: Remove the option of returning empty tuple in v1.0. 

170 def inputs(self) -> Mapping[ObservationIdentifiers, CoaddInputs]: 

171 """Identifiers for the input images that contributed to this cell, 

172 sorted by their `visit` attribute first, and then by `detector`. 

173 """ 

174 return self._inputs 

175 

176 @property 

177 def visit_count(self) -> int: 

178 """Number of visits that contributed to this cell.""" 

179 return len(self.inputs) 

180 

181 @property 

182 def identifiers(self) -> CellIdentifiers: 

183 """Struct of unique identifiers for this cell.""" 

184 # This overrides the patch-level property from 

185 # CommonComponentsProperties to provide cell-level information. 

186 return self._identifiers 

187 

188 @property 

189 def common(self) -> CommonComponents: 

190 # Docstring inherited. 

191 return self._common 

192 

193 @property 

194 def aperture_correction_map(self) -> SingleCellCoaddApCorrMap: 

195 """Mapping of algorithm name to aperture correction values. 

196 

197 Returns 

198 ------- 

199 aperture_correction_map : `frozendict` [`str`, float] 

200 Mapping of algorithm name to aperture correction values. 

201 """ 

202 return self._aperture_correction_map 

203 

204 @property 

205 def aperture_corrected_algorithms(self) -> Set[str]: 

206 """An iterable of algorithm names that have aperture correction values. 

207 

208 Returns 

209 ------- 

210 aperture_corrected_algorithms : `tuple` [`str`, ...] 

211 List of algorithms that have aperture correction values. 

212 """ 

213 if self._aperture_correction_map: 

214 return self._aperture_correction_map.keys() 

215 

216 return set() 

217 

218 def make_view(self, image: ImageLike, bbox: Box2I | None = None) -> ImageLike: 

219 """Make a view of an image, optionally within a given bounding box. 

220 

221 Parameters 

222 ---------- 

223 image : `ImageLike` 

224 The image to make a view of. 

225 bbox : `Box2I`, optional 

226 The bounding box within which to make the view. 

227 

228 Returns 

229 ------- 

230 image_view: `ImageLike` 

231 The view of the image. 

232 """ 

233 if bbox is None: 

234 bbox = self._inner_bbox 

235 return image[bbox]