lsst.pipe.tasks g2c8cee2ce7+8fcb1119d8
Loading...
Searching...
No Matches
_rewrite_cell_coadd.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
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__ = ("RewriteCellCoaddConnections", "RewriteCellCoaddTask", "RewriteCellCoaddConfig")
25
26from typing import Any, ClassVar
27
28import astropy.io.fits
29import astropy.time
30import astropy.units
31
32import lsst.pipe.base.connectionTypes as cT
33from lsst.afw.image import Exposure as LegacyExposure
34from lsst.afw.image import Mask as LegacyMask
35from lsst.afw.math import BackgroundList
36from lsst.cell_coadds import MultipleCellCoadd as LegacyMultipleCellCoadd
37from lsst.images import Box, Mask, get_legacy_deep_coadd_mask_planes
38from lsst.images.cells import CellCoadd
39from lsst.images.fields import field_from_legacy_background
40from lsst.images.fits import ExtensionKey, FitsOpaqueMetadata
41from lsst.meas.algorithms import SubtractBackgroundTask
42from lsst.pex.config import ConfigurableField, Field
43from lsst.pipe.base import (
44 AlgorithmError,
45 PipelineTask,
46 PipelineTaskConfig,
47 PipelineTaskConnections,
48 Struct,
49)
50from lsst.skymap import BaseSkyMap
51
52
54 PipelineTaskConnections,
55 dimensions={"patch", "band"},
56 defaultTemplates={"legacy_prefix": "legacy_", "future_prefix": ""},
57):
58 legacy_cell_coadd = cT.Input(
59 "deep_coadd_cell_predetection",
60 storageClass="MultipleCellCoadd",
61 dimensions={"patch", "band"},
62 doc="The pre-detection cell coadd to convert.",
63 )
64 object_background = cT.Input(
65 "deep_coadd_background",
66 storageClass="Background",
67 dimensions={"patch", "band"},
68 doc="The background model used to generate the object catalog, to be subtracted from the coadd.",
69 minimum=0,
70 )
71 object_mask = cT.Input(
72 "{legacy_prefix}deep_coadd.mask",
73 storageClass="Mask",
74 dimensions={"patch", "band"},
75 doc="The mask to use for the coadd, including the final DETECTED mask plane.",
76 minimum=0,
77 )
78 alternate_background_coadd = cT.Input(
79 "pretty_coadd_extra_bg_subtracted",
80 storageClass="ExposureF",
81 dimensions={"patch", "band"},
82 doc="A coadd to subtract from ``legacy_cell_coadd`` and fit a background to.",
83 minimum=0,
84 )
85 sky_map = cT.Input(
86 doc="Description of the skymap's tracts and patches.",
87 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
88 storageClass="SkyMap",
89 dimensions=("skymap",),
90 )
91 future_cell_coadd = cT.Output(
92 "{future_prefix}deep_coadd",
93 storageClass="CellCoadd",
94 dimensions={"patch", "band"},
95 doc="The output CellCoadd.",
96 )
97
98 config: RewriteCellCoaddConfig
99
100 def __init__(self, config: RewriteCellCoaddConfig | None):
101 super().__init__(config=config)
102 if not self.config.do_alternate_background_fit:
103 del self.alternate_background_coadd
104
105
106class RewriteCellCoaddConfig(
107 PipelineTaskConfig,
108 pipelineConnections=RewriteCellCoaddConnections,
109):
110 object_background_description = Field(
111 "Description of the object background, to be stored with the image.",
112 dtype=str,
113 default=(
114 "Background subtracted from the image when generating the Object catalog. "
115 "This intentionally oversubtracts the background to reduce blending and ensure "
116 "scattered light is subtracted. "
117 "Restoring this background does not restore all original backgrounds, "
118 "as the coadd was built from background-subtracted visit images; in most "
119 "cases this background term is actually quite small."
120 ),
121 )
122 do_alternate_background_fit = Field(
123 "Whether to fit an alternate background to the difference between "
124 "the alternate_background_coadd and the main coadd.",
125 dtype=bool,
126 default=True,
127 )
128 alternate_background_fit = ConfigurableField(
129 target=SubtractBackgroundTask,
130 doc=(
131 "Configuration for fitting the alternate background. "
132 "Ignored if do_alternate_background_fit is False"
133 ),
134 )
135 alternate_background_name = Field(
136 "Name for the alternate background attached to the image. "
137 "ignored if do_alternate_background_fit is False.",
138 dtype=str,
139 default="pretty",
140 )
141 alternate_background_description = Field(
142 "Description for the alternate background attached to the image. "
143 "Ignored if do_alternate_background_fit is False.",
144 dtype=str,
145 default=(
146 "An alternate background optimized for visually attractive RGB images. "
147 "'Subtracting' this background will generally *add* flux, correcting "
148 "for most oversubtraction problems, but leaving in scattered light and "
149 "some instrumental backgrounds in some cases. "
150 "Because this background is fit to a difference of two wholly different "
151 "coadds that may have different input images, it can also be pulled "
152 "up or down by bright variable or transient objects."
153 ),
154 )
155
156 def setDefaults(self) -> None:
157 super().setDefaults()
158 self.alternate_background_fit.useApprox = False
159 self.alternate_background_fit.binSize = 64
160 self.alternate_background_fit.ignoredPixelMask = ["NO_DATA", "SAT"]
161
162
163class RewriteCellCoaddTask(PipelineTask):
164 ConfigClass: ClassVar[type[RewriteCellCoaddConfig]] = RewriteCellCoaddConfig
165 config: RewriteCellCoaddConfig
166 _DefaultName = "rewriteCellCoadd"
167
168 def __init__(self, *args: Any, **kwargs: Any) -> None:
169 super().__init__(*args, **kwargs)
170 self.makeSubtask("alternate_background_fit")
171
172 def run(
173 self,
174 legacy_cell_coadd: LegacyMultipleCellCoadd,
175 sky_map: BaseSkyMap,
176 *,
177 object_background: BackgroundList | None = None,
178 object_mask: LegacyMask | None = None,
179 alternate_background_coadd: LegacyExposure | None = None,
180 ) -> Struct:
181 tract_info = sky_map[legacy_cell_coadd.identifiers.tract]
182 patch_bbox = tract_info[legacy_cell_coadd.identifiers.patch].getOuterBBox()
183 future_cell_coadd = CellCoadd.from_legacy(
184 legacy_cell_coadd,
185 tract_info=tract_info,
186 bbox=Box.from_legacy(patch_bbox),
187 )
188 if getattr(future_cell_coadd, "_opaque_metadata", None) is None:
189 future_cell_coadd._opaque_metadata = FitsOpaqueMetadata()
190 primary_header = future_cell_coadd._opaque_metadata.headers.setdefault(
191 ExtensionKey(), astropy.io.fits.Header()
192 )
193 primary_header.set("INSTRUME", "LSSTCam")
194 primary_header.set("ORIGIN", "NSF-DOE Vera C. Rubin Observatory")
195 primary_header.set("TELESCOP", "Rubin:Simonyi")
196 primary_header.set("DATE", astropy.time.Time.now().fits, "UTC date this HDU was written.")
197 if object_mask is not None:
198 future_cell_coadd.mask.clear()
199 future_cell_coadd.mask.update(
200 Mask.from_legacy(object_mask, plane_map=get_legacy_deep_coadd_mask_planes())
201 )
202 else:
203 # If we can't load the object DETECTED plane, clear the one that's
204 # present, as it's (misleadingly, in this context) the union of the
205 # calibrateImage detections.
206 self.log.warning(
207 "No object mask found; clearing DETECTED and using other predetection mask planes."
208 )
209 future_cell_coadd.mask.clear("DETECTED")
210
211 if self.config.do_alternate_background_fit:
212 if alternate_background_coadd is not None:
213 alt_bg_diff = future_cell_coadd.to_legacy_exposure(copy=True)
214 alt_bg_diff.maskedImage -= alternate_background_coadd.getMaskedImage()
215 try:
216 alt_bg_result = self.alternate_background_fit.run(alt_bg_diff, stats=False)
217 except AlgorithmError:
218 self.log.exception("Could not fit alternate background to diff; rewriting without it.")
219 else:
220 alt_bg = field_from_legacy_background(alt_bg_result.background, unit=astropy.units.nJy)
221 future_cell_coadd.backgrounds.add(
222 self.config.alternate_background_name,
223 alt_bg,
224 self.config.alternate_background_description,
225 )
226 else:
227 self.log.warning("No alternate background coadd found; rewriting without it.")
228 elif alternate_background_coadd is not None:
229 raise TypeError(
230 "alternate_background_coadd provided but config.do_alternate_background_fit=False"
231 )
232 if object_background is not None:
233 # We subtract the deep_coadd_background using afw to avoid tiny
234 # differences between the afw and lsst.images spline
235 # implementations, in the definition of deep_coadd itself (though
236 # that will still ultimately differ from the original due to lossy
237 # compression).
238 future_cell_coadd.image.array -= object_background.getImage().array
239 future_cell_coadd.backgrounds.add(
240 "object",
241 field_from_legacy_background(object_background, unit=astropy.units.nJy),
242 self.config.object_background_description,
243 is_subtracted=True,
244 )
245 else:
246 self.log.warning(
247 "No object background model found; rewriting coadd without subtracting background."
248 )
249 return Struct(future_cell_coadd=future_cell_coadd)