Coverage for python/lsst/meas/base/wrappers.py: 54%

174 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-07 10:11 +0000

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 

22import warnings 

23 

24import lsst.pex.config 

25from .pluginsBase import BasePlugin 

26from .pluginRegistry import generateAlgorithmName, register 

27from .apCorrRegistry import addApCorrName 

28from .sfm import SingleFramePlugin, SingleFramePluginConfig 

29from .forcedMeasurement import ForcedPlugin, ForcedPluginConfig 

30 

31__all__ = ("wrapSingleFrameAlgorithm", "wrapForcedAlgorithm", "wrapSimpleAlgorithm", 

32 "wrapAlgorithm", "wrapAlgorithmControl", "wrapTransform", "GenericPlugin") 

33 

34 

35class WrappedSingleFramePlugin(SingleFramePlugin): 

36 

37 def __init__(self, config, name, schema, metadata, logName=None): 

38 SingleFramePlugin.__init__(self, config, name, schema, metadata, logName=logName) 

39 if hasattr(self, "hasLogName") and self.hasLogName and logName is not None: 

40 self.cpp = self.factory(config, name, schema, metadata, logName=logName) 

41 else: 

42 self.cpp = self.factory(config, name, schema, metadata) 

43 

44 def measure(self, measRecord, exposure): 

45 self.cpp.measure(measRecord, exposure) 

46 

47 def measureN(self, measCat, exposure): 

48 self.cpp.measureN(measCat, exposure) 

49 

50 def fail(self, measRecord, error=None): 

51 self.cpp.fail(measRecord, error.cpp if error is not None else None) 

52 

53 

54class WrappedForcedPlugin(ForcedPlugin): 

55 

56 def __init__(self, config, name, schemaMapper, metadata, logName=None): 

57 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata, logName=logName) 

58 if hasattr(self, "hasLogName") and self.hasLogName and logName is not None: 

59 self.cpp = self.factory(config, name, schemaMapper, metadata, logName=logName) 

60 else: 

61 self.cpp = self.factory(config, name, schemaMapper, metadata) 

62 

63 def measure(self, measRecord, exposure, refRecord, refWcs): 

64 self.cpp.measureForced(measRecord, exposure, refRecord, refWcs) 

65 

66 def measureN(self, measCat, exposure, refCat, refWcs): 

67 self.cpp.measureNForced(measCat, exposure, refCat, refWcs) 

68 

69 def fail(self, measRecord, error=None): 

70 self.cpp.fail(measRecord, error.cpp if error is not None else None) 

71 

72 

73def wrapAlgorithmControl(Base, Control, module=None, hasMeasureN=False): 

74 """Wrap a C++ algorithm's control class into a Python config class. 

75 

76 Parameters 

77 ---------- 

78 Base : `SingleFramePluginConfig` or `ForcedPluginConfig` 

79 Base class for the returned config. 

80 Control : pybind11-wrapped version of a C++ class. 

81 Control class to be wrapped. 

82 module : module, `str`, `int`, or `None`; optional 

83 Either a module object, a string specifying the name of the module, or 

84 an integer specifying how far back in the stack to look for the module 

85 to use: ``0`` is `lsst.pex.config.wrap`, ``1`` is 

86 `lsst.meas.base.wrappers`, ``2`` is the immediate caller, etc. This 

87 will be used to set ``__module__`` for the new config class, and the 

88 class will also be added to the module. The default is none in which 

89 case module will be looked up from Control. 

90 hasMeasureN : `bool`, optional 

91 Whether the plugin supports fitting multiple objects at once (if so, a 

92 config option to enable/disable this will be added). 

93 

94 Returns 

95 ------- 

96 ConfigClass : `lsst.pex.config.Config` 

97 A new subclass of lsst.pex.config.Config. 

98 

99 Notes 

100 ----- 

101 This function is generally only called by `wrapAlgorithm`; it is unlikely 

102 users will have to call it directly. 

103 """ 

