Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of meas_base. 

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"""Base command-line driver task for forced measurement. 

23 

24Must be inherited to specialize for a specific dataset to be used (see 

25`ForcedPhotCcdTask`, `ForcedPhotCoaddTask`). 

26""" 

27 

28import lsst.afw.table 

29import lsst.pex.config 

30import lsst.daf.base 

31import lsst.pipe.base 

32import lsst.pex.config 

33 

34from lsst.pipe.base import PipelineTaskConnections, PipelineTaskConfig 

35import lsst.pipe.base.connectionTypes as cT 

36 

37from .references import MultiBandReferencesTask 

38from .forcedMeasurement import ForcedMeasurementTask 

39from .applyApCorr import ApplyApCorrTask 

40from .catalogCalculation import CatalogCalculationTask 

41 

42__all__ = ("ForcedPhotImageConfig", "ForcedPhotImageTask", "ForcedPhotImageConnections") 

43 

44 

45class ForcedPhotImageConnections(PipelineTaskConnections, 

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

47 defaultTemplates={"inputCoaddName": "deep", 

48 "outputCoaddName": "deep"}): 

49 inputSchema = cT.InitInput( 

50 doc="Schema for the input measurement catalogs.", 

51 name="{inputCoaddName}Coadd_ref_schema", 

52 storageClass="SourceCatalog", 

53 ) 

54 outputSchema = cT.InitOutput( 

55 doc="Schema for the output forced measurement catalogs.", 

56 name="{outputCoaddName}Coadd_forced_src_schema", 

57 storageClass="SourceCatalog", 

58 ) 

59 exposure = cT.Input( 

60 doc="Input exposure to perform photometry on.", 

61 name="{inputCoaddName}Coadd", 

62 storageClass="ExposureF", 

63 dimensions=["band", "skymap", "tract", "patch"], 

64 ) 

65 refCat = cT.Input( 

66 doc="Catalog of shapes and positions at which to force photometry.", 

67 name="{inputCoaddName}Coadd_ref", 

68 storageClass="SourceCatalog", 

69 dimensions=["skymap", "tract", "patch"], 

70 ) 

71 refWcs = cT.Input( 

72 doc="Reference world coordinate system.", 

73 name="{inputCoaddName}Coadd.wcs", 

74 storageClass="Wcs", 

75 dimensions=["band", "skymap", "tract", "patch"], 

76 ) 

77 measCat = cT.Output( 

78 doc="Output forced photometry catalog.", 

79 name="{outputCoaddName}Coadd_forced_src", 

80 storageClass="SourceCatalog", 

81 dimensions=["band", "skymap", "tract", "patch"], 

82 ) 

83 

84 

85class ForcedPhotImageConfig(PipelineTaskConfig, pipelineConnections=ForcedPhotImageConnections): 

86 """Config class for forced measurement driver task.""" 

87 # ForcedPhotImage options 

88 references = lsst.pex.config.ConfigurableField( 

89 target=MultiBandReferencesTask, 

90 doc="subtask to retrieve reference source catalog" 

91 ) 

92 measurement = lsst.pex.config.ConfigurableField( 

93 target=ForcedMeasurementTask, 

94 doc="subtask to do forced measurement" 

95 ) 

96 coaddName = lsst.pex.config.Field( 

97 doc="coadd name: typically one of deep or goodSeeing", 

98 dtype=str, 

99 default="deep", 

100 ) 

101 doApCorr = lsst.pex.config.Field( 

102 dtype=bool, 

103 default=True, 

104 doc="Run subtask to apply aperture corrections" 

105 ) 

106 applyApCorr = lsst.pex.config.ConfigurableField( 

107 target=ApplyApCorrTask, 

108 doc="Subtask to apply aperture corrections" 

109 ) 

110 catalogCalculation = lsst.pex.config.ConfigurableField( 

111 target=CatalogCalculationTask, 

112 doc="Subtask to run catalogCalculation plugins on catalog" 

113 ) 

114 

115 def setDefaults(self): 

116 # Docstring inherited. 

117 # Make catalogCalculation a no-op by default as no modelFlux is setup by default in 

118 # ForcedMeasurementTask 

119 super().setDefaults() 

120 

121 self.catalogCalculation.plugins.names = [] 

122 

123 

124class ForcedPhotImageTask(lsst.pipe.base.PipelineTask, lsst.pipe.base.CmdLineTask): 

