22"""Base measurement task, which subclassed by the single frame and forced
30from .pluginRegistry
import PluginMap
31from ._measBaseLib
import FatalAlgorithmError, MeasurementError
33from .pluginsBase
import BasePluginConfig, BasePlugin
34from .noiseReplacer
import NoiseReplacerConfig
36__all__ = (
"BaseMeasurementPluginConfig",
"BaseMeasurementPlugin",
37 "BaseMeasurementConfig",
"BaseMeasurementTask")
41FATAL_EXCEPTIONS = (MemoryError, FatalAlgorithmError)
45 """Base config class for all measurement plugins.
49 Most derived classes will want to override `setDefaults` in order to
50 customize the default `executionOrder`.
52 A derived class whose corresponding Plugin class implements a do `measureN`
53 method should additionally add a bool `doMeasureN` field to replace the
54 bool class attribute defined here.
57 doMeasure = lsst.pex.config.Field(dtype=bool, default=
True,
58 doc=
"whether to run this plugin in single-object mode")
64 """Base class for all measurement plugins.
68 This is class is a placeholder for future behavior which will be shared
69 only between measurement plugins and is implemented for symmetry with the
70 measurement base plugin configuration class
77 """Assign named plugins to measurement slots.
79 Slot configuration which assigns a particular named plugin to each of a set
80 of slots. Each slot allows a type of measurement to be fetched from the
81 `lsst.afw.table.SourceTable` without knowing which algorithm was used to
86 The default algorithm for each slot must be registered, even if the default
90 Field = lsst.pex.config.Field
91 centroid =
Field(dtype=str, default=
"base_SdssCentroid", optional=
True,
92 doc=
"the name of the centroiding algorithm used to set source x,y")
93 shape =
Field(dtype=str, default=
"base_SdssShape", optional=
True,
94 doc=
"the name of the algorithm used to set source moments parameters")
95 psfShape =
Field(dtype=str, default=
"base_SdssShape_psf", optional=
True,
96 doc=
"the name of the algorithm used to set PSF moments parameters")
97 apFlux =
Field(dtype=str, default=
"base_CircularApertureFlux_12_0", optional=
True,
98 doc=
"the name of the algorithm used to set the source aperture instFlux slot")
99 modelFlux =
Field(dtype=str, default=
"base_GaussianFlux", optional=
True,
100 doc=
"the name of the algorithm used to set the source model instFlux slot")
101 psfFlux =
Field(dtype=str, default=
"base_PsfFlux", optional=
True,
102 doc=
"the name of the algorithm used to set the source psf instFlux slot")
103 gaussianFlux =
Field(dtype=str, default=
"base_GaussianFlux", optional=
True,
104 doc=
"the name of the algorithm used to set the source Gaussian instFlux slot")
105 calibFlux =
Field(dtype=str, default=
"base_CircularApertureFlux_12_0", optional=
True,
106 doc=
"the name of the instFlux measurement algorithm used for calibration")
109 """Set up a slots in a schema following configuration directives.
113 schema : `lsst.afw.table.Schema`
114 The schema in which slots will be set up.
118 This is defined in this configuration class to support use in unit
119 tests without needing to construct an `lsst.pipe.base.Task` object.
121 aliases = schema.getAliasMap()
123 aliases.set(
"slot_Centroid", self.
centroid)
124 if self.
shape is not None:
125 aliases.set(
"slot_Shape", self.
shape)
127 aliases.set(
"slot_PsfShape", self.
psfShape)
128 if self.
apFlux is not None:
129 aliases.set(
"slot_ApFlux", self.
apFlux)
131 aliases.set(
"slot_ModelFlux", self.
modelFlux)
133 aliases.set(
"slot_PsfFlux", self.
psfFlux)
137 aliases.set(
"slot_CalibFlux", self.
calibFlux)
141 """Base configuration for all measurement driver tasks."""
142 slots = lsst.pex.config.ConfigField(
143 dtype=SourceSlotConfig,
144 doc=
"Mapping from algorithms to special column aliases."
149 if self.
slots.centroid
is not None and self.
slots.centroid
not in self.plugins.names:
150 raise lsst.pex.config.FieldValidationError(
151 self.__class__.slots,
153 "source centroid slot algorithm is not being run."
155 if self.
slots.shape
is not None and self.
slots.shape
not in self.plugins.names:
156 raise lsst.pex.config.FieldValidationError(
157 self.__class__.slots,
159 "source shape slot algorithm '%s' is not being run." % self.
slots.shape
161 for slot
in (self.
slots.psfFlux, self.
slots.apFlux, self.
slots.modelFlux,
162 self.
slots.gaussianFlux, self.
slots.calibFlux):
164 for name
in self.plugins.names:
165 if len(name) <= len(slot)
and name == slot[:len(name)]:
168 raise lsst.pex.config.FieldValidationError(
169 self.__class__.slots,
171 f
"Source instFlux algorithm '{slot}' is not being run, required from "
172 f
"non-None slots in: {self.slots}."
177 """Base configuration for all measurement driver tasks except
178 SimpleForcedMeasurementTask.
182 ignoreSlotPluginChecks : `bool`, optional
183 Do not check that all slots have an associated plugin to run when
184 validating this config. This is primarily for tests that were written
185 before we made Tasks always call `config.validate()` on init.
186 DEPRECATED DM-35949: this is a temporary workaround while we better
187 define how config/schema validation works for measurement tasks.
191 Subclasses should define the 'plugins' and 'undeblended' registries, e.g.
195 plugins = PluginBaseClass.registry.makeField(
198 doc="Plugins to be run and their configuration"
200 undeblended = PluginBaseClass.registry.makeField(
203 doc="Plugins to run on undeblended image"
206 where ``PluginBaseClass`` is the appropriate base class of the plugin
207 (e.g., `SingleFramePlugin` or `ForcedPlugin`).
209 def __new__(cls, *args, ignoreSlotPluginChecks=False, **kwargs):
210 instance = super().
__new__(cls, *args, **kwargs)
211 if ignoreSlotPluginChecks:
212 msg = (
"ignoreSlotPluginChecks is deprecated and should only be used in tests."
213 " No removal date has been set; see DM-35949.")
214 warnings.warn(msg, category=FutureWarning, stacklevel=2)
215 object.__setattr__(instance,
"_ignoreSlotPluginChecks", ignoreSlotPluginChecks)
218 doReplaceWithNoise = lsst.pex.config.Field(
219 dtype=bool, default=
True, optional=
False,
220 doc=
'When measuring, replace other detected footprints with noise?')
222 noiseReplacer = lsst.pex.config.ConfigField(
223 dtype=NoiseReplacerConfig,
224 doc=
"configuration that sets how to replace neighboring sources with noise"
226 undeblendedPrefix = lsst.pex.config.Field(
227 dtype=str, default=
"undeblended_",
228 doc=
"Prefix to give undeblended plugins"
238 """Ultimate base class for all measurement tasks.
242 algMetadata : `lsst.daf.base.PropertyList` or `None`
243 Will be modified in-place to contain metadata about the plugins being
244 run. If `None`, an empty `~lsst.daf.base.PropertyList` will be
247 Additional arguments passed to `lsst.pipe.base.Task.__init__`.
251 This base class was created after `BaseMeasurementTask` already existed
252 to add a common base class for `SimpleForcedMeasurementTask`,
253 `SingleFrameMeasurementTask`, and `ForcedMeasurementTask` without
254 breaking downstream code. It is not intended to be used directly,
255 but rather to be subclassed by those tasks.
258 ConfigClass = SimpleBaseMeasurementConfig
259 _DefaultName =
"measurement"
262 """Plugins to be invoked (`PluginMap`).
264 Initially empty, this will be populated as plugins are initialized. It
265 should be considered read-only.
269 """Metadata about active plugins (`lsst.daf.base.PropertyList`).
271 Contains additional information about active plugins to be saved with
272 the output catalog. Will be filled by subclasses.
278 if algMetadata
is None:
283 """Initialize plugins (and slots) according to configuration.
288 Keyword arguments forwarded directly to plugin constructors.
292 Derived class constructors should call this method to fill the
293 `plugins` attribute and add corresponding output fields and slot
294 aliases to the output schema.
296 In addition to the attributes added by `BaseMeasurementTask.__init__`,
297 a ``schema``` attribute holding the output schema must be present
298 before this method is called.
300 Keyword arguments are forwarded directly to plugin constructors,
301 allowing derived classes to use plugins with different signatures.
307 if self.config.slots.centroid
is not None:
308 self.
plugins[self.config.slots.centroid] =
None
311 for executionOrder, name, config, PluginClass
in sorted(self.config.plugins.apply()):
314 if getattr(PluginClass,
"hasLogName",
False):
316 logName=self.log.getChild(name).name, **kwds)
323 if self.config.slots.centroid
is not None and self.
plugins[self.config.slots.centroid]
is None:
324 del self.
plugins[self.config.slots.centroid]
327 invalidPsfName =
"base_InvalidPsf_flag"
328 if invalidPsfName
in schema:
334 doc=
"Invalid PSF at this location.",
338 """Call ``measure`` on all plugins and consistently handle exceptions.
342 measRecord : `lsst.afw.table.SourceRecord`
343 The record corresponding to the object being measured. Will be
344 updated in-place with the results of measurement.
346 Positional arguments forwarded to ``plugin.measure``
348 Keyword arguments. Two are handled locally:
351 Beginning execution order (inclusive). Measurements with
352 ``executionOrder`` < ``beginOrder`` are not executed. `None`
356 Ending execution order (exclusive). Measurements with
357 ``executionOrder`` >= ``endOrder`` are not executed. `None`
360 Others are forwarded to ``plugin.measure()``.
364 This method can be used with plugins that have different signatures;
365 the only requirement is that ``measRecord`` be the first argument.
366 Subsequent positional arguments and keyword arguments are forwarded
367 directly to the plugin.
369 This method should be considered "protected": it is intended for use by
370 derived classes, not users.
372 beginOrder = kwds.pop(
"beginOrder",
None)
373 endOrder = kwds.pop(
"endOrder",
None)
374 for plugin
in self.
plugins.iter():
375 if beginOrder
is not None and plugin.getExecutionOrder() < beginOrder:
377 if endOrder
is not None and plugin.getExecutionOrder() >= endOrder:
382 """Call ``measure`` on the specified plugin.
384 Exceptions are handled in a consistent way.
388 plugin : subclass of `BasePlugin`
389 Plugin that will be executed.
390 measRecord : `lsst.afw.table.SourceRecord`
391 The record corresponding to the object being measured. Will be
392 updated in-place with the results of measurement.
394 Positional arguments forwarded to ``plugin.measure()``.
396 Keyword arguments forwarded to ``plugin.measure()``.
400 This method can be used with plugins that have different signatures;
401 the only requirement is that ``plugin`` and ``measRecord`` be the first
402 two arguments. Subsequent positional arguments and keyword arguments
403 are forwarded directly to the plugin.
405 This method should be considered "protected": it is intended for use by
406 derived classes, not users.
409 plugin.measure(measRecord, *args, **kwds)
410 except FATAL_EXCEPTIONS:
412 except MeasurementError
as error:
413 self.log.getChild(plugin.name).debug(
414 "MeasurementError in %s.measure on record %s: %s",
415 plugin.name, measRecord.getId(), error)
416 plugin.fail(measRecord, error)
417 except InvalidPsfError
as error:
418 self.log.getChild(plugin.name).debug(
419 "InvalidPsfError in %s.measure on record %s: %s",
420 plugin.name, measRecord.getId(), error)
422 plugin.fail(measRecord)
423 except Exception
as error:
424 self.log.getChild(plugin.name).warning(
425 "Exception in %s.measure on record %s: %s",
426 plugin.name, measRecord.getId(), error)
427 plugin.fail(measRecord)
431 """Ultimate base class for all measurement tasks
432 other than SimpleForcedMeasurementTask.
436 algMetadata : `lsst.daf.base.PropertyList` or `None`
437 Will be modified in-place to contain metadata about the plugins being
438 run. If `None`, an empty `~lsst.daf.base.PropertyList` will be
441 Additional arguments passed to `lsst.pipe.base.Task.__init__`.
445 This base class for `SingleFrameMeasurementTask` and
446 `ForcedMeasurementTask` mostly exists to share code between the two, and
447 generally should not be used directly.
450 ConfigClass = BaseMeasurementConfig
453 super().
__init__(algMetadata=algMetadata, **kwds)
459 for executionOrder, name, config, PluginClass
in sorted(self.config.undeblended.apply()):
460 undeblendedName = self.config.undeblendedPrefix + name
461 if getattr(PluginClass,
"hasLogName",
False):
464 logName=self.log.getChild(undeblendedName).name,
471 """Call ``measureN`` on all plugins and consistently handle exceptions.
475 measCat : `lsst.afw.table.SourceCatalog`
476 Catalog containing only the records for the source family to be
477 measured, and where outputs should be written.
479 Positional arguments forwarded to ``plugin.measure()``
481 Keyword arguments. Two are handled locally:
484 Beginning execution order (inclusive): Measurements with
485 ``executionOrder`` < ``beginOrder`` are not executed. `None`
488 Ending execution order (exclusive): measurements with
489 ``executionOrder`` >= ``endOrder`` are not executed. `None` for
492 Others are are forwarded to ``plugin.measure()``.
496 This method can be used with plugins that have different signatures;
497 the only requirement is that ``measRecord`` be the first argument.
498 Subsequent positional arguments and keyword arguments are forwarded
499 directly to the plugin.
501 This method should be considered "protected": it is intended for use by
502 derived classes, not users.
504 beginOrder = kwds.pop(
"beginOrder",
None)
505 endOrder = kwds.pop(
"endOrder",
None)
506 for plugin
in self.
plugins.iterN():
507 if beginOrder
is not None and plugin.getExecutionOrder() < beginOrder:
509 if endOrder
is not None and plugin.getExecutionOrder() >= endOrder:
514 """Call ``measureN`` on the specified plugin.
516 Exceptions are handled in a consistent way.
520 plugin : subclass of `BasePlugin`
521 Plugin that will be executed.
522 measCat : `lsst.afw.table.SourceCatalog`
523 Catalog containing only the records for the source family to be
524 measured, and where outputs should be written.
526 Positional arguments forwarded to ``plugin.measureN()``.
528 Keyword arguments forwarded to ``plugin.measureN()``.
532 This method can be used with plugins that have different signatures;
533 the only requirement is that the ``plugin`` and ``measCat`` be the
534 first two arguments. Subsequent positional arguments and keyword
535 arguments are forwarded directly to the plugin.
537 This method should be considered "protected": it is intended for use by
538 derived classes, not users.
541 plugin.measureN(measCat, *args, **kwds)
542 except FATAL_EXCEPTIONS:
545 except MeasurementError
as error:
546 self.log.getChild(plugin.name).debug(
547 "MeasurementError in %s.measureN on records %s-%s: %s",
548 plugin.name, measCat[0].getId(), measCat[-1].getId(), error)
549 for measRecord
in measCat:
550 plugin.fail(measRecord, error)
551 except InvalidPsfError
as error:
552 self.log.getChild(plugin.name).debug(
553 "InvalidPsfError in %s.measureN on records %s-%s: %s",
554 plugin.name, measCat[0].getId(), measCat[-1].getId(), error)
555 for measRecord
in measCat:
557 plugin.fail(measRecord, error)
558 except Exception
as error:
559 self.log.getChild(plugin.name).warning(
560 "Exception in %s.measureN on records %s-%s: %s",
561 plugin.name, measCat[0].getId(), measCat[-1].getId(), error)
562 for measRecord
in measCat:
563 plugin.fail(measRecord)
567 """Get a set of footprints from a catalog, keyed by id.
571 catalog : `lsst.afw.table.SourceCatalog`
572 Catalog with `lsst.afw.detection.Footprint`s attached.
576 footprints : `dict` [`int`: (`int`, `lsst.afw.detection.Footprint`)]
577 Dictionary of footprint, keyed by id number, with a tuple of
578 the parent id and footprint.
580 return {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
581 for measRecord
in catalog}
__new__(cls, *args, ignoreSlotPluginChecks=False, **kwargs)
callMeasureN(self, measCat, *args, **kwds)
__init__(self, algMetadata=None, **kwds)
doMeasurementN(self, plugin, measCat, *args, **kwds)
getFootprintsFromCatalog(catalog)
initializePlugins(self, **kwds)
doMeasurement(self, plugin, measRecord, *args, **kwds)
__init__(self, algMetadata=None, **kwds)
addInvalidPsfFlag(self, schema)
initializePlugins(self, **kwds)
callMeasure(self, measRecord, *args, **kwds)
setupSchema(self, schema)