Coverage for python / lsst / pipe / tasks / deblendCoaddSourcesPipeline.py: 0%

87 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 09:17 +0000

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 

22__all__ = ["DeblendCoaddSourcesMultiConfig", "DeblendCoaddSourcesMultiTask"] 

23 

24import numpy as np 

25 

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

27import lsst.pipe.base.connectionTypes as cT 

28 

29from lsst.pex.config import ConfigurableField, Field 

30from lsst.meas.base import SkyMapIdGeneratorConfig 

31from lsst.meas.extensions.scarlet import ScarletDeblendTask 

32 

33import lsst.afw.image as afwImage 

34import lsst.afw.table as afwTable 

35 

36from .coaddBase import reorderRefs 

37 

38 

39deblendBaseTemplates = {"inputCoaddName": "deep", "outputCoaddName": "deep"} 

40 

41 

42class DeblendCoaddSourcesMultiConnections(PipelineTaskConnections, 

43 dimensions=("tract", "patch", "skymap"), 

44 defaultTemplates=deblendBaseTemplates): 

45 inputSchema = cT.InitInput( 

46 doc="Input schema to use in the deblend catalog", 

47 name="{inputCoaddName}Coadd_mergeDet_schema", 

48 storageClass="SourceCatalog" 

49 ) 

50 peakSchema = cT.InitInput( 

51 doc="Schema of the footprint peak catalogs", 

52 name="{inputCoaddName}Coadd_peak_schema", 

53 storageClass="PeakCatalog" 

54 ) 

55 mergedDetections = cT.Input( 

56 doc="Detection catalog merged across bands", 

57 name="{inputCoaddName}Coadd_mergeDet", 

58 storageClass="SourceCatalog", 

59 dimensions=("tract", "patch", "skymap") 

60 ) 

61 coadds = cT.Input( 

62 doc="Exposure on which to run deblending", 

63 name="{inputCoaddName}Coadd_calexp", 

64 storageClass="ExposureF", 

65 multiple=True, 

66 dimensions=("tract", "patch", "band", "skymap") 

67 ) 

68 coadds_cell = cT.Input( 

69 doc="Exposure on which to run deblending", 

70 name="{inputCoaddName}CoaddCell", 

71 storageClass="MultipleCellCoadd", 

72 multiple=True, 

73 dimensions=("tract", "patch", "band", "skymap") 

74 ) 

75 backgrounds = cT.Input( 

76 doc="Background model to subtract from the cell-based coadd", 

77 name="{inputCoaddName}Coadd_calexp_background", 

78 storageClass="Background", 

79 multiple=True, 

80 dimensions=("tract", "patch", "band", "skymap") 

81 ) 

82 deconvolvedCoadds = cT.Input( 

83 doc="Deconvolved coadds", 

84 name="deconvolved_{inputCoaddName}_coadd", 

85 storageClass="ExposureF", 

86 multiple=True, 

87 dimensions=("tract", "patch", "band", "skymap") 

88 ) 

89 outputSchema = cT.InitOutput( 

90 doc="Output of the schema used in deblending task", 

91 name="{outputCoaddName}Coadd_deblendedFlux_schema", 

92 storageClass="SourceCatalog" 

93 ) 

94 # TODO[DM-47405]: remove this deprecated connection. 

95 fluxCatalogs = cT.Output( 

96 doc="Flux weighted catalogs produced by multiband deblending", 

97 name="{outputCoaddName}Coadd_deblendedFlux", 

98 storageClass="SourceCatalog", 

99 dimensions=("tract", "patch", "band", "skymap"), 

100 multiple=True, 

101 deprecated="Deprecated and unused; will be removed after v29." 

102 ) 

103 # TODO[DM-47405]: remove this deprecated connection. 

104 templateCatalogs = cT.Output( 

105 doc="Template catalogs produced by multiband deblending", 

106 name="{outputCoaddName}Coadd_deblendedModel", 

107 storageClass="SourceCatalog", 

108 dimensions=("tract", "patch", "band", "skymap"), 

109 multiple=True, 

110 deprecated="Deprecated and unused; will be removed after v29." 

111 ) 

112 deblendedCatalog = cT.Output( 

113 doc="Catalogs produced by multiband deblending", 

114 name="{outputCoaddName}Coadd_deblendedCatalog", 

115 storageClass="SourceCatalog", 

116 dimensions=("tract", "patch", "skymap"), 

117 ) 

118 scarletModelData = cT.Output( 

119 doc="Multiband scarlet models produced by the deblender", 

120 name="{outputCoaddName}Coadd_scarletModelData", 

121 storageClass="LsstScarletModelData", 

122 dimensions=("tract", "patch", "skymap"), 

123 ) 

124 objectParents = cT.Output( 

125 doc="Parents of the deblended objects", 

126 name="object_parent_patch", 

127 storageClass="SourceCatalog", 

128 dimensions=("tract", "patch", "skymap"), 

129 ) 

130 

131 def __init__(self, *, config=None): 

132 super().__init__(config=config) 

133 del self.fluxCatalogs 

