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

58 statements  

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

21 

22__all__ = ( 

23 "DeltaSkyCorrHistTask", 

24 "DeltaSkyCorrHistConfig", 

25 "DeltaSkyCorrHistConnections", 

26 "DeltaSkyCorrAnalysisConnections", 

27 "DeltaSkyCorrAnalysisConfig", 

28 "DeltaSkyCorrAnalysisTask", 

29) 

30 

31import logging 

32 

33import numpy as np 

34 

35from lsst.pex.config import Field, ListField 

36from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections 

37from lsst.pipe.base.connectionTypes import Input, Output 

38 

39from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask 

40 

41log = logging.getLogger(__name__) 

42 

43 

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

45 """Connections class for DeltaSkyCorrHistTask.""" 

46 

47 skyCorrs = Input( 

48 name="skyCorr", 

49 storageClass="Background", 

50 doc="Sky correction background models from a run without any synthetic source injection.", 

51 multiple=True, 

52 dimensions=("instrument", "visit", "detector"), 

53 deferLoad=True, 

54 ) 

55 injected_skyCorrs = Input( 

56 name="injected_skyCorr", 

57 storageClass="Background", 

58 doc="Sky correction background models from a run with synthetic sources injected into the data.", 

59 multiple=True, 

60 dimensions=("instrument", "visit", "detector"), 

61 deferLoad=True, 

62 ) 

63 calexpBackgrounds = Input( 

64 name="calexpBackground", 

65 storageClass="Background", 

66 doc="Initial per-detector background models associated with the calibrated exposure.", 

67 multiple=True, 

68 dimensions=("instrument", "visit", "detector"), 

69 deferLoad=True, 

70 ) 

71 photoCalib = Input( 

72 name="calexp.photoCalib", 

73 storageClass="PhotoCalib", 

74 doc="Photometric calibration associated with the calibrated exposure.", 

75 multiple=True, 

76 dimensions=("instrument", "visit", "detector"), 

77 deferLoad=True, 

78 ) 

79 delta_skyCorr_hist = Output( 

80 name="delta_skyCorr_hist", 

81 storageClass="ArrowNumpyDict", 

82 doc="A dictionary containing the histogram values, bin mid points, and bin lower/upper edges for the " 

83 "aggregated skyCorr difference dataset, i.e., the difference between the injected and non-injected " 

84 "sky correction background models.", 

85 dimensions=("instrument", "visit"), 

86 ) 

87 

88 

89class DeltaSkyCorrHistConfig(PipelineTaskConfig, pipelineConnections=DeltaSkyCorrHistConnections): 

90 """Config class for DeltaSkyCorrHistTask.""" 

91 

92 bin_range = ListField[float]( 

93 doc="The lower and upper range for the histogram bins, in nJy.", 

94 default=[-1, 1], 

95 ) 

96 bin_width = Field[float]( 

97 doc="The width of each histogram bin, in nJy.", 

98 default=0.0001, 

99 ) 

100 

101 

102class DeltaSkyCorrHistTask(PipelineTask): 

103 """A task for generating a histogram of counts in the difference image 

104 between an injected sky correction frame and a non-injected sky correction 

105 frame (i.e., injected_skyCorr - skyCorr). 

106 """ 

107 

108 ConfigClass = DeltaSkyCorrHistConfig 

109 _DefaultName = "deltaSkyCorrHist" 

110 

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

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

113 

114 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

115 inputs = butlerQC.get(inputRefs) 

116 inputs["num_initial_bgs"] = len(inputs["calexpBackgrounds"][0].get()) 

117 delta_skyCorr_hist = self.run(**{k: v for k, v in inputs.items() if k != "calexpBackgrounds"}) 

118 butlerQC.put(delta_skyCorr_hist, outputRefs.delta_skyCorr_hist) 

119 

120 def run(self, skyCorrs, injected_skyCorrs, num_initial_bgs, photoCalib): 

