28import lsst.meas.extensions.piff.piffPsfDeterminer
29from lsst.utils.timer
import timeMethod
33 starSelector = measAlg.sourceSelectorRegistry.makeField(
34 "Star selection algorithm",
37 makePsfCandidates = pexConfig.ConfigurableField(
38 target=measAlg.MakePsfCandidatesTask,
39 doc=
"Task to make psf candidates from selected stars.",
41 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
42 "PSF Determination algorithm",
45 reserve = pexConfig.ConfigurableField(
46 target=measAlg.ReserveSourcesTask,
47 doc=
"Reserve sources from fitting"
60@anchor MeasurePsfTask_
64@section pipe_tasks_measurePsf_Contents Contents
66 - @ref pipe_tasks_measurePsf_Purpose
67 - @ref pipe_tasks_measurePsf_Initialize
68 - @ref pipe_tasks_measurePsf_IO
69 - @ref pipe_tasks_measurePsf_Config
70 - @ref pipe_tasks_measurePsf_Debug
71 - @ref pipe_tasks_measurePsf_Example
73@section pipe_tasks_measurePsf_Purpose Description
75A task that selects stars from a catalog of sources and uses those to measure the PSF.
77The star selector is a subclass of
78@ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
79and the PSF determiner is a sublcass of
80@ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
83There is no establised set of configuration parameters for these algorithms, so once you start modifying
84parameters (as we do in @ref pipe_tasks_measurePsf_Example) your code is no longer portable.
86@section pipe_tasks_measurePsf_Initialize Task initialisation
90@section pipe_tasks_measurePsf_IO Invoking the Task
94@section pipe_tasks_measurePsf_Config Configuration parameters
96See
@ref MeasurePsfConfig.
98@section pipe_tasks_measurePsf_Debug Debug variables
100The command line task interface supports a
101flag
@c -d to
import @b debug.py
from your
@c PYTHONPATH; see
102<a href=
"https://pipelines.lsst.io/modules/lsstDebug/">the lsstDebug documentation</a>
103for more about
@b debug.py files.
107 <DD> If
True, display debugging plots
109 <DD> display the Exposure + spatialCells
110 <DT> displayPsfCandidates
111 <DD> show mosaic of candidates
112 <DT> showBadCandidates
113 <DD> Include bad candidates
114 <DT> displayPsfMosaic
115 <DD> show mosaic of reconstructed PSF(xy)
116 <DT> displayResiduals
118 <DT> normalizeResiduals
119 <DD> Normalise residuals by object amplitude
122Additionally you can enable any debug outputs that your chosen star selector
and psf determiner support.
124@section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
126This code
is in `measurePsfTask.py`
in the examples directory,
and can be run
as @em e.g.
128examples/measurePsfTask.py --doDisplay
130@dontinclude measurePsfTask.py
132The example also runs SourceDetectionTask
and SingleFrameMeasurementTask.
134Import the tasks (there are some other standard imports; read the file to see them all):
136@skip SourceDetectionTask
139We need to create the tasks before processing any data
as the task constructor
140can add an extra column to the schema, but first we need an almost-empty
143@skipline makeMinimalSchema
145We can now call the constructors
for the tasks we need to find
and characterize candidate
148@skip SourceDetectionTask.ConfigClass
151Note that we
've chosen a minimal set of measurement plugins: we need the
152outputs of @c base_SdssCentroid,
@c base_SdssShape
and @c base_CircularApertureFlux
as
153inputs to the PSF measurement algorithm,
while @c base_PixelFlags identifies
154and flags bad sources (e.g.
with pixels too close to the edge) so they can be
157Now we can create
and configure the task that we
're interested in:
162We
're now ready to process the data (we could loop over multiple exposures/catalogues using the same
163task objects). First create the output table:
167And process the image:
172We can then unpack
and use the results:
177If you specified
@c --doDisplay you can see the PSF candidates:
184To investigate the
@ref pipe_tasks_measurePsf_Debug, put something like
190 if name ==
"lsst.pipe.tasks.measurePsf" :
192 di.displayExposure =
False
193 di.displayPsfCandidates =
True
194 di.displayPsfMosaic =
True
195 di.displayResiduals =
True
196 di.showBadCandidates =
True
197 di.normalizeResiduals =
False
203into your debug.py file
and run measurePsfTask.py
with the
@c --debug flag.
205 ConfigClass = MeasurePsfConfig
206 _DefaultName = "measurePsf"
209 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
212 @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
214 If schema
is not None,
'calib_psf_candidate' and 'calib_psf_used' fields will be added to
215 identify which stars were employed
in the PSF estimation.
217 @note This task can add fields to the schema, so any code calling this task must ensure that
218 these fields are indeed present
in the input table.
221 pipeBase.Task.__init__(self, **kwargs)
222 if schema
is not None:
224 "calib_psf_candidate", type=
"Flag",
225 doc=(
"Flag set if the source was a candidate for PSF determination, "
226 "as determined by the star selector.")
229 "calib_psf_used", type=
"Flag",
230 doc=(
"Flag set if the source was actually used for PSF determination, "
231 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
236 self.makeSubtask(
"starSelector")
237 self.makeSubtask(
"makePsfCandidates")
238 self.makeSubtask(
"psfDeterminer", schema=schema)
239 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
240 doc=
"set if source was reserved from PSF determination")
243 def run(self, exposure, sources, expId=0, matches=None):
246 @param[
in,out] exposure Exposure to process; measured PSF will be added.
247 @param[
in,out] sources Measured sources on exposure; flag fields will be set marking
248 stars chosen by the star selector
and the PSF determiner
if a schema
249 was passed to the task constructor.
250 @param[
in] expId Exposure id used
for generating random seed.
255 object respectively)
as returned by
@em e.g. the AstrometryTask.
256 Used by star selectors that choose to refer to an external catalog.
258 @return a pipe.base.Struct
with fields:
259 - psf: The measured PSF (also set
in the input exposure)
261 as returned by the psf determiner.
263 self.log.info("Measuring PSF")
269 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
277 stars = self.starSelector.
run(sourceCat=sources, matches=matches, exposure=exposure)
278 selectionResult = self.makePsfCandidates.
run(stars.sourceCat, exposure=exposure)
279 self.log.info(
"PSF star selector found %d candidates", len(selectionResult.psfCandidates))
280 reserveResult = self.reserve.
run(selectionResult.goodStarCat, expId=expId)
282 psfDeterminerList = [cand
for cand, use
283 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
285 if selectionResult.psfCandidates
and self.
candidateKeycandidateKey
is not None:
286 for cand
in selectionResult.psfCandidates:
287 source = cand.getSource()
290 self.log.info(
"Sending %d candidates to PSF determiner", len(psfDeterminerList))
295 disp = afwDisplay.Display(frame=frame)
296 disp.mtv(exposure, title=
"psf determination")
301 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
303 self.log.info(
"PSF determination using %d/%d stars.",
304 self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars"))
311 disp = afwDisplay.Display(frame=frame)
315 if displayPsfCandidates:
321 showBadCandidates=showBadCandidates,
322 normalizeResiduals=normalizeResiduals,
325 disp = afwDisplay.Display(frame=frame)
326 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
327 disp.scale(
"linear", 0, 1)
330 return pipeBase.Struct(
337 """Return True if this task makes use of the "matches" argument to the run method"""
338 return self.starSelector.usesMatches
346 disp = afwDisplay.Display(frame=frame)
347 maUtils.showPsfSpatialCells(exposure, cellSet,
348 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
349 size=4, display=disp)
350 for cell
in cellSet.getCellList():
351 for cand
in cell.begin(
not showBadCandidates):
352 status = cand.getStatus()
353 disp.dot(
'+', *cand.getSource().getCentroid(),
354 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
355 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
360 for cell
in cellSet.getCellList():
361 for cand
in cell.begin(
not showBadCandidates):
363 im = cand.getMaskedImage()
365 chi2 = cand.getChi2()
371 stamps.append((im,
"%d%s" %
372 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
377 mos = afwDisplay.utils.Mosaic()
378 disp = afwDisplay.Display(frame=frame)
379 for im, label, status
in stamps:
380 im = type(im)(im,
True)
382 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
383 except NotImplementedError:
386 mos.append(im, label,
387 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
388 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
391 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
394def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
395 psf = exposure.getPsf()
396 disp = afwDisplay.Display(frame=frame)
399 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
400 normalize=normalizeResiduals,
401 showBadCandidates=showBadCandidates)
403 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
404 normalize=normalizeResiduals,
405 showBadCandidates=showBadCandidates,
409 if not showBadCandidates:
410 showBadCandidates =
True
def run(self, exposure, sources, expId=0, matches=None)
Measure the PSF.
def __init__(self, schema=None, **kwargs)
Create the detection task.
def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1)
def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2)
def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1)