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

73 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-28 09:06 +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__ = [ 

23 "CatalogExposurePsf", "CoaddPsfFitConfig", "CoaddPsfFitConnections", 

24 "CoaddPsfFitSubConfig", "CoaddPsfFitSubTask", "CoaddPsfFitTask", 

25] 

26 

27from .fit_multiband import CatalogExposure, CatalogExposureConfig 

28from lsst.geom import Point2D 

29from lsst.meas.base import SkyMapIdGeneratorConfig 

30import lsst.pex.config as pexConfig 

31import lsst.pipe.base as pipeBase 

32import lsst.pipe.base.connectionTypes as cT 

33 

34from abc import ABC, abstractmethod 

35from pydantic.dataclasses import dataclass 

36 

37 

38@dataclass(frozen=True, kw_only=True, config=CatalogExposureConfig) 

39class CatalogExposurePsf(CatalogExposure): 

40 def get_catalog(self): 

41 return self.catalog 

42 

43 def get_psf_image(self, source): 

44 """Return the PSF image for this object.""" 

45 center = Point2D(round(source.getX()), round(source.getY())) 

46 return self.exposure.getPsf().computeKernelImage(center).array 

47 

48 

49CoaddPsfFitBaseTemplates = { 

50 "name_coadd": "deep", 

51 "name_output_method": "multiprofit", 

52} 

53 

54 

55class CoaddPsfFitConnections( 

56 pipeBase.PipelineTaskConnections, 

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

58 defaultTemplates=CoaddPsfFitBaseTemplates, 

59): 

60 coadd = cT.Input( 

61 doc="Coadd image to fit a PSF model to", 

62 name="{name_coadd}Coadd_calexp", 

63 storageClass="ExposureF", 

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

65 ) 

66 coadd_cell = cT.Input( 

67 doc="Cell-coadd image to fit a PSF model to", 

68 name="{name_coadd}CoaddCell", 

69 storageClass="MultipleCellCoadd", 

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

71 ) 

72 background = cT.Input( 

73 doc="Background model to subtract from the coadd_cell", 

74 name="{name_coadd}Coadd_calexp_background", 

75 storageClass="Background", 

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

77 ) 

78 cat_meas = cT.Input( 

79 doc="Deblended single-band source catalog", 

80 name="{name_coadd}Coadd_meas", 

81 storageClass="SourceCatalog", 

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

83 ) 

84 cat_output = cT.Output( 

85 doc="Output PSF fit parameter catalog", 

86 name="{name_coadd}Coadd_psfs_{name_output_method}", 

87 storageClass="ArrowTable", 

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

89 ) 

90 

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

92 super().__init__(config=config) 

93 if config is None: 

94 return 

95 

96 if config.use_cell_coadds: 

97 del self.coadd 

98 else: 

99 del self.coadd_cell 

100 del self.background 

101 

102 

103class CoaddPsfFitSubConfig(pexConfig.Config): 

104 """Base config class for the CoaddPsfFitTask. 

105 

106 Implementing classes may add any necessary attributes. 

107 """ 

108 

109 

110class CoaddPsfFitSubTask(pipeBase.Task, ABC): 

111 """Interface for CoaddPsfFitTask subtasks to fit PSFs. 

112 

113 Parameters 

114 ---------- 

115 **kwargs 

116 Additional arguments to be passed to the `lsst.pipe.base.Task` 

117 constructor. 

118 """ 

119 ConfigClass = CoaddPsfFitSubConfig 

120 

121 def __init__(self, **kwargs): 

122 super().__init__(**kwargs) 

123 

124 @abstractmethod 

125 def run( 

126 self, catexp: CatalogExposurePsf 

127 ) -> pipeBase.Struct: 