104 if hasMeasureN: 104 ↛ 110line 104 didn't jump to line 110, because the condition on line 104 was never true

105 # We need to add a Config field to enable multi-object measurement, to 

106 # replace the simple bool class attribute that's on the base class. 

107 # To do that, we create the Config class dynamically here, then call 

108 # makeControlClass to finish it off by adding fields from the control 

109 # object. 

110 cls = type( 

111 Control.__name__.replace("Control", "Config"), 

112 (Base,), 

113 {"doMeasureN": lsst.pex.config.Field(dtype=bool, default=True, 

114 doc="whether to run this plugin in multi-object mode")} 

115 ) 

116 ConfigClass = lsst.pex.config.makeConfigClass(Control, module=module, cls=cls) 

117 else: 

118 # If we don't have to add that Config field, we can delegate all of 

119 # the work to pex_config's makeControlClass 

120 ConfigClass = lsst.pex.config.makeConfigClass(Control, module=module, base=Base) 

121 return ConfigClass 

122 

123 

124def wrapAlgorithm(Base, AlgClass, factory, executionOrder, name=None, Control=None, 

125 ConfigClass=None, TransformClass=None, doRegister=True, shouldApCorr=False, 

126 apCorrList=(), hasLogName=False, **kwds): 

127 """Wrap a C++ algorithm class to create a measurement plugin. 

128 

129 Parameters 

130 ---------- 

131 Base : `SingleFramePlugin` or `ForcedPlugin` 

132 Base class for the returned Plugin. 

133 AlgClass : API compatible with `SingleFrameAlgorithm` or `ForcedAlgorithm` 

134 C++ algorithm class to convert. May either derive directly from 

135 `SingleFrameAlgorithm` or `ForcedAlgorithm`, or be an unrelated class 

136 which has the same ``measure`` and ``measureN`` signatures. 

137 factory : callable 

138 A callable that is used to construct an instance of ``AlgClass``. It 

139 must take four arguments, either ``(config, name, schema, metadata)`` 

140 or ``(config, name, schemaMapper, metadata)``, depending on whether 

141 the algorithm is single-frame or forced. 

142 executionOrder : `float` 

143 The order this plugin should be run, relative to others 

144 (see `BasePlugin.getExecutionOrder`). 

145 name : `str`, optional 

146 String to use when registering the algorithm. Ignored if 

147 ``doRegistry=False``, set to ``generateAlgorithmName(AlgClass)`` if 

148 `None`. 

149 Control : Pybind11-wrapped version of a C++ class, optional 

150 Pybind11-wrapped C++ Control class for the algorithm; 

151 ``AlgClass.Control`` is used if `None`. Ignored if ``ConfigClass`` 

152 is not `None`. 

153 ConfigClass : subclass of `BaseMeasurementPluginConfig` 

154 Python config class that wraps the C++ algorithm's pybind11-wrapped 

155 Control class. If `None`, `wrapAlgorithmControl` is called to 

156 generate a Config class using the ``Control`` argument. 

157 TransformClass : subclass of `MeasurementTransform`, optional 

158 Transformation which may be used to post-process the results of 

159 measurement. If `None`, the default defined by `BasePlugin` is 

160 used. 

161 doRegister : `bool`, optional 

162 If `True` (the default), register the plugin with ``Base``'s 

163 registry, allowing it to be used by measurement tasks. 

164 shouldApCorr : `bool`, optional 

165 Does this algorithm measure an instFlux that can be aperture 

166 corrected? This is shorthand for ``apCorrList=[name]`` and is ignored 

167 if ``apCorrList`` is specified. 

168 apCorrList : iterable of `str`, optional 

169 Field name prefixes for instFlux fields to be aperture corrected. If 

170 an algorithm measures a single instFlux that should be aperture 

171 corrected, then it is simpler to set ``shouldApCorr=True``. However, 

172 if an algorithm produces multiple such fields, then specify 

173 ``apCorrList`` instead. For example, ``modelfit_CModel`` produces 

174 three such fields: ``apCorrList= ("modelfit_CModel_exp", 

175 "modelfit_CModel_exp", "modelfit_CModel_def")`` If ``apCorrList`` is 

176 not empty then ``shouldApCorr`` is ignored. If non-empty and 

177 ``doRegister`` is `True` then the names are added to the set 

178 retrieved by ``getApCorrNameSet``. 

179 hasLogName : `bool`, optional 

180 `True` if the C++ algorithm supports ``logName`` as a constructor 

181 argument. 

182 **kwds 

183 Additional keyword arguments passed to generateAlgorithmControl, which 

184 may include: 

185 

186 - ``hasMeasureN``: Whether the plugin supports fitting multiple 

187 objects at once ;if so, a config option to enable/disable this will 

188 be added (`bool`). 

189 - ``executionOrder``: If not `None`, an override for the default 

190 execution order for this plugin (the default is ``2.0``, which is 

191 usually appropriate for fluxes; `bool`). 

192 

193 Returns 

194 ------- 

195 PluginClass : subclass of ``Base`` 

196 The new plugin class. 

197 """ 