134 del self.templateCatalogs 

135 

136 if config: 

137 if config.useCellCoadds: 

138 del self.coadds 

139 else: 

140 del self.coadds_cell 

141 del self.backgrounds 

142 

143 

144class DeblendCoaddSourcesMultiConfig(PipelineTaskConfig, 

145 pipelineConnections=DeblendCoaddSourcesMultiConnections): 

146 useCellCoadds = Field[bool]( 

147 doc="Use cell-based coadds instead of regular coadds?", 

148 default=False, 

149 ) 

150 multibandDeblend = ConfigurableField( 

151 target=ScarletDeblendTask, 

152 doc="Task to deblend an images in multiple bands" 

153 ) 

154 idGenerator = SkyMapIdGeneratorConfig.make_field() 

155 

156 

157class DeblendCoaddSourcesMultiTask(PipelineTask): 

158 ConfigClass = DeblendCoaddSourcesMultiConfig 

159 _DefaultName = "deblendCoaddSourcesMulti" 

160 

161 def __init__(self, initInputs, **kwargs): 

162 super().__init__(initInputs=initInputs, **kwargs) 

163 schema = initInputs["inputSchema"].schema 

164 self.peakSchema = initInputs["peakSchema"].schema 

165 self.schemaMapper = afwTable.SchemaMapper(schema) 

166 self.schemaMapper.addMinimalSchema(schema) 

167 self.schema = self.schemaMapper.getOutputSchema() 

168 self.makeSubtask("multibandDeblend", schema=self.schema, peakSchema=self.peakSchema) 

169 self.outputSchema = afwTable.SourceCatalog(self.schema) 

170 

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

172 # Obtain the list of bands, sort them (alphabetically), then reorder 

173 # all input lists to match this band order. 

174 # Note: sometimes deconvolution fails. If this happens then 

175 # the dataIds missing from deconvolvedRefs will be removed 

176 # during the process. 

177 deconvolvedRefs = inputRefs.deconvolvedCoadds 

178 bandOrder = [dRef.dataId["band"] for dRef in deconvolvedRefs] 

179 bandOrder.sort() 

180 inputRefs = reorderRefs(inputRefs, bandOrder, dataIdKey="band") 

181 inputs = butlerQC.get(inputRefs) 

182 bands = [dRef.dataId["band"] for dRef in deconvolvedRefs] 

183 mergedDetections = inputs.pop("mergedDetections") 

184 if self.config.useCellCoadds: 

185 exposures = [mcc.stitch().asExposure() for mcc in inputs.pop("coadds_cell")] 

186 backgrounds = inputs.pop("backgrounds") 

187 for exposure, background in zip(exposures, backgrounds): 

188 exposure.image -= background.getImage() 

189 coadds = exposures 

190 else: 

191 coadds = inputs.pop("coadds") 

192 

193 # Ensure that the coadd bands and deconvolved coadd bands match 

194 coaddRefs = inputRefs.coadds_cell if self.config.useCellCoadds else inputRefs.coadds 

195 coaddBands = [dRef.dataId["band"] for dRef in coaddRefs] 

196 if bands != coaddBands: 

197 self.log.error("Coadd bands %s != deconvolved coadd bands %s", bands, coaddBands) 

198 raise RuntimeError( 

199 "Number of coadd bands and deconvolved coadd bands do not match. " 

200 "This should never happen and indicates a bug in reorderRefs." 

201 ) 

202 

203 deconvolvedCoadds = inputs.pop("deconvolvedCoadds") 

204 

205 # Check that all inputs have been extracted correctly. 

206 assert not inputs, "runQuantum got extra inputs" 

207 

208 outputs = self.run( 

209 coadds=coadds, 

210 bands=bands, 

211 mergedDetections=mergedDetections, 

212 idFactory=self.config.idGenerator.apply(butlerQC.quantum.dataId).make_table_id_factory(), 

213 deconvolvedCoadds=deconvolvedCoadds, 

214 ) 

215 butlerQC.put(outputs, outputRefs) 

216 

217 def run(self, coadds, bands, mergedDetections, deconvolvedCoadds, idFactory): 

218 sources = self._makeSourceCatalog(mergedDetections, idFactory) 

219 multiExposure = afwImage.MultibandExposure.fromExposures(bands, coadds) 

220 mDeconvolved = afwImage.MultibandExposure.fromExposures(bands, deconvolvedCoadds) 

221 result = self.multibandDeblend.run(multiExposure, mDeconvolved, sources) 

222 return result 

223 

224 def _makeSourceCatalog(self, mergedDetections, idFactory): 

225 # There may be gaps in the mergeDet catalog, which will cause the 

226 # source ids to be inconsistent. So we update the id factory 

227 # with the largest id already in the catalog. 

228 maxId = np.max(mergedDetections["id"]) 

229 idFactory.notify(maxId) 

230 table = afwTable.SourceTable.make(self.schema, idFactory) 

231 sources = afwTable.SourceCatalog(table) 

232 sources.extend(mergedDetections, self.schemaMapper) 

233 return sources