26 import lsst.pex.config
as pexConfig
31 starSelector = measAlg.sourceSelectorRegistry.makeField(
32 "Star selection algorithm",
35 makePsfCandidates = pexConfig.ConfigurableField(
36 target=measAlg.MakePsfCandidatesTask,
37 doc=
"Task to make psf candidates from selected stars.",
39 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
40 "PSF Determination algorithm",
43 reserve = pexConfig.ConfigurableField(
44 target=measAlg.ReserveSourcesTask,
45 doc=
"Reserve sources from fitting"
58 @anchor MeasurePsfTask_
60 @brief Measure the PSF
62 @section pipe_tasks_measurePsf_Contents Contents
64 - @ref pipe_tasks_measurePsf_Purpose
65 - @ref pipe_tasks_measurePsf_Initialize
66 - @ref pipe_tasks_measurePsf_IO
67 - @ref pipe_tasks_measurePsf_Config
68 - @ref pipe_tasks_measurePsf_Debug
69 - @ref pipe_tasks_measurePsf_Example
71 @section pipe_tasks_measurePsf_Purpose Description
73 A task that selects stars from a catalog of sources and uses those to measure the PSF.
75 The star selector is a subclass of
76 @ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
77 and the PSF determiner is a sublcass of
78 @ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
81 There is no establised set of configuration parameters for these algorithms, so once you start modifying
82 parameters (as we do in @ref pipe_tasks_measurePsf_Example) your code is no longer portable.
84 @section pipe_tasks_measurePsf_Initialize Task initialisation
88 @section pipe_tasks_measurePsf_IO Invoking the Task
92 @section pipe_tasks_measurePsf_Config Configuration parameters
94 See @ref MeasurePsfConfig.
96 @section pipe_tasks_measurePsf_Debug Debug variables
98 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
99 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py files.
103 <DD> If True, display debugging plots
105 <DD> display the Exposure + spatialCells
106 <DT> displayPsfCandidates
107 <DD> show mosaic of candidates
108 <DT> showBadCandidates
109 <DD> Include bad candidates
110 <DT> displayPsfMosaic
111 <DD> show mosaic of reconstructed PSF(xy)
112 <DT> displayResiduals
114 <DT> normalizeResiduals
115 <DD> Normalise residuals by object amplitude
118 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support.
120 @section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
122 This code is in @link measurePsfTask.py@endlink in the examples directory, and can be run as @em e.g.
124 examples/measurePsfTask.py --doDisplay
126 @dontinclude measurePsfTask.py
128 The example also runs SourceDetectionTask and SingleFrameMeasurementTask;
129 see @ref meas_algorithms_measurement_Example for more explanation.
131 Import the tasks (there are some other standard imports; read the file to see them all):
133 @skip SourceDetectionTask
134 @until MeasurePsfTask
136 We need to create the tasks before processing any data as the task constructor
137 can add an extra column to the schema, but first we need an almost-empty
140 @skipline makeMinimalSchema
142 We can now call the constructors for the tasks we need to find and characterize candidate
145 @skip SourceDetectionTask.ConfigClass
148 Note that we've chosen a minimal set of measurement plugins: we need the
149 outputs of @c base_SdssCentroid, @c base_SdssShape and @c base_CircularApertureFlux as
150 inputs to the PSF measurement algorithm, while @c base_PixelFlags identifies
151 and flags bad sources (e.g. with pixels too close to the edge) so they can be
154 Now we can create and configure the task that we're interested in:
157 @until measurePsfTask
159 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
160 task objects). First create the output table:
164 And process the image:
169 We can then unpack and use the results:
174 If you specified @c --doDisplay you can see the PSF candidates:
181 To investigate the @ref pipe_tasks_measurePsf_Debug, put something like
185 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
187 if name == "lsst.pipe.tasks.measurePsf" :
189 di.displayExposure = False # display the Exposure + spatialCells
190 di.displayPsfCandidates = True # show mosaic of candidates
191 di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy)
192 di.displayResiduals = True # show residuals
193 di.showBadCandidates = True # Include bad candidates
194 di.normalizeResiduals = False # Normalise residuals by object amplitude
198 lsstDebug.Info = DebugInfo
200 into your debug.py file and run measurePsfTask.py with the @c --debug flag.
202 ConfigClass = MeasurePsfConfig
203 _DefaultName =
"measurePsf"
206 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
208 @param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
209 @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
211 If schema is not None, 'calib_psf_candidate' and 'calib_psf_used' fields will be added to
212 identify which stars were employed in the PSF estimation.
214 @note This task can add fields to the schema, so any code calling this task must ensure that
215 these fields are indeed present in the input table.
218 pipeBase.Task.__init__(self, **kwargs)
219 if schema
is not None:
221 "calib_psf_candidate", type=
"Flag",
222 doc=(
"Flag set if the source was a candidate for PSF determination, "
223 "as determined by the star selector.")
226 "calib_psf_used", type=
"Flag",
227 doc=(
"Flag set if the source was actually used for PSF determination, "
228 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
233 self.makeSubtask(
"starSelector")
234 self.makeSubtask(
"makePsfCandidates")
235 self.makeSubtask(
"psfDeterminer", schema=schema)
236 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
237 doc=
"set if source was reserved from PSF determination")
240 def run(self, exposure, sources, expId=0, matches=None):
243 @param[in,out] exposure Exposure to process; measured PSF will be added.
244 @param[in,out] sources Measured sources on exposure; flag fields will be set marking
245 stars chosen by the star selector and the PSF determiner if a schema
246 was passed to the task constructor.
247 @param[in] expId Exposure id used for generating random seed.
248 @param[in] matches A list of lsst.afw.table.ReferenceMatch objects
249 (@em i.e. of lsst.afw.table.Match
250 with @c first being of type lsst.afw.table.SimpleRecord and @c second
251 type lsst.afw.table.SourceRecord --- the reference object and detected
252 object respectively) as returned by @em e.g. the AstrometryTask.
253 Used by star selectors that choose to refer to an external catalog.
255 @return a pipe.base.Struct with fields:
256 - psf: The measured PSF (also set in the input exposure)
257 - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
258 as returned by the psf determiner.
260 self.log.
info(
"Measuring PSF")
266 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
274 stars = self.starSelector.
run(sourceCat=sources, matches=matches, exposure=exposure)
275 selectionResult = self.makePsfCandidates.
run(stars.sourceCat, exposure=exposure)
276 self.log.
info(
"PSF star selector found %d candidates" % len(selectionResult.psfCandidates))
277 reserveResult = self.reserve.
run(selectionResult.goodStarCat, expId=expId)
279 psfDeterminerList = [cand
for cand, use
280 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
282 if selectionResult.psfCandidates
and self.
candidateKey is not None:
283 for cand
in selectionResult.psfCandidates:
284 source = cand.getSource()
287 self.log.
info(
"Sending %d candidates to PSF determiner" % len(psfDeterminerList))
292 disp = afwDisplay.Display(frame=frame)
293 disp.mtv(exposure, title=
"psf determination")
298 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
300 self.log.
info(
"PSF determination using %d/%d stars." %
301 (self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars")))
308 disp = afwDisplay.Display(frame=frame)
312 if displayPsfCandidates:
318 showBadCandidates=showBadCandidates,
319 normalizeResiduals=normalizeResiduals,
322 disp = afwDisplay.Display(frame=frame)
323 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
324 disp.scale(
"linear", 0, 1)
327 return pipeBase.Struct(
334 """Return True if this task makes use of the "matches" argument to the run method"""
335 return self.starSelector.usesMatches
343 disp = afwDisplay.Display(frame=frame)
344 maUtils.showPsfSpatialCells(exposure, cellSet,
345 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
346 size=4, display=disp)
347 for cell
in cellSet.getCellList():
348 for cand
in cell.begin(
not showBadCandidates):
349 status = cand.getStatus()
350 disp.dot(
'+', *cand.getSource().getCentroid(),
351 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
352 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
357 for cell
in cellSet.getCellList():
358 for cand
in cell.begin(
not showBadCandidates):
360 im = cand.getMaskedImage()
362 chi2 = cand.getChi2()
368 stamps.append((im,
"%d%s" %
369 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
374 mos = afwDisplay.utils.Mosaic()
375 disp = afwDisplay.Display(frame=frame)
376 for im, label, status
in stamps:
377 im =
type(im)(im,
True)
380 except NotImplementedError:
383 mos.append(im, label,
384 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
385 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
388 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
391 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
392 psf = exposure.getPsf()
393 disp = afwDisplay.Display(frame=frame)
396 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
397 normalize=normalizeResiduals,
398 showBadCandidates=showBadCandidates)
400 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
401 normalize=normalizeResiduals,
402 showBadCandidates=showBadCandidates,
406 if not showBadCandidates:
407 showBadCandidates =
True