198 if ConfigClass is None: 198 ↛ 203line 198 didn't jump to line 203, because the condition on line 198 was always true

199 if Control is None: 199 ↛ 200line 199 didn't jump to line 200, because the condition on line 199 was never true

200 Control = AlgClass.Control 

201 ConfigClass = wrapAlgorithmControl(Base.ConfigClass, Control, **kwds) 

202 

203 def getExecutionOrder(): 

204 return executionOrder 

205 typeDict = dict(AlgClass=AlgClass, ConfigClass=ConfigClass, factory=staticmethod(factory), 

206 getExecutionOrder=staticmethod(getExecutionOrder)) 

207 if TransformClass: 

208 typeDict['getTransformClass'] = staticmethod(lambda: TransformClass) 208 ↛ exitline 208 didn't run the lambda on line 208

209 PluginClass = type(AlgClass.__name__ + Base.__name__, (Base,), typeDict) 

210 if doRegister: 210 ↛ 216line 210 didn't jump to line 216, because the condition on line 210 was always true

211 if name is None: 

212 name = generateAlgorithmName(AlgClass) 

213 Base.registry.register(name, PluginClass) 

214 if shouldApCorr: 

215 addApCorrName(name) 

216 PluginClass.hasLogName = hasLogName 

217 return PluginClass 

218 

219 

220def wrapSingleFrameAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, hasMeasureN=False, 

221 hasLogName=False, deprecated=None, **kwds): 

