Coverage for python/lsst/cell_coadds/_single_cell_coadd.py: 55%
83 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 08:01 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 08:01 +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__ = (
25 "CoaddInputs",
26 "SingleCellCoadd",
27)
29from collections.abc import Mapping, Set
30from dataclasses import dataclass
31from typing import TYPE_CHECKING
33from lsst.afw.geom import Quadrupole
34from lsst.afw.image import ImageD, ImageF
35from lsst.geom import Box2I
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
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
47@dataclass
48class CoaddInputs:
49 """Container for inputs to the coaddition process."""
51 overlaps_center: bool
52 """Whether a single (detector, visit) observation overlaps the center
53 of the cell."""
55 overlap_fraction: float
56 """Fraction of the cell that is covered by the overlap region."""
58 unmasked_overlap_fraction: float
59 """Fraction of the cell covered by this detector, excluding
60 rejected pixels."""
62 weight: float
63 """Weight to be used for this input."""
65 psf_shape: Quadrupole
66 """Second order moments of the PSF."""
68 psf_shape_flag: bool
69 """Flag indicating whether the PSF shape measurement was successful."""
72class SingleCellCoadd(CommonComponentsProperties):
73 """A single coadd cell, built only from input images that completely
74 overlap that cell.
76 Parameters
77 ----------
78 outer : `OwnedImagePlanes`
79 The actual coadded images.
80 psf : `ImageD`
81 The coadded PSF image.
82 inner_bbox : `Box2I`
83 The bounding box of the inner region of this cell; must be disjoint
84 with but adjacent to all other cell inner regions.
85 inputs : `Mapping` [`ObservationIdentifiers`, `CoaddInputs`]
86 Identifiers of observations that contributed to this cell.
87 common : `CommonComponents`
88 Image attributes common to all cells in a patch.
89 identifiers : `CellIdentifiers`
90 Struct of identifiers for this cell.
91 aperture_correction_map : `frozendict` [`str`, `float`], optional
92 Mapping of algorithm name to aperture correction value for this cell.
94 Notes
95 -----
96 At present we assume a single PSF image per cell is sufficient to capture
97 spatial variability, which seems adequate given the results we have so far
98 and the cell sizes we intend to use.
99 """
101 def __init__(
102 self,
103 outer: OwnedImagePlanes,
104 *,
105 psf: ImageD,
106 inner_bbox: Box2I,
107 inputs: Mapping[ObservationIdentifiers, CoaddInputs],
108 common: CommonComponents,
109 identifiers: CellIdentifiers,
110 aperture_correction_map: SingleCellCoaddApCorrMap = EMPTY_AP_CORR_MAP,
111 ):
112 assert outer.bbox.contains(
113 inner_bbox
114 ), f"Cell inner bbox {inner_bbox} is not contained by outer bbox {outer.bbox}."
115 self._outer = outer
116 self._psf = psf
117 self._inner_bbox = inner_bbox
118 self._inner = ViewImagePlanes(outer, bbox=inner_bbox, make_view=self.make_view)
119 self._common = common
120 # Remove any duplicate elements in the input, sort them and pack them
121 # as a dictionary.
122 # TODO: Remove support for inputs as None when bumping to v1.0 .
123 self._inputs: Mapping[ObservationIdentifiers, CoaddInputs]
124 if inputs:
125 self._inputs = dict.fromkeys(
126 sorted(set(inputs)),
127 CoaddInputs(
128 overlaps_center=False,
129 overlap_fraction=0.0,
130 unmasked_overlap_fraction=0.0,
131 weight=0.0,
132 psf_shape=Quadrupole(),
133 psf_shape_flag=True,
134 ),
135 )
136 self._inputs.update(inputs)
137 else:
138 self._inputs = {}
139 self._identifiers = identifiers
140 self._aperture_correction_map = aperture_correction_map
142 def _set_cell_edges(
143 self,
144 *,
145 edge_width: int = 1,
146 edge_mask_name: str = "CELL_EDGE",
147 ) -> None:
148 """Set a mask bit indicating the inner cell edges.
150 Parameters
151 ----------
152 edge_width : `int`, optional
153 The width of the edge region to flag, in pixels.
154 edge_mask_name : `str`, optional
155 The name of the mask plane to add for the edge region.
156 """
157 self._inner.mask.addMaskPlane(edge_mask_name)
158 self._inner.mask.array[:, :edge_width] |= self._inner.mask.getPlaneBitMask(edge_mask_name)
159 self._inner.mask.array[:, -edge_width:] |= self._inner.mask.getPlaneBitMask(edge_mask_name)
160 self._inner.mask.array[:edge_width, :] |= self._inner.mask.getPlaneBitMask(edge_mask_name)
161 self._inner.mask.array[-edge_width:, :] |= self._inner.mask.getPlaneBitMask(edge_mask_name)
163 @property
164 def inner(self) -> ImagePlanes:
165 """Image planes within the inner region of this cell that is disjoint
166 with all other cell inner regions.
167 """
168 return self._inner
170 @property
171 def outer(self) -> ImagePlanes:
172 """Image planes within the full outer region of this cell."""
173 return self._outer
175 @property
176 def psf_image(self) -> ImageF:
177 """The coadded PSF image."""
178 return self._psf
180 @property
181 # TODO: Remove the option of returning empty tuple in v1.0.
182 def inputs(self) -> Mapping[ObservationIdentifiers, CoaddInputs]:
183 """Identifiers for the input images that contributed to this cell,
184 sorted by their `visit` attribute first, and then by `detector`.
185 """
186 return self._inputs
188 @property
189 def visit_count(self) -> int:
190 """Number of visits that contributed to this cell."""
191 return len(self.inputs)
193 @property
194 def identifiers(self) -> CellIdentifiers:
195 """Struct of unique identifiers for this cell."""
196 # This overrides the patch-level property from
197 # CommonComponentsProperties to provide cell-level information.
198 return self._identifiers
200 @property
201 def common(self) -> CommonComponents:
202 # Docstring inherited.
203 return self._common
205 @property
206 def aperture_correction_map(self) -> SingleCellCoaddApCorrMap:
207 """Mapping of algorithm name to aperture correction values.
209 Returns
210 -------
211 aperture_correction_map : `frozendict` [`str`, float]
212 Mapping of algorithm name to aperture correction values.
213 """
214 return self._aperture_correction_map
216 @property
217 def aperture_corrected_algorithms(self) -> Set[str]:
218 """An iterable of algorithm names that have aperture correction values.
220 Returns
221 -------
222 aperture_corrected_algorithms : `tuple` [`str`, ...]
223 List of algorithms that have aperture correction values.
224 """
225 if self._aperture_correction_map:
226 return self._aperture_correction_map.keys()
228 return set()
230 def make_view(self, image: ImageLike, bbox: Box2I | None = None) -> ImageLike:
231 """Make a view of an image, optionally within a given bounding box.
233 Parameters
234 ----------
235 image : `ImageLike`
236 The image to make a view of.
237 bbox : `Box2I`, optional
238 The bounding box within which to make the view.
240 Returns
241 -------
242 image_view: `ImageLike`
243 The view of the image.
244 """
245 if bbox is None:
246 bbox = self._inner_bbox
247 return image[bbox]