Coverage for python / lsst / analysis / tools / tasks / deltaSkyCorrAnalysis.py: 0%

58 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-15 00:23 +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/>. 

21 

22__all__ = ( 

23 "DeltaSkyCorrHistTask", 

24 "DeltaSkyCorrHistConfig", 

25 "DeltaSkyCorrHistConnections", 

26 "DeltaSkyCorrAnalysisConnections", 

27 "DeltaSkyCorrAnalysisConfig", 

28 "DeltaSkyCorrAnalysisTask", 

29) 

30 

31import logging 

32 

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 

37 

38from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask 

39 

40log = logging.getLogger(__name__) 

41 

42 

43class DeltaSkyCorrHistConnections(PipelineTaskConnections, dimensions=("instrument", "visit")): 

44 """Connections class for DeltaSkyCorrHistTask.""" 

45 

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 ) 

86 

87 

88class DeltaSkyCorrHistConfig(PipelineTaskConfig, pipelineConnections=DeltaSkyCorrHistConnections): 

89 """Config class for DeltaSkyCorrHistTask.""" 

90 

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 ) 

99 

100 

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 """ 

106 

107 ConfigClass = DeltaSkyCorrHistConfig 

108 _DefaultName = "deltaSkyCorrHist" 

109 

110 def __init__(self, initInputs=None, *args, **kwargs): 

111 super().__init__(*args, **kwargs) 

112 

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) 

118 

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). 

123 

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. 

143 

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. 

149 

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. 

159 

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} 

168 

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)) 

177 

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) 

185 

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:] 

191 

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 

195 

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 

199 

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 

208 

209 

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 ) 

224 

225 

226class DeltaSkyCorrAnalysisConfig(AnalysisBaseConfig, pipelineConnections=DeltaSkyCorrAnalysisConnections): 

227 pass 

228 

229 

230class DeltaSkyCorrAnalysisTask(AnalysisPipelineTask): 

231 ConfigClass = DeltaSkyCorrAnalysisConfig 

232 _DefaultName = "deltaSkyCorrAnalysis"