121 """Generate a histogram of counts in the difference image between an 

122 injected sky correction frame and a non-injected sky correction frame 

123 (i.e., injected_skyCorr - skyCorr). 

124 

125 Parameters 

126 ---------- 

127 skyCorrs : `list`[`~lsst.daf.butler.DeferredDatasetHandle`] 

128 Sky correction background models from a run without any synthetic 

129 source injection. 

130 These deferred dataset handles should normally resolve to 

131 `~lsst.afw.math.BackgroundList` objects. 

132 injected_skyCorrs : `list`[`~lsst.daf.butler.DeferredDatasetHandle`] 

133 Sky correction background models from a run with synthetic sources 

134 injected into the data. 

135 These deferred dataset handles should normally resolve to 

136 `~lsst.afw.math.BackgroundList` objects. 

137 num_initial_bgs : `int` 

138 The length of the initial per-detector background model list. 

139 This number of background models will be skipped from the start of 

140 each skyCorr/injected_skyCorr background model list. 

141 See the Notes section for more details. 

142 photoCalib : `list`[`~lsst.daf.butler.DeferredDatasetHandle`] 

143 Photometric calibration, for conversion from counts to nJy. 

144 

145 Returns 

146 ------- 

147 delta_skyCorr_hist : `dict`[`str`, `~numpy.ndarray`] 

148 A dictionary containing the histogram values and bin lower/upper 

149 edges for the skyCorr difference dataset. 

150 

151 Notes 

152 ----- 

153 The first N background elements in the skyCorr/injected_skyCorr 

154 background list are the inverse of the initial per-detector background 

155 solution. 

156 The effect of this is that adding a sky correction frame to a 

157 background-subtracted calibrated exposure will undo the per-detector 

158 background solution and apply the full focal plane sky correction in 

159 its place. 

160 

161 For this task, we only want to compare the extra (subtractive) sky 

162 correction components, so we skip the first N background models from 

163 the sky frame. 

164 """ 

165 # Generate lookup tables for the skyCorr/injected_skyCorr data. 

166 lookup_skyCorrs = {x.dataId: x for x in skyCorrs} 

167 lookup_injected_skyCorrs = {x.dataId: x for x in injected_skyCorrs} 

168 lookup_photoCalib = {x.dataId: x for x in photoCalib} 

169 

170 # Set up the global histogram. 

171 bin_edges = np.arange( 

172 self.config.bin_range[0], 

173 self.config.bin_range[1] + self.config.bin_width, 

174 self.config.bin_width, 

175 ) 

176 hist = np.zeros(len(bin_edges) - 1) 

177 log.info("Generating a histogram containing %d bins.", len(hist)) 

178 

179 # Loop over the skyCorr/injected_skyCorr data. 

180 for dataId in lookup_injected_skyCorrs.keys(): 

181 # Get the skyCorr/injected_skyCorr data. 

182 skyCorr = lookup_skyCorrs[dataId].get() 

183 injected_skyCorr = lookup_injected_skyCorrs[dataId].get() 

184 # And the photometric calibration 

185 instFluxToNanojansky = lookup_photoCalib[dataId].get().instFluxToNanojansky(1) 

186 

187 # Isolate the extra (subtractive) sky correction components. 

188 skyCorr_extras = skyCorr.clone() 

189 skyCorr_extras._backgrounds = skyCorr_extras._backgrounds[num_initial_bgs:] 

190 injected_skyCorr_extras = injected_skyCorr.clone() 

191 injected_skyCorr_extras._backgrounds = injected_skyCorr_extras._backgrounds[num_initial_bgs:] 

192 

193 # Create the delta_skyCorr array. 

194 delta_skyCorr_det = injected_skyCorr_extras.getImage().array - skyCorr_extras.getImage().array 

195 delta_skyCorr_det *= instFluxToNanojansky # Convert image to nJy 

196 

197 # Compute the per-detector histogram; update the global histogram. 

198 hist_det, _ = np.histogram(delta_skyCorr_det, bins=bin_edges) 

199 hist += hist_det 

200 

201 # Return results. 

202 num_populated_bins = len([x for x in hist if x == 0]) 

203 log.info("Populated %d of %d histogram bins.", len(hist) - num_populated_bins, len(hist)) 

204 bin_mid = bin_edges[:-1] + (self.config.bin_width / 2) 

205 delta_skyCorr_hist = dict( 

206 hist=hist, bin_lower=bin_edges[:-1], bin_upper=bin_edges[1:], bin_mid=bin_mid 

207 ) 

208 return delta_skyCorr_hist 

209 

210 

211class DeltaSkyCorrAnalysisConnections( 

212 AnalysisBaseConnections, 

213 dimensions=("instrument", "visit"), 

214 defaultTemplates={"outputName": "deltaSkyCorr"}, 

215): 

216 data = Input( 

217 name="delta_skyCorr_hist", 

218 storageClass="ArrowNumpyDict", 

219 doc="A dictionary containing the histogram values, bin mid points, and bin lower/upper edges for the " 

220 "aggregated skyCorr difference dataset, i.e., the difference between the injected and non-injected " 

221 "sky correction background models.", 

222 deferLoad=True, 

223 dimensions=("instrument", "visit"), 

224 ) 

225 

226 

227class DeltaSkyCorrAnalysisConfig(AnalysisBaseConfig, pipelineConnections=DeltaSkyCorrAnalysisConnections): 

228 pass 

229 

230 

231class DeltaSkyCorrAnalysisTask(AnalysisPipelineTask): 

232 ConfigClass = DeltaSkyCorrAnalysisConfig 

233 _DefaultName = "deltaSkyCorrAnalysis"