22from __future__
import annotations
24__all__ = (
"RewriteDifferenceImageConnections",
"RewriteDifferenceImageTask",
"RewriteDifferenceImageConfig")
27from collections.abc
import Mapping
28from typing
import ClassVar
32import lsst.pipe.base.connectionTypes
as cT
36from lsst.daf.butler
import DataCoordinate
37from lsst.images
import DifferenceImage, DifferenceImageTemplateInfo
38from lsst.images.convolution_kernels
import ImageBasisConvolutionKernel
39from lsst.images.fields
import field_from_legacy_background, field_from_legacy_photo_calib
42from lsst.pipe.base
import (
43 InputQuantizedConnection,
44 OutputQuantizedConnection,
47 PipelineTaskConnections,
54 PipelineTaskConnections,
55 dimensions={
"visit",
"detector"},
56 defaultTemplates={
"legacy_prefix":
"legacy_",
"future_prefix":
""},
58 legacy_exposure = cT.Input(
59 "{legacy_prefix}difference_image",
64 storageClass=
"DifferenceImage",
65 dimensions={
"visit",
"detector"},
67 doc=
"The input image to convert.",
69 template_psf = cT.Input(
70 "template_detector.psf",
72 dimensions={
"visit",
"detector"},
73 doc=
"The PSF of the detector-level template, which holds some provenance information.",
75 template_metadata = cT.Input(
76 "template_detector.metadata",
77 storageClass=
"PropertyList",
78 dimensions={
"visit",
"detector"},
79 doc=
"The metadata of the detector-level template, which holds some provenance information.",
81 template_coadds = cT.Input(
83 storageClass=
"ExposureF",
84 dimensions={
"tract",
"patch",
"band"},
88 "The template coadds that may have gone into the detector-level template. "
89 "This is a dummy input that is not actually loaded; it is needed only to connect "
90 "data IDs to butler dataset IDs."
93 visit_summary = cT.Input(
95 storageClass=
"ExposureCatalog",
97 doc=
"A visit summary catalog with the PhotoCalib that was already applied to the image's pixels.",
99 difference_kernel = cT.Input(
101 storageClass=
"MatchingKernel",
102 dimensions={
"visit",
"detector"},
103 doc=
"The kernel used to match the template to the science image.",
105 background = cT.Input(
106 "difference_image_background",
107 storageClass=
"Background",
108 dimensions={
"visit",
"detector"},
109 doc=
"The background model that was subtracted from this image.",
111 future_difference_image = cT.Output(
112 "{future_prefix}difference_image",
113 storageClass=
"DifferenceImage",
114 dimensions={
"visit",
"detector"},
115 doc=
"The output difference image.",
118 config: RewriteDifferenceImageConfig
120 def __init__(self, config: RewriteDifferenceImageConfig):
121 super().__init__(config=config)
122 if self.config.background_description
is None:
126class RewriteDifferenceImageConfig(
128 pipelineConnections=RewriteDifferenceImageConnections,
130 instrumental_unit = Field(
131 "Unit for instrumental flux pixels (i.e. uncalibrated side of a PhotoCalib).",
135 background_description = Field(
136 "Description of the subtracted background, to be stored with the image. "
137 "If None, no background will be attached.",
144class RewriteDifferenceImageTask(PipelineTask):
145 ConfigClass: ClassVar[type[RewriteDifferenceImageConfig]] = RewriteDifferenceImageConfig
146 config: RewriteDifferenceImageConfig
147 _DefaultName =
"rewriteDifferenceImage"
151 butlerQC: QuantumContext,
152 inputRefs: InputQuantizedConnection,
153 outputRefs: OutputQuantizedConnection,
155 inputs = butlerQC.get(inputRefs)
156 difference_image = inputs.pop(
"legacy_exposure").get(parameters={
"preserve_quantization":
True})
157 visit_summary = inputs.pop(
"visit_summary")
158 coadd_data_ids_by_uuid = {h.ref.id: h.ref.dataId
for h
in inputs.pop(
"template_coadds")}
159 photo_calib: PhotoCalib = visit_summary.find(butlerQC.quantum.dataId[
"detector"]).getPhotoCalib()
161 difference_image, photo_calib=photo_calib, **inputs, coadd_data_ids_by_uuid=coadd_data_ids_by_uuid
163 butlerQC.put(outputs, outputRefs)
167 difference_image: DifferenceImage,
169 photo_calib: PhotoCalib,
170 background: BackgroundList |
None =
None,
171 difference_kernel: Kernel,
172 template_psf: CoaddPsf,
173 template_metadata: PropertyList,
174 coadd_data_ids_by_uuid: Mapping[uuid.UUID, DataCoordinate],
176 instrumental_unit = astropy.units.Unit(self.config.instrumental_unit)
177 difference_image.photometric_scaling = field_from_legacy_photo_calib(
178 photo_calib, bounds=difference_image.bbox, instrumental_unit=instrumental_unit
180 if background
is not None and self.config.background_description:
181 difference_image.backgrounds.add(
183 field_from_legacy_background(
184 background, bounds=difference_image.bbox, unit=difference_image.unit
186 self.config.background_description,
189 difference_image.kernel = ImageBasisConvolutionKernel.from_legacy(difference_kernel)
190 difference_image.templates = DifferenceImageTemplateInfo.from_legacy(
191 difference_image.sky_projection.pixel_frame,
194 coadd_data_ids_by_uuid,
196 return Struct(future_difference_image=difference_image)