125 """A base class for command-line forced measurement drivers. 

126 

127 Parameters 

128 ---------- 

129 butler : `lsst.daf.persistence.butler.Butler`, optional 

130 A Butler which will be passed to the references subtask to allow it to 

131 load its schema from disk. Optional, but must be specified if 

132 ``refSchema`` is not; if both are specified, ``refSchema`` takes 

133 precedence. 

134 refSchema : `lsst.afw.table.Schema`, optional 

135 The schema of the reference catalog, passed to the constructor of the 

136 references subtask. Optional, but must be specified if ``butler`` is 

137 not; if both are specified, ``refSchema`` takes precedence. 

138 **kwds 

139 Keyword arguments are passed to the supertask constructor. 

140 

141 Notes 

142 ----- 

143 This is a an abstract class, which is the common ancestor for 

144 `ForcedPhotCcdTask` and `ForcedPhotCoaddTask`. It provides the 

145 `runDataRef` method that does most of the work, while delegating a few 

146 customization tasks to other methods that are overridden by subclasses. 

147 

148 This task is not directly usable as a command line task. Subclasses must: 

149 

150 - Set the `_DefaultName` class attribute; 

151 - Implement `makeIdFactory`; 

152 - Implement `fetchReferences`; 

153 - Optionally, implement `attachFootprints`. 

154 """ 

155 

156 ConfigClass = ForcedPhotImageConfig 

157 _DefaultName = "processImageForcedTask" 

158 

159 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds): 

160 super().__init__(**kwds) 

161 

162 if initInputs is not None: 

163 refSchema = initInputs['inputSchema'].schema 

164 

165 self.makeSubtask("references", butler=butler, schema=refSchema) 

166 if refSchema is None: 

167 refSchema = self.references.schema 

168 self.makeSubtask("measurement", refSchema=refSchema) 

169 # It is necessary to get the schema internal to the forced measurement task until such a time 

170 # that the schema is not owned by the measurement task, but is passed in by an external caller 

171 if self.config.doApCorr: 

172 self.makeSubtask("applyApCorr", schema=self.measurement.schema) 

173 self.makeSubtask('catalogCalculation', schema=self.measurement.schema) 

174 self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema) 

175 

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

177 inputs = butlerQC.get(inputRefs) 

178 inputs['measCat'] = self.generateMeasCat(inputRefs.exposure.dataId, 

179 inputs['exposure'], 

180 inputs['refCat'], inputs['refWcs'], 

181 "tract_patch") 

182 outputs = self.run(**inputs) 

183 butlerQC.put(outputs, outputRefs) 

184 

185 def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName): 

186 """Generate a measurement catalog for Gen3. 

187 

188 Parameters 

189 ---------- 

190 exposureDataId : `DataId` 

191 Butler dataId for this exposure. 

192 exposure : `lsst.afw.image.exposure.Exposure` 

193 Exposure to generate the catalog for. 

194 refCat : `lsst.afw.table.SourceCatalog` 

195 Catalog of shapes and positions at which to force photometry. 

196 refWcs : `lsst.afw.image.SkyWcs` 

197 Reference world coordinate system. 

198 idPackerName : `str` 

199 Name of DimensionPacker to use to generate the packed version 

200 of the data ID to mangle into source IDs. 

201 

202 Returns 

203 ------- 

204 measCat : `lsst.afw.table.SourceCatalog` 

205 Catalog of forced sources to measure. 

206 """ 

207 expId, expBits = exposureDataId.pack(idPackerName, returnMaxBits=True) 

208 idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits) 

209 

210 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs, 

211 idFactory=idFactory) 

212 return measCat 

213 

214 def runDataRef(self, dataRef, psfCache=None): 

215 """Perform forced measurement on a single exposure. 

216 

217 Parameters 

218 ---------- 

219 dataRef : `lsst.daf.persistence.ButlerDataRef` 

220 Passed to the ``references`` subtask to obtain the reference WCS, 

221 the ``getExposure`` method (implemented by derived classes) to 

222 read the measurment image, and the ``fetchReferences`` method to 

223 get the exposure and load the reference catalog (see 

224 :lsst-task`lsst.meas.base.references.CoaddSrcReferencesTask`). 

225 Refer to derived class documentation for details of the datasets 

226 and data ID keys which are used. 

227 psfCache : `int`, optional 

228 Size of PSF cache, or `None`. The size of the PSF cache can have 

229 a significant effect upon the runtime for complicated PSF models. 

230 

231 Notes 

232 ----- 

233 Sources are generated with ``generateMeasCat`` in the ``measurement`` 

234 subtask. These are passed to ``measurement``'s ``run`` method, which 

235 fills the source catalog with the forced measurement results. The 

236 sources are then passed to the ``writeOutputs`` method (implemented by 

237 derived classes) which writes the outputs. 

238 """ 

239 refWcs = self.references.getWcs(dataRef) 

240 exposure = self.getExposure(dataRef) 

241 if psfCache is not None: 

242 exposure.getPsf().setCacheSize(psfCache) 

243 refCat = self.fetchReferences(dataRef, exposure) 

244 

245 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs, 

246 idFactory=self.makeIdFactory(dataRef)) 

247 self.log.info("Performing forced measurement on %s" % (dataRef.dataId,)) 

248 self.attachFootprints(measCat, refCat, exposure, refWcs, dataRef) 

249 

250 exposureId = self.getExposureId(dataRef) 