222 """Expose a C++ ``SingleFrameAlgorithm`` class as a measurement plugin. 

223 

224 Parameters 

225 ---------- 

226 AlgClass : API compatible with `SingleFrameAlgorithm` 

227 C++ algorithm class to convert. May either derive directly from 

228 `SingleFrameAlgorithm` or be an unrelated class which has the same 

229 ``measure``, ``measureN`` and ``fail`` signatures. 

230 executionOrder : `float` 

231 The order this plugin should be run, relative to others 

232 (see `BasePlugin.getExecutionOrder`). 

233 name : `str`, optional 

234 Name to use when registering the algorithm. Ignored if 

235 ``doRegistry=False``; set to ``generateAlgorithmName(AlgClass)`` if 

236 `None`. 

237 needsMetadata : `bool`, optional 

238 Sets whether the ``AlgClass``'s constructor should be passed a 

239 `~lsst.daf.base.PropertySet` metadata argument. 

240 hasMeasureN : `bool`, optional 

241 Does the algorithm support simultaneous measurement of multiple 

242 sources? If `True`, a `bool` ``doMeasureN`` field will be added to 

243 the generated config class, and its value will be passed as the last 

244 argument when calling the ``AlgClass`` constructor. 

245 hasLogName : `bool`, optional 

246 `True` if the C++ algorithm supports ``logName`` as a constructor 

247 argument. 

248 deprecated : `str`, optional 

249 If specified, emit as a deprecation warning when the plugin is 

250 constructed. 

251 **kwds 

252 Additional keyword arguments are passed to the lower-level 

253 `wrapAlgorithm` and `wrapAlgorithmControl` classes. 

254 

255 Returns 

256 ------- 

257 singleFramePlugin : subclass of `SingleFramePlugin` 

258 The new measurement plugin class. 

259 

260 Notes 

261 ----- 

262 The first three arguments to the C++ constructor are expected to be 

263 ``Control const & ctrl, std::string const & name, Schema & schema``. 

264 

265 If ``needsMetadata`` is `True`, we also append ``PropertySet & metadata``. 

266 

267 If ``hasMeasureN`` is `True`, we also append ``bool doMeasureN``. 

268 

269 If ``hasLogName`` is `True`, we also append ``std::string logName``. 

270 

271 If more than one of the above is `True`, the metadata ``PropertySet`` 

272 precedes the ``doMeasureN`` ``bool`` and the ``logName`` comes last of the 

273 three. 

274 """ 

275 if hasMeasureN: 275 ↛ 276line 275 didn't jump to line 276, because the condition on line 275 was never true

276 if needsMetadata: 

277 def factory(config, name, schema, metadata, **kwargs): 

278 if deprecated: 

279 warnings.warn(deprecated, category=FutureWarning) 

280 return AlgClass(config.makeControl(), name, schema, metadata, config.doMeasureN, **kwargs) 

281 else: 

282 def factory(config, name, schema, metadata, **kwargs): 

283 if deprecated: 

284 warnings.warn(deprecated, category=FutureWarning) 

285 return AlgClass(config.makeControl(), name, schema, config.doMeasureN, **kwargs) 

286 else: 

287 if needsMetadata: 

288 def factory(config, name, schema, metadata, **kwargs): 

289 if deprecated: 

290 warnings.warn(deprecated, category=FutureWarning) 

291 return AlgClass(config.makeControl(), name, schema, metadata, **kwargs) 

292 else: 

293 def factory(config, name, schema, metadata, **kwargs): 

294 if deprecated: 

295 warnings.warn(deprecated, category=FutureWarning) 

296 return AlgClass(config.makeControl(), name, schema, **kwargs) 

297 

298 return wrapAlgorithm(WrappedSingleFramePlugin, AlgClass, executionOrder=executionOrder, name=name, 

299 factory=factory, hasMeasureN=hasMeasureN, hasLogName=hasLogName, **kwds) 

300 

301 

302def wrapForcedAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, 

303 hasMeasureN=False, needsSchemaOnly=False, hasLogName=False, 

304 deprecated=None, **kwds): 

