Coverage for python / lsst / analysis / tools / tasks / deltaSkyCorrAnalysis.py: 0%
58 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 18:53 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 18:53 +0000
1# This file is part of analysis_tools.
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/>.
22__all__ = (
23 "DeltaSkyCorrHistTask",
24 "DeltaSkyCorrHistConfig",
25 "DeltaSkyCorrHistConnections",
26 "DeltaSkyCorrAnalysisConnections",
27 "DeltaSkyCorrAnalysisConfig",
28 "DeltaSkyCorrAnalysisTask",
29)
31import logging
33import numpy as np
34from lsst.pex.config import Field, ListField
35from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections
36from lsst.pipe.base.connectionTypes import Input, Output
38from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask
40log = logging.getLogger(__name__)
43class DeltaSkyCorrHistConnections(PipelineTaskConnections, dimensions=("instrument", "visit")):
44 """Connections class for DeltaSkyCorrHistTask."""
46 skyCorrs = Input(
47 name="skyCorr",
48 storageClass="Background",
49 doc="Sky correction background models from a run without any synthetic source injection.",
50 multiple=True,
51 dimensions=("instrument", "visit", "detector"),
52 deferLoad=True,
53 )
54 injected_skyCorrs = Input(
55 name="injected_skyCorr",
56 storageClass="Background",
57 doc="Sky correction background models from a run with synthetic sources injected into the data.",
58 multiple=True,
59 dimensions=("instrument", "visit", "detector"),
60 deferLoad=True,
61 )
62 calexpBackgrounds = Input(
63 name="calexpBackground",
64 storageClass="Background",
65 doc="Initial per-detector background models associated with the calibrated exposure.",
66 multiple=True,
67 dimensions=("instrument", "visit", "detector"),
68 deferLoad=True,
69 )
70 photoCalib = Input(
71 name="calexp.photoCalib",
72 storageClass="PhotoCalib",
73 doc="Photometric calibration associated with the calibrated exposure.",
74 multiple=True,
75 dimensions=("instrument", "visit", "detector"),
76 deferLoad=True,
77 )
78 delta_skyCorr_hist = Output(
79 name="delta_skyCorr_hist",
80 storageClass="ArrowNumpyDict",
81 doc="A dictionary containing the histogram values, bin mid points, and bin lower/upper edges for the "
82 "aggregated skyCorr difference dataset, i.e., the difference between the injected and non-injected "
83 "sky correction background models.",
84 dimensions=("instrument", "visit"),
85 )
88class DeltaSkyCorrHistConfig(PipelineTaskConfig, pipelineConnections=DeltaSkyCorrHistConnections):
89 """Config class for DeltaSkyCorrHistTask."""
91 bin_range = ListField[float](
92 doc="The lower and upper range for the histogram bins, in nJy.",
93 default=[-1, 1],
94 )
95 bin_width = Field[float](
96 doc="The width of each histogram bin, in nJy.",
97 default=0.0001,
98 )
101class DeltaSkyCorrHistTask(PipelineTask):
102 """A task for generating a histogram of counts in the difference image
103 between an injected sky correction frame and a non-injected sky correction
104 frame (i.e., injected_skyCorr - skyCorr).
105 """
107 ConfigClass = DeltaSkyCorrHistConfig
108 _DefaultName = "deltaSkyCorrHist"
110 def __init__(self, initInputs=None, *args, **kwargs):
111 super().__init__(*args, **kwargs)
113 def runQuantum(self, butlerQC, inputRefs, outputRefs):
114 inputs = butlerQC.get(inputRefs)
115 inputs["num_initial_bgs"] = len(inputs["calexpBackgrounds"][0].get())
116 delta_skyCorr_hist = self.run(**{k: v for k, v in inputs.items() if k != "calexpBackgrounds"})
117 butlerQC.put(delta_skyCorr_hist, outputRefs.delta_skyCorr_hist)
119 def run(self, skyCorrs, injected_skyCorrs, num_initial_bgs, photoCalib):
120 """Generate a histogram of counts in the difference image between an
121 injected sky correction frame and a non-injected sky correction frame
122 (i.e., injected_skyCorr - skyCorr).
124 Parameters
125 ----------
126 skyCorrs : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
127 Sky correction background models from a run without any synthetic
128 source injection.
129 These deferred dataset handles should normally resolve to
130 `~lsst.afw.math.BackgroundList` objects.
131 injected_skyCorrs : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
132 Sky correction background models from a run with synthetic sources
133 injected into the data.
134 These deferred dataset handles should normally resolve to
135 `~lsst.afw.math.BackgroundList` objects.
136 num_initial_bgs : `int`
137 The length of the initial per-detector background model list.
138 This number of background models will be skipped from the start of
139 each skyCorr/injected_skyCorr background model list.
140 See the Notes section for more details.
141 photoCalib : `list`[`~lsst.daf.butler.DeferredDatasetHandle`]
142 Photometric calibration, for conversion from counts to nJy.
144 Returns
145 -------
146 delta_skyCorr_hist : `dict`[`str`, `~numpy.ndarray`]
147 A dictionary containing the histogram values and bin lower/upper
148 edges for the skyCorr difference dataset.
150 Notes
151 -----
152 The first N background elements in the skyCorr/injected_skyCorr
153 background list are the inverse of the initial per-detector background
154 solution.
155 The effect of this is that adding a sky correction frame to a
156 background-subtracted calibrated exposure will undo the per-detector
157 background solution and apply the full focal plane sky correction in
158 its place.
160 For this task, we only want to compare the extra (subtractive) sky
161 correction components, so we skip the first N background models from
162 the sky frame.
163 """
164 # Generate lookup tables for the skyCorr/injected_skyCorr data.
165 lookup_skyCorrs = {x.dataId: x for x in skyCorrs}
166 lookup_injected_skyCorrs = {x.dataId: x for x in injected_skyCorrs}
167 lookup_photoCalib = {x.dataId: x for x in photoCalib}
169 # Set up the global histogram.
170 bin_edges = np.arange(
171 self.config.bin_range[0],
172 self.config.bin_range[1] + self.config.bin_width,
173 self.config.bin_width,
174 )
175 hist = np.zeros(len(bin_edges) - 1)
176 log.info("Generating a histogram containing %d bins.", len(hist))
178 # Loop over the skyCorr/injected_skyCorr data.
179 for dataId in lookup_injected_skyCorrs.keys():
180 # Get the skyCorr/injected_skyCorr data.
181 skyCorr = lookup_skyCorrs[dataId].get()
182 injected_skyCorr = lookup_injected_skyCorrs[dataId].get()
183 # And the photometric calibration
184 instFluxToNanojansky = lookup_photoCalib[dataId].get().instFluxToNanojansky(1)
186 # Isolate the extra (subtractive) sky correction components.
187 skyCorr_extras = skyCorr.clone()
188 skyCorr_extras._backgrounds = skyCorr_extras._backgrounds[num_initial_bgs:]
189 injected_skyCorr_extras = injected_skyCorr.clone()
190 injected_skyCorr_extras._backgrounds = injected_skyCorr_extras._backgrounds[num_initial_bgs:]
192 # Create the delta_skyCorr array.
193 delta_skyCorr_det = injected_skyCorr_extras.getImage().array - skyCorr_extras.getImage().array
194 delta_skyCorr_det *= instFluxToNanojansky # Convert image to nJy
196 # Compute the per-detector histogram; update the global histogram.
197 hist_det, _ = np.histogram(delta_skyCorr_det, bins=bin_edges)
198 hist += hist_det
200 # Return results.
201 num_populated_bins = len([x for x in hist if x == 0])
202 log.info("Populated %d of %d histogram bins.", len(hist) - num_populated_bins, len(hist))
203 bin_mid = bin_edges[:-1] + (self.config.bin_width / 2)
204 delta_skyCorr_hist = dict(
205 hist=hist, bin_lower=bin_edges[:-1], bin_upper=bin_edges[1:], bin_mid=bin_mid
206 )
207 return delta_skyCorr_hist
210class DeltaSkyCorrAnalysisConnections(
211 AnalysisBaseConnections,
212 dimensions=("instrument", "visit"),
213 defaultTemplates={"outputName": "deltaSkyCorr"},
214):
215 data = Input(
216 name="delta_skyCorr_hist",
217 storageClass="ArrowNumpyDict",
218 doc="A dictionary containing the histogram values, bin mid points, and bin lower/upper edges for the "
219 "aggregated skyCorr difference dataset, i.e., the difference between the injected and non-injected "
220 "sky correction background models.",
221 deferLoad=True,
222 dimensions=("instrument", "visit"),
223 )
226class DeltaSkyCorrAnalysisConfig(AnalysisBaseConfig, pipelineConnections=DeltaSkyCorrAnalysisConnections):
227 pass
230class DeltaSkyCorrAnalysisTask(AnalysisPipelineTask):
231 ConfigClass = DeltaSkyCorrAnalysisConfig
232 _DefaultName = "deltaSkyCorrAnalysis"