LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+b203dec576,g18429d2f64+358861cd2c,g199a45376c+0ba108daf9,g1fd858c14a+dd066899e3,g262e1987ae+ebfced1d55,g29ae962dfc+72fd90588e,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+b668f15bc5,g4595892280+3897dae354,g47891489e3+abcf9c3559,g4d44eb3520+fb4ddce128,g53246c7159+8c5ae1fdc5,g67b6fd64d1+abcf9c3559,g67fd3c3899+1f72b5a9f7,g74acd417e5+cb6b47f07b,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+abcf9c3559,g8d7436a09f+bcf525d20c,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+6054cc57f1,g97be763408+06f794da49,g9dd6db0277+1f72b5a9f7,ga681d05dcb+7e36ad54cd,gabf8522325+735880ea63,gac2eed3f23+abcf9c3559,gb89ab40317+abcf9c3559,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+1f72b5a9f7,gdab6d2f7ff+cb6b47f07b,gdc713202bf+1f72b5a9f7,gdfd2d52018+8225f2b331,ge365c994fd+375fc21c71,ge410e46f29+abcf9c3559,geaed405ab2+562b3308c0,gf9a733ac38+8c5ae1fdc5,w.2025.35
LSST Data Management Base Package
Loading...
Searching...
No Matches
simple_forced_measurement.py
Go to the documentation of this file.
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__all__ = ("SimpleForcedMeasurementConfig", "SimpleForcedMeasurementTask")
23
24import numpy as np
25
26import lsst.afw.geom
27import lsst.afw.image
28import lsst.afw.table
29import lsst.daf.base
30import lsst.geom
31import lsst.pex.config
32import lsst.pipe.base
33from lsst.utils.logging import PeriodicLogger
34
35from .baseMeasurement import SimpleBaseMeasurementConfig, SimpleBaseMeasurementTask
36from .forcedMeasurement import ForcedPlugin
37
38
40 """Config class for SimpleForcedMeasurementTask."""
41 plugins = ForcedPlugin.registry.makeField(
42 multi=True,
43 default=["base_PixelFlags",
44 "base_TransformedCentroidFromCoord",
45 "base_PsfFlux",
46 ],
47 doc="Plugins to be run and their configuration"
48 )
49 psfFootprintScaling = lsst.pex.config.Field(
50 dtype=float,
51 doc="Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
52 default=3.0,
53 )
55 keytype=str, itemtype=str, doc="Mapping of reference columns to source columns",
56 default={"id": "objectId", "parent": "parentObjectId",
57 "coord_ra": "coord_ra", "coord_dec": "coord_dec"}
58 )
59 checkUnitsParseStrict = lsst.pex.config.Field(
60 doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
61 dtype=str,
62 default="raise",
63 )
64
65 def setDefaults(self):
66 self.slots.centroid = "base_TransformedCentroidFromCoord"
67 self.slots.shape = None
68 self.slots.apFlux = None
69 self.slots.modelFlux = None
70 self.slots.psfFlux = "base_PsfFlux"
71 self.slots.gaussianFlux = None
72 self.slots.calibFlux = None
73
74
76 """Measure sources on an image using a simple forced measurement algorithm.
77
78 This differes from ForcedMeasurmentTask in that it uses a PSF-based
79 footprint for every source so it does not need to transform footprints.
80
81 Parameters
82 ----------
83 algMetadata : `lsst.daf.base.PropertyList` or `None`
84 Will be updated in place to to record information about each
85 algorithm. An empty `~lsst.daf.base.PropertyList` will be created if
86 `None`.
87 **kwds
88 Keyword arguments are passed to the supertask constructor.
89 """
90 ConfigClass = SimpleForcedMeasurementConfig
91
92 def __init__(self, refSchema, algMetadata: lsst.daf.base.PropertyList = None, **kwds):
93 super().__init__(algMetadata=algMetadata, **kwds)
95 self.mapper.addMinimalSchema(lsst.afw.table.SourceTable.makeMinimalSchema(), False)
96 self.config.slots.setupSchema(self.mapper.editOutputSchema())
97 for refName, targetName in self.config.copyColumns.items():
98 refItem = refSchema.find(refName)
99 self.mapper.addMapping(refItem.key, targetName)
100 self.config.slots.setupSchema(self.mapper.editOutputSchema())
101 self.initializePlugins(schemaMapper=self.mapper)
102 self.addInvalidPsfFlag(self.mapper.editOutputSchema())
103 self.schema = self.mapper.getOutputSchema()
104 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
105
106 def run(
107 self,
108 refCat: lsst.afw.table.SourceCatalog,
110 exposure: lsst.afw.image.Exposure,
111 refWcs: lsst.afw.geom.SkyWcs,
112 beginOrder: int | None = None,
113 endOrder: int | None = None,
114 ) -> None:
115 """Perform forced measurement.
116
117 Parameters
118 ----------
119 refCat : `lsst.afw.table.SourceCatalog`
120 Catalog with locations and ids of sources to measure.
121 measCat : `lsst.afw.table.SourceCatalog`
122 Catalog that measurements are made on.
123 exposure : `lsst.afw.image.exposureF`
124 Image to be measured. Must have at least a `lsst.afw.geom.SkyWcs`
125 attached.
126 refWcs : `lsst.afw.geom.SkyWcs`
127 Defines the X,Y coordinate system of ``refCat``.
128 beginOrder : `int`, optional
129 Beginning execution order (inclusive). Algorithms with
130 ``executionOrder`` < ``beginOrder`` are not executed. `None` for no limit.
131 endOrder : `int`, optional
132 Ending execution order (exclusive). Algorithms with
133 ``executionOrder`` >= ``endOrder`` are not executed. `None` for no limit.
134 idFactory : `lsst.afw.table.IdFactory`, optional
135 Factory for creating IDs for sources.
136 """
137 self._attachPsfShapeFootprints(measCat, exposure, scaling=self.config.psfFootprintScaling)
138 self.log.info("Performing forced measurement on %d source%s", len(refCat),
139 "" if len(refCat) == 1 else "s")
140 # Wrap the task logger into a periodic logger.
141 periodicLog = PeriodicLogger(self.log)
142
143 for index in range(len(refCat)):
144 measRecord = measCat[index]
145 refRecord = refCat[index]
146 if measRecord.getFootprint() is None:
147 self.log.warning("Skipping object with ID %s that is off the image.", measRecord.getId())
148 self.callMeasure(measRecord, exposure, refRecord, refWcs,
149 beginOrder=beginOrder, endOrder=endOrder)
150 # Log a message if it has been a while since the last log.
151 periodicLog.log("Forced measurement complete for %d parents (and their children) out of %d",
152 index + 1, len(refCat))
153
154 def _attachPsfShapeFootprints(self, sources, exposure, scaling=3):
155 """Attach Footprints to blank sources prior to measurement, by
156 creating elliptical Footprints from the PSF moments.
157
158 Parameters
159 ----------
160 sources : `lsst.afw.table.SourceCatalog`
161 Blank catalog (with all rows and columns, but values other than
162 ``coord_ra``, ``coord_dec`` unpopulated).
163 to which footprints should be attached.
164 exposure : `lsst.afw.image.Exposure`
165 Image object from which peak values and the PSF are obtained.
166 scaling : `int`, optional
167 Scaling factor to apply to the PSF second-moments ellipse in order
168 to determine the footprint boundary.
169 """
170 psf = exposure.getPsf()
171 if psf is None:
172 raise RuntimeError("Cannot construct Footprints from PSF shape without a PSF.")
173 bbox = exposure.getBBox()
174 wcs = exposure.getWcs()
175 # This will always be coord_ra, coord_dec since we converted the
176 # astropy table into a schema and schema is always coord_ra, coord_dec.
177 x, y = wcs.skyToPixelArray(sources["coord_ra"], sources["coord_dec"], degrees=False)
178 inBBox = np.atleast_1d(lsst.geom.Box2D(bbox).contains(x, y))
179 for idx, record in enumerate(sources):
180 localPoint = lsst.geom.Point2D(x[idx], y[idx])
181 localIntPoint = lsst.geom.Point2I(localPoint)
182 if not inBBox[idx]:
183 record.setFootprint(None)
184 continue
185 ellipse = lsst.afw.geom.ellipses.Ellipse(psf.computeShape(localPoint), localPoint)
186 ellipse.getCore().scale(scaling)
187 spans = lsst.afw.geom.SpanSet.fromShape(ellipse)
188 footprint = lsst.afw.detection.Footprint(spans.clippedTo(bbox), bbox)
189 footprint.addPeak(localIntPoint.getX(), localIntPoint.getY(),
190 exposure.image._get(localIntPoint, lsst.afw.image.PARENT))
191 record.setFootprint(footprint)
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition SkyWcs.h:118
static std::shared_ptr< geom::SpanSet > fromShape(int r, Stencil s=Stencil::CIRCLE, lsst::geom::Point2I offset=lsst::geom::Point2I())
Factory function for creating SpanSets from a Stencil.
Definition SpanSet.cc:688
An ellipse defined by an arbitrary BaseCore and a center point.
Definition Ellipse.h:51
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition Exposure.h:72
A mapping between the keys of two Schemas, used to copy data between them.
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Definition Source.h:258
Class for storing ordered metadata with comments.
A floating-point coordinate rectangle geometry.
Definition Box.h:413
None run(self, lsst.afw.table.SourceCatalog refCat, lsst.afw.table.SourceCatalog measCat, lsst.afw.image.Exposure exposure, lsst.afw.geom.SkyWcs refWcs, int|None beginOrder=None, int|None endOrder=None)
__init__(self, refSchema, lsst.daf.base.PropertyList algMetadata=None, **kwds)