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"
54 msg = (f
"PIFF kernelSize={self.psfDeterminer['piff'].kernelSize}"
55 f
" must be >= psf candidate kernelSize={self.makePsfCandidates.kernelSize}.")
56 raise pexConfig.FieldValidationError(MeasurePsfConfig.makePsfCandidates, self, msg)
68@anchor MeasurePsfTask_
72@section pipe_tasks_measurePsf_Contents Contents
74 - @ref pipe_tasks_measurePsf_Purpose
75 - @ref pipe_tasks_measurePsf_Initialize
76 - @ref pipe_tasks_measurePsf_IO
77 - @ref pipe_tasks_measurePsf_Config
78 - @ref pipe_tasks_measurePsf_Debug
79 - @ref pipe_tasks_measurePsf_Example
81@section pipe_tasks_measurePsf_Purpose Description
83A task that selects stars from a catalog of sources and uses those to measure the PSF.
85The star selector is a subclass of
86@ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
87and the PSF determiner is a sublcass of
88@ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
91There is no establised set of configuration parameters for these algorithms, so once you start modifying
92parameters (as we do in @ref pipe_tasks_measurePsf_Example) your code is no longer portable.
94@section pipe_tasks_measurePsf_Initialize Task initialisation
98@section pipe_tasks_measurePsf_IO Invoking the Task
102@section pipe_tasks_measurePsf_Config Configuration parameters
104See
@ref MeasurePsfConfig.
106@section pipe_tasks_measurePsf_Debug Debug variables
108The command line task interface supports a
109flag
@c -d to
import @b debug.py
from your
@c PYTHONPATH; see
110<a href=
"https://pipelines.lsst.io/modules/lsstDebug/">the lsstDebug documentation</a>
111for more about
@b debug.py files.
115 <DD> If
True, display debugging plots
117 <DD> display the Exposure + spatialCells
118 <DT> displayPsfCandidates
119 <DD> show mosaic of candidates
120 <DT> showBadCandidates
121 <DD> Include bad candidates
122 <DT> displayPsfMosaic
123 <DD> show mosaic of reconstructed PSF(xy)
124 <DT> displayResiduals
126 <DT> normalizeResiduals
127 <DD> Normalise residuals by object amplitude
130Additionally you can enable any debug outputs that your chosen star selector
and psf determiner support.
132@section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
134This code
is in `measurePsfTask.py`
in the examples directory,
and can be run
as @em e.g.
136examples/measurePsfTask.py --doDisplay
138@dontinclude measurePsfTask.py
140The example also runs SourceDetectionTask
and SingleFrameMeasurementTask.
142Import the tasks (there are some other standard imports; read the file to see them all):
144@skip SourceDetectionTask
147We need to create the tasks before processing any data
as the task constructor
148can add an extra column to the schema, but first we need an almost-empty
151@skipline makeMinimalSchema
153We can now call the constructors
for the tasks we need to find
and characterize candidate
156@skip SourceDetectionTask.ConfigClass
159Note that we
've chosen a minimal set of measurement plugins: we need the
160outputs of @c base_SdssCentroid,
@c base_SdssShape
and @c base_CircularApertureFlux
as
161inputs to the PSF measurement algorithm,
while @c base_PixelFlags identifies
162and flags bad sources (e.g.
with pixels too close to the edge) so they can be
165Now we can create
and configure the task that we
're interested in:
170We
're now ready to process the data (we could loop over multiple exposures/catalogues using the same
171task objects). First create the output table:
175And process the image:
180We can then unpack
and use the results:
185If you specified
@c --doDisplay you can see the PSF candidates:
192To investigate the
@ref pipe_tasks_measurePsf_Debug, put something like
198 if name ==
"lsst.pipe.tasks.measurePsf" :
200 di.displayExposure =
False
201 di.displayPsfCandidates =
True
202 di.displayPsfMosaic =
True
203 di.displayResiduals =
True
204 di.showBadCandidates =
True
205 di.normalizeResiduals =
False
211into your debug.py file
and run measurePsfTask.py
with the
@c --debug flag.
213 ConfigClass = MeasurePsfConfig
214 _DefaultName = "measurePsf"
217 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
220 @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
222 If schema
is not None,
'calib_psf_candidate' and 'calib_psf_used' fields will be added to
223 identify which stars were employed
in the PSF estimation.
225 @note This task can add fields to the schema, so any code calling this task must ensure that
226 these fields are indeed present
in the input table.
229 pipeBase.Task.__init__(self, **kwargs)
230 if schema
is not None:
232 "calib_psf_candidate", type=
"Flag",
233 doc=(
"Flag set if the source was a candidate for PSF determination, "
234 "as determined by the star selector.")
237 "calib_psf_used", type=
"Flag",
238 doc=(
"Flag set if the source was actually used for PSF determination, "
239 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
244 self.makeSubtask(
"starSelector")
245 self.makeSubtask(
"makePsfCandidates")
246 self.makeSubtask(
"psfDeterminer", schema=schema)
247 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
248 doc=
"set if source was reserved from PSF determination")
251 def run(self, exposure, sources, expId=0, matches=None):
254 @param[
in,out] exposure Exposure to process; measured PSF will be added.
255 @param[
in,out] sources Measured sources on exposure; flag fields will be set marking
256 stars chosen by the star selector
and the PSF determiner
if a schema
257 was passed to the task constructor.
258 @param[
in] expId Exposure id used
for generating random seed.
263 object respectively)
as returned by
@em e.g. the AstrometryTask.
264 Used by star selectors that choose to refer to an external catalog.
266 @return a pipe.base.Struct
with fields:
267 - psf: The measured PSF (also set
in the input exposure)
269 as returned by the psf determiner.
271 self.log.info("Measuring PSF")
277 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
285 stars = self.starSelector.
run(sourceCat=sources, matches=matches, exposure=exposure)
286 selectionResult = self.makePsfCandidates.
run(stars.sourceCat, exposure=exposure)
287 self.log.
info(
"PSF star selector found %d candidates", len(selectionResult.psfCandidates))
288 reserveResult = self.reserve.
run(selectionResult.goodStarCat, expId=expId)
290 psfDeterminerList = [cand
for cand, use
291 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
293 if selectionResult.psfCandidates
and self.
candidateKeycandidateKey
is not None:
294 for cand
in selectionResult.psfCandidates:
295 source = cand.getSource()
298 self.log.
info(
"Sending %d candidates to PSF determiner", len(psfDeterminerList))
303 disp = afwDisplay.Display(frame=frame)
304 disp.mtv(exposure, title=
"psf determination")
309 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
311 self.log.
info(
"PSF determination using %d/%d stars.",
312 self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars"))
319 disp = afwDisplay.Display(frame=frame)
323 if displayPsfCandidates:
329 showBadCandidates=showBadCandidates,
330 normalizeResiduals=normalizeResiduals,
333 disp = afwDisplay.Display(frame=frame)
334 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
335 disp.scale(
"linear", 0, 1)
338 return pipeBase.Struct(
345 """Return True if this task makes use of the "matches" argument to the run method"""
346 return self.starSelector.usesMatches
354 disp = afwDisplay.Display(frame=frame)
355 maUtils.showPsfSpatialCells(exposure, cellSet,
356 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
357 size=4, display=disp)
358 for cell
in cellSet.getCellList():
359 for cand
in cell.begin(
not showBadCandidates):
360 status = cand.getStatus()
361 disp.dot(
'+', *cand.getSource().getCentroid(),
362 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
363 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
368 for cell
in cellSet.getCellList():
369 for cand
in cell.begin(
not showBadCandidates):
371 im = cand.getMaskedImage()
373 chi2 = cand.getChi2()
379 stamps.append((im,
"%d%s" %
380 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
385 mos = afwDisplay.utils.Mosaic()
386 disp = afwDisplay.Display(frame=frame)
387 for im, label, status
in stamps:
388 im =
type(im)(im,
True)
391 except NotImplementedError:
394 mos.append(im, label,
395 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
396 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
399 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
402def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
403 psf = exposure.getPsf()
404 disp = afwDisplay.Display(frame=frame)
407 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
408 normalize=normalizeResiduals,
409 showBadCandidates=showBadCandidates)
411 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
412 normalize=normalizeResiduals,
413 showBadCandidates=showBadCandidates,
417 if not showBadCandidates:
418 showBadCandidates =
True
A collection of SpatialCells covering an entire image.
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 run(self, exposure, sources, expId=0, matches=None)
Measure the PSF.
def __init__(self, schema=None, **kwargs)
Create the detection task.
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)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
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.