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
« 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/>.
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 weight: float
59 """Weight to be used for this input."""
61 psf_shape: Quadrupole
62 """Second order moments of the PSF."""
64 psf_shape_flag: bool
65 """Flag indicating whether the PSF shape measurement was successful."""
68class SingleCellCoadd(CommonComponentsProperties):
69 """A single coadd cell, built only from input images that completely
70 overlap that cell.
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.
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 """
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
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.
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)
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
158 @property
159 def outer(self) -> ImagePlanes:
160 """Image planes within the full outer region of this cell."""
161 return self._outer
163 @property
164 def psf_image(self) -> ImageF:
165 """The coadded PSF image."""
166 return self._psf
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
176 @property
177 def visit_count(self) -> int:
178 """Number of visits that contributed to this cell."""
179 return len(self.inputs)
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
188 @property
189 def common(self) -> CommonComponents:
190 # Docstring inherited.
191 return self._common
193 @property
194 def aperture_correction_map(self) -> SingleCellCoaddApCorrMap:
195 """Mapping of algorithm name to aperture correction values.
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
204 @property
205 def aperture_corrected_algorithms(self) -> Set[str]:
206 """An iterable of algorithm names that have aperture correction values.
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()
216 return set()
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.
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.
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]