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, NoiseReplacer, DummyNoiseReplacer
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
452 NOISE_SEED_MULTIPLIER =
"NOISE_SEED_MULTIPLIER"
453 """Name by which the noise seed multiplier is recorded in metadata ('str').
456 NOISE_SOURCE =
"NOISE_SOURCE"
457 """Name by which the noise source is recorded in metadata ('str').
460 NOISE_OFFSET =
"NOISE_OFFSET"
461 """Name by which the noise offset is recorded in metadata ('str').
464 NOISE_EXPOSURE_ID =
"NOISE_EXPOSURE_ID"
465 """Name by which the noise exposire ID is recorded in metadata ('str').
469 super().
__init__(algMetadata=algMetadata, **kwds)
475 for executionOrder, name, config, PluginClass
in sorted(self.config.undeblended.apply()):
476 undeblendedName = self.config.undeblendedPrefix + name
477 if getattr(PluginClass,
"hasLogName",
False):
480 logName=self.log.getChild(undeblendedName).name,
488 """Get a set of footprints from a catalog, keyed by id.
492 catalog : `lsst.afw.table.SourceCatalog`
493 Catalog with `lsst.afw.detection.Footprint`s attached.
497 footprints : `dict` [`int`: (`int`, `lsst.afw.detection.Footprint`)]
498 Dictionary of footprint, keyed by id number, with a tuple of
499 the parent id and footprint.
501 return {measRecord.getId(): (measRecord.getParent(), measRecord.getFootprint())
502 for measRecord
in catalog}
505 """Replace all pixels in the exposure covered by the footprint of
506 ``measRecord`` with noise.
510 exposure : `lsst.afw.image.Exposure`
511 Exposure in which to replace pixels.
512 measCat : `lsst.afw.table.SourceCatalog`
513 Catalog that will be measured.t
514 footprints : `dict` [`int`: (`int`, `lsst.afw.detection.Footprint`)]
515 Dictionary of footprints, keyed by id number, with a tuple of
516 the parent id and footprint.
517 exposureId : `int`, optional
518 Unique identifier for the exposure.
519 noiseImage : `lsst.afw.image.Image` or `None`, optional
520 Image from which to draw noise pixels. If `None`, noise will be
521 drawn from the input exposure.
523 if self.config.doReplaceWithNoise:
525 self.config.noiseReplacer,
529 exposureId=exposureId,
530 noiseImage=noiseImage,
532 algMetadata = measCat.getTable().getMetadata()
533 if algMetadata
is not None:
535 algMetadata.addString(self.
NOISE_SOURCE, self.config.noiseReplacer.noiseSource)
536 algMetadata.addDouble(self.
NOISE_OFFSET, self.config.noiseReplacer.noiseOffset)
537 if exposureId
is not None:
__new__(cls, *args, ignoreSlotPluginChecks=False, **kwargs)
__init__(self, algMetadata=None, **kwds)
str NOISE_SEED_MULTIPLIER
getFootprintsFromCatalog(catalog)
initializePlugins(self, **kwds)
initNoiseReplacer(self, exposure, measCat, footprints, exposureId=None, noiseImage=None)
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)