305 """Expose a C++ ``ForcedAlgorithm`` class as a measurement plugin. 

306 

307 Parameters 

308 ---------- 

309 AlgClass : API compatible with `ForcedAlgorithm` 

310 C++ algorithm class to convert. May either derive directly from 

311 `ForcedAlgorithm` or be an unrelated class which has the same 

312 ``measure``, ``measureN`` and ``fail`` signatures. 

313 executionOrder : `float` 

314 The order this plugin should be run, relative to others 

315 (see `BasePlugin.getExecutionOrder`). 

316 name : `str`, optional 

317 Name to use when registering the algorithm. Ignored if 

318 ``doRegistry=False``; set to ``generateAlgorithmName(AlgClass)`` if 

319 `None`. 

320 needsMetadata : `bool`, optional 

321 Sets whether the ``AlgClass``'s constructor should be passed a 

322 `~lsst.daf.base.PropertySet` metadata argument. 

323 hasMeasureN : `bool`, optional 

324 Does the algorithm support simultaneous measurement of multiple 

325 sources? If `True`, a `bool` ``doMeasureN`` field will be added to 

326 the generated config class, and its value will be passed as the last 

327 argument when calling the ``AlgClass`` constructor. 

328 hasLogName : `bool`, optional 

329 `True` if the C++ algorithm supports ``logName`` as a constructor 

330 argument. 

331 needsSchemaOnly : `bool`, optional 

332 Whether the algorithm constructor expects a Schema argument 

333 (representing the output `~lsst.afw.table.Schema`) rather than the 

334 full `~lsst.afw.table.SchemaMapper` (which provides access to both the 

335 reference schema and the output schema). 

336 deprecated : `str`, optional 

337 If specified, emit as a deprecation warning when the plugin is 

338 constructed. 

339 **kwds 

340 Additional keyword arguments are passed to the lower-level 

341 `wrapAlgorithm` and `wrapAlgorithmControl` classes. 

342 

343 Returns 

344 ------- 

345 forcedPlugin : subclass of `ForcedPlugin` 

346 The new measurement plugin class. 

347 

348 Notes 

349 ----- 

350 The first two arguments to the C++ constructor are expected to be 

351 ``Control const & ctrl, std::string const & name`` 

352 

353 If ``needsSchemaOnly`` is `True`, then the third argument will be 

354 ``Schema & schema``; otherwise, it will be ``SchemaMapper & 

355 schemaMapper``. 

356 

357 If ``needsMetadata`` is `True`, we also append ``PropertySet & 

358 metadata``. 

359 

360 If ``hasMeasureN`` is `True`, we also append ``bool doMeasureN``. 

361 

362 If ``hasLogName`` is `True`, we also append ``std::string logName``. 

363 

364 If more than one of the above is `True`, the metadata ``PropertySet`` 

365 precedes the ``doMeasureN`` ``bool`` and the ``logName`` comes last of the 

366 three. 

367 """ 

368 if needsSchemaOnly: 368 ↛ 372line 368 didn't jump to line 372, because the condition on line 368 was always true

369 def extractSchemaArg(m): 

370 return m.editOutputSchema() 

371 else: 

372 def extractSchemaArg(m): 

373 return m 

374 if hasMeasureN: 374 ↛ 375line 374 didn't jump to line 375, because the condition on line 374 was never true

375 if needsMetadata: 

376 def factory(config, name, schemaMapper, metadata, **kwargs): 

377 if deprecated: 

378 warnings.warn(deprecated, category=FutureWarning) 

379 return AlgClass(config.makeControl(), name, extractSchemaArg(schemaMapper), 

380 metadata, config.doMeasureN, **kwargs) 

381 else: 

382 def factory(config, name, schemaMapper, metadata, **kwargs): 

383 if deprecated: 

384 warnings.warn(deprecated, category=FutureWarning) 

385 return AlgClass(config.makeControl(), name, extractSchemaArg(schemaMapper), 

386 config.doMeasureN, **kwargs) 

387 else: 

388 if needsMetadata: 

389 def factory(config, name, schemaMapper, metadata, **kwargs): 

390 if deprecated: 

391 warnings.warn(deprecated, category=FutureWarning) 

392 return AlgClass(config.makeControl(), name, extractSchemaArg(schemaMapper), 

393 metadata, **kwargs) 

394 else: 

395 def factory(config, name, schemaMapper, metadata, **kwargs): 

396 if deprecated: 

397 warnings.warn(deprecated, category=FutureWarning) 

398 return AlgClass(config.makeControl(), name, extractSchemaArg(schemaMapper), **kwargs) 

399 

400 return wrapAlgorithm(WrappedForcedPlugin, AlgClass, executionOrder=executionOrder, name=name, 

401 factory=factory, hasLogName=hasLogName, **kwds) 

402 

403 