128 """Fit PSF images at locations of sources in a single exposure. 

129 

130 Parameters 

131 ---------- 

132 catexp : `CatalogExposurePsf` 

133 An exposure to fit a model PSF at the position of all 

134 sources in the corresponding catalog. 

135 

136 Returns 

137 ------- 

138 retStruct : `lsst.pipe.base.Struct` 

139 A struct with a cat_output attribute containing the output 

140 measurement catalog. 

141 

142 Notes 

143 ----- 

144 Subclasses may have further requirements on the input parameters, 

145 including: 

146 - Passing only one catexp per band; 

147 - Catalogs containing HeavyFootprints with deblended images; 

148 - Fitting only a subset of the sources. 

149 If any requirements are not met, the subtask should fail as soon as 

150 possible. 

151 """ 

152 raise NotImplementedError() 

153 

154 

155class CoaddPsfFitConfig( 

156 pipeBase.PipelineTaskConfig, 

157 pipelineConnections=CoaddPsfFitConnections, 

158): 

159 """Configure a CoaddPsfFitTask, including a configurable fitting subtask. 

160 """ 

161 use_cell_coadds = pexConfig.Field( 

162 dtype=bool, 

163 default=False, 

164 doc="Use cell coadd images for PSF fitting", 

165 ) 

166 fit_coadd_psf = pexConfig.ConfigurableField( 

167 target=CoaddPsfFitSubTask, 

168 doc="Task to fit PSF models for a single coadd", 

169 ) 

170 idGenerator = SkyMapIdGeneratorConfig.make_field() 

171 

172 

173class CoaddPsfFitTask(pipeBase.PipelineTask): 

174 """Fit a PSF model at the location of sources in a coadd. 

175 

176 This task is intended to fit only a single PSF model at the 

177 centroid of all of the sources in a single coadd exposure. 

178 Subtasks may choose to filter which sources they fit, 

179 and may output whatever columns they desire in addition to 

180 the minimum of 'id'. 

181 """ 

182 ConfigClass = CoaddPsfFitConfig 

183 _DefaultName = "CoaddPsfFit" 

184 

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

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

187 self.makeSubtask("fit_coadd_psf") 

188 

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

190 inputs = butlerQC.get(inputRefs) 

191 id_tp = self.config.idGenerator.apply(butlerQC.quantum.dataId).catalog_id 

192 dataId = inputRefs.cat_meas.dataId 

193 

194 if self.config.use_cell_coadds: 

195 coaddDataRef = inputRefs.coadd_cell 

196 multiple_cell_coadd = inputs.pop('coadd_cell') 

197 background = inputs.pop('background') 

198 exposure = multiple_cell_coadd.stitch().asExposure() 

199 exposure.image -= background.getImage() 

200 else: 

201 coaddDataRef = inputRefs.coadd 

202 exposure = inputs.pop('coadd') 

203 

204 for dataRef in (coaddDataRef,): 

205 if dataRef.dataId != dataId: 

206 raise RuntimeError(f'{dataRef=}.dataId != {inputRefs.cat_meas.dataId=}') 

207 

208 catalog = inputs.pop('cat_meas') 

209 catexp = CatalogExposurePsf( 

210 catalog=catalog, exposure=exposure, dataId=dataId, id_tract_patch=id_tp, 

211 ) 

212 assert not inputs, "runQuantum got more inputs than expected" 

213 outputs = self.run(catexp=catexp) 

214 butlerQC.put(outputs, outputRefs) 

215 

216 def run(self, catexp: CatalogExposurePsf) -> pipeBase.Struct: 

217 """Fit a PSF model at the location of sources in a coadd. 

218 

219 Parameters 

220 ---------- 

221 catexp : `typing.List [CatalogExposurePsf]` 

222 A list of catalog-exposure pairs in a given band. 

223 

224 Returns 

225 ------- 

226 retStruct : `lsst.pipe.base.Struct` 

227 A struct with a cat_output attribute containing the output 

228 measurement catalog. 

229 

230 Notes 

231 ----- 

232 Subtasks may have further requirements; see `CoaddPsfFitSubTask.run`. 

233 """ 

234 cat_output = self.fit_coadd_psf.run(catexp).output 

235 retStruct = pipeBase.Struct(cat_output=cat_output) 

236 return retStruct