22__all__ = [
"MeasurePsfConfig",
"MeasurePsfTask"]
31from lsst.utils.timer
import timeMethod
35 starSelector = measAlg.sourceSelectorRegistry.makeField(
36 "Star selection algorithm",
39 makePsfCandidates = pexConfig.ConfigurableField(
40 target=measAlg.MakePsfCandidatesTask,
41 doc=
"Task to make psf candidates from selected stars.",
43 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
44 "PSF Determination algorithm",
47 reserve = pexConfig.ConfigurableField(
48 target=measAlg.ReserveSourcesTask,
49 doc=
"Reserve sources from fitting"
56 msg = (f
"PIFF kernelSize={self.psfDeterminer['piff'].stampSize}"
57 f
" must be >= psf candidate kernelSize={self.makePsfCandidates.kernelSize}.")
58 raise pexConfig.FieldValidationError(MeasurePsfConfig.makePsfCandidates, self, msg)
62 """A task that selects stars from a catalog of sources and uses those to measure the PSF.
66 schema : `lsst.sfw.table.Schema`
69 Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
73 If schema is not None,
'calib_psf_candidate' and 'calib_psf_used' fields will be added to
74 identify which stars were employed
in the PSF estimation.
76 This task can add fields to the schema, so any code calling this task must ensure that
77 these fields are indeed present
in the input table.
79 The star selector
is a subclass of
81 and the PSF determiner
is a sublcass of
84 There
is no establised set of configuration parameters
for these algorithms, so once you start modifying
85 parameters (
as we do
in @ref pipe_tasks_measurePsf_Example) your code
is no longer portable.
92 If
True, display debugging plots
94 display the Exposure + spatialCells
96 show mosaic of candidates
98 Include bad candidates
100 show mosaic of reconstructed PSF(xy)
104 Normalise residuals by object amplitude
106 Additionally you can enable any debug outputs that your chosen star selector
and psf determiner support.
108 ConfigClass = MeasurePsfConfig
109 _DefaultName = "measurePsf"
112 pipeBase.Task.__init__(self, **kwargs)
113 if schema
is not None:
115 "calib_psf_candidate", type=
"Flag",
116 doc=(
"Flag set if the source was a candidate for PSF determination, "
117 "as determined by the star selector.")
120 "calib_psf_used", type=
"Flag",
121 doc=(
"Flag set if the source was actually used for PSF determination, "
122 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
127 self.makeSubtask(
"starSelector")
128 self.makeSubtask(
"makePsfCandidates")
129 self.makeSubtask(
"psfDeterminer", schema=schema)
130 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
131 doc=
"set if source was reserved from PSF determination")
134 def run(self, exposure, sources, expId=0, matches=None):
140 Exposure to process; measured PSF will be added.
142 Measured sources on exposure; flag fields will be set marking
143 stars chosen by the star selector and the PSF determiner
if a schema
144 was passed to the task constructor.
145 expId : `int`, optional
146 Exposure id used
for generating random seed.
147 matches : `list`, optional
152 object respectively)
as returned by
@em e.g. the AstrometryTask.
153 Used by star selectors that choose to refer to an external catalog.
157 measurement : `lsst.pipe.base.Struct`
158 PSF measurement
as a struct
with attributes:
161 The measured PSF (also set
in the input exposure).
164 as returned by the psf determiner.
166 self.log.info("Measuring PSF")
172 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
180 stars = self.starSelector.run(sourceCat=sources, matches=matches, exposure=exposure)
181 selectionResult = self.makePsfCandidates.run(stars.sourceCat, exposure=exposure)
182 self.log.info(
"PSF star selector found %d candidates", len(selectionResult.psfCandidates))
183 reserveResult = self.reserve.run(selectionResult.goodStarCat, expId=expId)
185 psfDeterminerList = [cand
for cand, use
186 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
188 if selectionResult.psfCandidates
and self.
candidateKey is not None:
189 for cand
in selectionResult.psfCandidates:
190 source = cand.getSource()
193 self.log.info(
"Sending %d candidates to PSF determiner", len(psfDeterminerList))
198 disp = afwDisplay.Display(frame=frame)
199 disp.mtv(exposure, title=
"psf determination")
204 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
206 self.log.info(
"PSF determination using %d/%d stars.",
207 self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars"))
214 disp = afwDisplay.Display(frame=frame)
218 if displayPsfCandidates:
224 showBadCandidates=showBadCandidates,
225 normalizeResiduals=normalizeResiduals,
228 disp = afwDisplay.Display(frame=frame)
229 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
230 disp.scale(
"linear", 0, 1)
233 return pipeBase.Struct(
240 """Return True if this task makes use of the "matches" argument to the run method"""
241 return self.starSelector.usesMatches
249 disp = afwDisplay.Display(frame=frame)
250 maUtils.showPsfSpatialCells(exposure, cellSet,
251 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
252 size=4, display=disp)
253 for cell
in cellSet.getCellList():
254 for cand
in cell.begin(
not showBadCandidates):
255 status = cand.getStatus()
256 disp.dot(
'+', *cand.getSource().getCentroid(),
257 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
258 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
263 for cell
in cellSet.getCellList():
264 for cand
in cell.begin(
not showBadCandidates):
266 im = cand.getMaskedImage()
268 chi2 = cand.getChi2()
274 stamps.append((im,
"%d%s" %
275 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
280 mos = afwDisplay.utils.Mosaic()
281 disp = afwDisplay.Display(frame=frame)
282 for im, label, status
in stamps:
283 im =
type(im)(im,
True)
286 except NotImplementedError:
289 mos.append(im, label,
290 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
291 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
294 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
297def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
298 psf = exposure.getPsf()
299 disp = afwDisplay.Display(frame=frame)
302 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
303 normalize=normalizeResiduals,
304 showBadCandidates=showBadCandidates)
306 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
307 normalize=normalizeResiduals,
308 showBadCandidates=showBadCandidates,
312 if not showBadCandidates:
313 showBadCandidates =
True
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A collection of SpatialCells covering an entire image.
Defines the fields and offsets for a table.
Record class that must contain a unique ID field and a celestial coordinate field.
Record class that contains measurements made on a single exposure.
def __init__(self, schema=None, **kwargs)
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1)
def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2)
def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1)
Lightweight representation of a geometric match between two records.