404def wrapSimpleAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, hasMeasureN=False, 

405 hasLogName=False, deprecated=None, **kwds): 

406 r"""Expose a C++ ``SimpleAlgorithm`` class as a measurement plugin. 

407 

408 ``SimpleAlgorithm``\ s are made available as both `SingleFramePlugin`\ s 

409 and `ForcedPlugin`\ s. 

410 

411 Parameters 

412 ---------- 

413 AlgClass : Subclass of C++ ``SimpleAlgorithm``, or API compatible 

414 Algorithm class to convert. The C++ class should be wrapped with 

415 Pybind11, and must provide ``measure()``, ``measureN()`` and ``fail()` 

416 signatures equivalent to ``SimpleAlgorithm``. 

417 executionOrder : `float` 

418 The order this plugin should be run, relative to others 

419 (see `~BasePlugin.getExecutionOrder`). 

420 name : `str`, optional 

421 Name to use when registering the algorithm. Ignored if 

422 ``doRegistry=False``; set to ``generateAlgorithmName(AlgClass)`` if 

423 `None`. 

424 needsMetadata : `bool`, optional 

425 Sets whether the ``AlgClass``'s constructor should be passed a 

426 `~lsst.daf.base.PropertySet` metadata argument. 

427 hasMeasureN : `bool`, optional 

428 Does the algorithm support simultaneous measurement of multiple 

429 sources? If `True`, a `bool` ``doMeasureN`` field will be added to 

430 the generated config class, and its value will be passed as the last 

431 argument when calling the ``AlgClass`` constructor. 

432 hasLogName : `bool`, optional 

433 `True` if the C++ algorithm supports ``logName`` as a constructor 

434 argument. 

435 deprecated : `str`, optional 

436 If specified, emit as a deprecation warning when the plugin is 

437 constructed. 

438 **kwds 

439 Additional keyword arguments are passed to the lower-level 

440 `wrapAlgorithm` and `wrapAlgorithmControl` classes. 

441 

442 Returns 

443 ------- 

444 singleFramePlugin : subclass of `SingleFramePlugin` 

445 The new single frame measurement plugin class. 

446 forcedPlugin : subclass of `ForcedPlugin` 

447 The new forced measurement plugin class. 

448 

449 Notes 

450 ----- 

451 The first three arguments to the C++ constructor are expected to be 

452 ``Control const & ctrl, std::string const & name, Schema & schema``. 

453 

454 If ``needsMetadata`` is `True`, we also append ``PropertySet & 

455 metadata``. 

456 

457 If ``hasMeasureN`` is `True`, we also append ``bool doMeasureN``. 

458 

459 If ``hasLogName`` is `True`, we also append ``std::string logName``. 

460 

461 If more than one of the above is `True`, the metadata ``PropertySet`` 

462 precedes the ``doMeasureN`` ``bool`` and the ``logName`` comes last of the 

463 three. 

464 """ 

465 return (wrapSingleFrameAlgorithm(AlgClass, executionOrder=executionOrder, name=name, 

466 needsMetadata=needsMetadata, hasLogName=hasLogName, 

467 deprecated=deprecated, **kwds), 

468 wrapForcedAlgorithm(AlgClass, executionOrder=executionOrder, name=name, 

469 needsMetadata=needsMetadata, hasLogName=hasLogName, 

470 needsSchemaOnly=True, **kwds)) 

471 

472 

473def wrapTransform(transformClass, hasLogName=False): 

474 """Modify a C++ transform to accept either a ``Config`` or a ``Control``. 

475 

476 That is, the configuration may either be provided as a (C++) ``Control`` 

477 object or an instance of a Python class derived from 

478 `~lsst.meas.base.BasePluginConfig`. 

479 

480 Parameters 

481 ---------- 

482 transformClass : Subclass of C++ ``BaseTransform`` 

483 A C++ transform class, wrapped with pybind11. Its constructor must 

484 take a ``Control`` object, a ``std::string``, and a 

485 `~lsst.afw.table.SchemaMapper`, in that order. 

486 hasLogName : `bool`, optional 

487 Unused. 

488 """ 