251 

252 forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId) 

253 

254 self.writeOutput(dataRef, forcedPhotResult.measCat) 

255 

256 def run(self, measCat, exposure, refCat, refWcs, exposureId=None): 

257 """Perform forced measurement on a single exposure. 

258 

259 Parameters 

260 ---------- 

261 measCat : `lsst.afw.table.SourceCatalog` 

262 The measurement catalog, based on the sources listed in the 

263 reference catalog. 

264 exposure : `lsst.afw.image.Exposure` 

265 The measurement image upon which to perform forced detection. 

266 refCat : `lsst.afw.table.SourceCatalog` 

267 The reference catalog of sources to measure. 

268 refWcs : `lsst.afw.image.SkyWcs` 

269 The WCS for the references. 

270 exposureId : `int` 

271 Optional unique exposureId used for random seed in measurement 

272 task. 

273 

274 Returns 

275 ------- 

276 result : `lsst.pipe.base.Struct` 

277 Structure with fields: 

278 

279 ``measCat`` 

280 Catalog of forced measurement results 

281 (`lsst.afw.table.SourceCatalog`). 

282 """ 

283 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId) 

284 if self.config.doApCorr: 

285 self.applyApCorr.run( 

286 catalog=measCat, 

287 apCorrMap=exposure.getInfo().getApCorrMap() 

288 ) 

289 self.catalogCalculation.run(measCat) 

290 

291 return lsst.pipe.base.Struct(measCat=measCat) 

292 

293 def makeIdFactory(self, dataRef): 

294 """Hook for derived classes to make an ID factory for forced sources. 

295 

296 Notes 

297 ----- 

298 That this applies to forced *source* IDs, not object IDs, which are 

299 usually handled by the ``measurement.copyColumns`` config option. 

300 

301 """ 

302 raise NotImplementedError() 

303 

304 def getExposureId(self, dataRef): 

305 raise NotImplementedError() 

306 

307 def fetchReferences(self, dataRef, exposure): 

308 """Hook for derived classes to define how to get reference objects. 

309 

310 Notes 

311 ----- 

312 Derived classes should call one of the ``fetch*`` methods on the 

313 ``references`` subtask, but which one they call depends on whether the 

314 region to get references for is a easy to describe in patches (as it 

315 would be when doing forced measurements on a coadd), or is just an 

316 arbitrary box (as it would be for CCD forced measurements). 

317 """ 

318 raise NotImplementedError() 

319 

320 def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef): 

321 r"""Attach footprints to blank sources prior to measurements. 

322 

323 Notes 

324 ----- 

325 `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the 

326 pixel coordinate system of the image being measured, while the actual 

327 detections may start out in a different coordinate system. 

328 

329 Subclasses of this class must implement this method to define how 

330 those `~lsst.afw.detection.Footprint`\ s should be generated. 

331 

332 This default implementation transforms the 

333 `~lsst.afw.detection.Footprint`\ s from the reference catalog from the 

334 reference WCS to the exposure's WcS, which downgrades 

335 `lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s into regular 

336 `~lsst.afw.detection.Footprint`\ s, destroying deblend information. 

337 """ 

338 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs) 

339 

340 def getExposure(self, dataRef): 

341 """Read input exposure on which measurement will be performed. 

342 

343 Parameters 

344 ---------- 

345 dataRef : `lsst.daf.persistence.ButlerDataRef` 

346 Butler data reference. 

347 """ 

348 return dataRef.get(self.dataPrefix + "calexp", immediate=True) 

349 

350 def writeOutput(self, dataRef, sources): 

351 """Write forced source table 

352 

353 Parameters 

354 ---------- 

355 dataRef : `lsst.daf.persistence.ButlerDataRef` 

356 Butler data reference. The forced_src dataset (with 

357 self.dataPrefix prepended) is all that will be modified. 

358 sources : `lsst.afw.table.SourceCatalog` 

359 Catalog of sources to save. 

360 """ 

361 dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS) 

362 

363 def getSchemaCatalogs(self): 

364 """The schema catalogs that will be used by this task. 

365 

366 Returns 

367 ------- 

368 schemaCatalogs : `dict` 

369 Dictionary mapping dataset type to schema catalog. 

370 

371 Notes 

372 ----- 

373 There is only one schema for each type of forced measurement. The 

374 dataset type for this measurement is defined in the mapper. 

375 """ 

376 catalog = lsst.afw.table.SourceCatalog(self.measurement.schema) 

377 catalog.getTable().setMetadata(self.measurement.algMetadata) 

378 datasetType = self.dataPrefix + "forced_src" 

379 return {datasetType: catalog} 

380 

381 def _getConfigName(self): 

382 # Documented in superclass 

383 return self.dataPrefix + "forced_config" 

384 

385 def _getMetadataName(self): 

386 # Documented in superclass 

387 return self.dataPrefix + "forced_metadata"