489 oldInit = transformClass.__init__ 

490 

491 def _init(self, ctrl, name, mapper, logName=None): 

492 if hasattr(ctrl, "makeControl"): 

493 ctrl = ctrl.makeControl() 

494 # logName signature needs to be on this Class __init__, but is not 

495 # needed by the C++ plugin. 

496 oldInit(self, ctrl, name, mapper) 

497 

498 transformClass.__init__ = _init 

499 

500 

501class GenericPlugin(BasePlugin): 

502 """Abstract base class for a generic plugin. 

503 

504 Parameters 

505 ---------- 

506 config : `lsst.pex.config.Config` 

507 An instance of this class' ``ConfigClass``. 

508 name : `str` 

509 Name of this measurement plguin, for registering. 

510 schema : `lsst.afw.table.Schema` 

511 The catalog schema. New fields should be added here to 

512 hold measurements produced by this plugin. 

513 metadata : `lsst.daf.base.PropertySet` 

514 Metadata that will be attached to the output catalog. 

515 logName : `str`, optional 

516 Name of log component. 

517 

518 Notes 

519 ----- 

520 A generic plugin can be used with the `singleFramePluginFromGeneric` 

521 and/or `forcedPluginFromGeneric` wrappers to create classes that can be 

522 used for single frame measurement and/or forced measurement (as 

523 appropriate). The only real difference between `SingleFramePlugin` and 

524 `ForcedPlugin` is the ``measure`` method; this class introduces a shared 

525 signature for `measure` that, in combination with the aforementioned 

526 wrappers, allows both plugin styles to share a single implementation. 

527 

528 This doesn't use `abc.ABCMeta` because I couldn't get it to work 

529 with a superclass. 

530 

531 Sub-classes should set `ConfigClass` and implement the `measure` and 

532 `measureN` methods. They may optionally provide alternative 

533 implementations for the `__init__`, `fail` and `getExecutionOrder` 

534 methods. 

535 

536 This default implementation simply adds a field for recording 

537 a fatal failure of the measurement plugin. 

538 """ 

539 ConfigClass = None 

540 

541 @classmethod 

542 def getExecutionOrder(cls): 

543 return 0 

544 

545 def __init__(self, config, name, schema, metadata, logName=None): 

546 BasePlugin.__init__(self, config, name, logName=logName) 

547 self._failKey = schema.addField(name + '_flag', type="Flag", doc="Set for any fatal failure") 

548 

549 def measure(self, measRecord, exposure, center): 

550 """Measure a single source. 

551 

552 It is the responsibility of this method to perform the desired 

553 measurement and record the result in the `measRecord`. 

554 

555 Parameters 

556 ---------- 

557 measRecord : `lsst.afw.table.SourceRecord` 

558 Catalog record for the source being measured. 

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

560 Exposure on which the source is being measured. 

561 center : `lsst.geom.Point2D` 

562 Pixel coordinates of the object. 

563 

564 Raises 

565 ------ 

566 MeasurementError 

567 Raised if the measurement fails for a known/justifiable reason. 

568 """ 

569 raise NotImplementedError() 

570 

571 def measureN(self, measCat, exposure, refCat, refWcs): 

572 """Measure multiple sources. 

573 

574 It is the responsibility of this method to perform the desired 

575 measurement and record the result in the `measCat`. 

576 

577 Parameters 

578 ---------- 

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

580 Catalog for the sources being measured. 

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

582 Exposure on which the source is being measured. 

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

584 Reference catalog. 

585 refWcs : `lsst.afw.image.Wcs` 

586 Astrometric solution for the reference image. 

587 

588 Raises 

589 ------ 

590 MeasurementError 

591 Raised if the measurement fails for a known/justifiable reason. 

592 """ 

593 raise NotImplementedError() 

594 

595 def fail(self, measRecord, error=None): 

596 """Record a measurement failure. 

597 

598 This default implementation simply records the failure in the source 

599 record. 

600 

601 Parameters 

602 ---------- 

603 measRecord : `lsst.afw.table.SourceRecord` 

604 Catalog record for the source being measured. 

605 error : `Exception` 

606 Error causing failure, or `None`. 

607 """ 

608 measRecord.set(self._failKey, True) 

609 

610 @classmethod 

611 def makeSingleFramePlugin(cls, name): 

612 """Produce a SingleFramePlugin subclass from this GenericPlugin class. 

613 

614 The class is also registered. 

615 

616 Parameters 

617 ---------- 

618 name : `str` 

619 Name of plugin to register. 

620 """ 

621 class SingleFrameFromGenericConfig(cls.ConfigClass, SingleFramePluginConfig): 

622 pass 

623 

624 @register(name) 

625 class SingleFrameFromGenericPlugin(SingleFramePlugin): 

626 ConfigClass = SingleFrameFromGenericConfig 

627 

628 def __init__(self, config, name, schema, metadata, logName=None): 

629 SingleFramePlugin.__init__(self, config, name, schema, metadata, logName=logName) 

630 self._generic = cls(config, name, schema, metadata) 

631 

632 def measure(self, measRecord, exposure): 

633 center = measRecord.getCentroid() 

634 return self._generic.measure(measRecord, exposure, center) 

635 

636 def measureN(self, measCat, exposure, refCat, refWcs): 

637 return self._generic.measureN(measCat, exposure, refCat, refWcs) 

638 

639 def fail(self, measRecord, error=None): 

640 self._generic.fail(measRecord, error if error is not None else None) 

641 

642 @staticmethod 

643 def getExecutionOrder(): 

644 return cls.getExecutionOrder() 

645 

646 def getTransformClass(self): 

647 return self._generic.getTransformClass() 

648 

649 return SingleFrameFromGenericPlugin 

650 

651 @classmethod 

652 def makeForcedPlugin(cls, name): 

653 """Produce a ForcedPlugin subclass from this GenericPlugin class. 

654 

655 The class is also registered. 

656 

657 Parameters 

658 ---------- 

659 name : `str` 

660 Name of plugin to register. 

661 """ 

662 class ForcedFromGenericConfig(cls.ConfigClass, ForcedPluginConfig): 

663 pass 

664 

665 @register(name) 

666 class ForcedFromGenericPlugin(ForcedPlugin): 

667 ConfigClass = ForcedFromGenericConfig 

668 

669 def __init__(self, config, name, schemaMapper, metadata, logName=None): 

670 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata, logName=logName) 

671 schema = schemaMapper.editOutputSchema() 

672 self._generic = cls(config, name, schema, metadata) 

673 

674 def measure(self, measRecord, exposure, refRecord, refWcs): 

675 # Forced photometry tasks should almost be configured with a 

676 # centroider (populating measRecord.getCentroid()) that 

677 # transforms the reference centroid, but we respect their 

678 # decision if they decided to re-centroid (or do something more 

679 # unusual) on the image being measured. 

680 center = measRecord.getCentroid() 

681 return self._generic.measure(measRecord, exposure, center) 

682 

683 def measureN(self, measCat, exposure, refCat, refWcs): 

684 return self._generic.measureN(measCat, exposure, refCat, refWcs) 

685 

686 def fail(self, measRecord, error=None): 

687 self._generic.fail(measRecord, error if error is not None else None) 

688 

689 @staticmethod 

690 def getExecutionOrder(): 

691 return cls.getExecutionOrder() 

692 

693 def getTransformClass(self): 

694 return self._generic.getTransformClass() 

695 

696 return ForcedFromGenericPlugin