32 starSelector = measAlg.sourceSelectorRegistry.makeField(
33 "Star selection algorithm",
36 makePsfCandidates = pexConfig.ConfigurableField(
37 target=measAlg.MakePsfCandidatesTask,
38 doc=
"Task to make psf candidates from selected stars.",
40 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
41 "PSF Determination algorithm",
44 reserve = pexConfig.ConfigurableField(
45 target=measAlg.ReserveSourcesTask,
46 doc=
"Reserve sources from fitting"
59 @anchor MeasurePsfTask_
61 @brief Measure the PSF
63 @section pipe_tasks_measurePsf_Contents Contents
65 - @ref pipe_tasks_measurePsf_Purpose
66 - @ref pipe_tasks_measurePsf_Initialize
67 - @ref pipe_tasks_measurePsf_IO
68 - @ref pipe_tasks_measurePsf_Config
69 - @ref pipe_tasks_measurePsf_Debug
70 - @ref pipe_tasks_measurePsf_Example
72 @section pipe_tasks_measurePsf_Purpose Description
74 A task that selects stars from a catalog of sources and uses those to measure the PSF.
76 The star selector is a subclass of
77 @ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
78 and the PSF determiner is a sublcass of
79 @ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
82 There is no establised set of configuration parameters for these algorithms, so once you start modifying
83 parameters (as we do in @ref pipe_tasks_measurePsf_Example) your code is no longer portable.
85 @section pipe_tasks_measurePsf_Initialize Task initialisation
89 @section pipe_tasks_measurePsf_IO Invoking the Task
93 @section pipe_tasks_measurePsf_Config Configuration parameters
95 See @ref MeasurePsfConfig.
97 @section pipe_tasks_measurePsf_Debug Debug variables
99 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
100 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py files.
104 <DD> If True, display debugging plots
106 <DD> display the Exposure + spatialCells
107 <DT> displayPsfCandidates
108 <DD> show mosaic of candidates
109 <DT> showBadCandidates
110 <DD> Include bad candidates
111 <DT> displayPsfMosaic
112 <DD> show mosaic of reconstructed PSF(xy)
113 <DT> displayResiduals
115 <DT> normalizeResiduals
116 <DD> Normalise residuals by object amplitude
119 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support.
121 @section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
123 This code is in @link measurePsfTask.py@endlink in the examples directory, and can be run as @em e.g.
125 examples/measurePsfTask.py --doDisplay
127 @dontinclude measurePsfTask.py
129 The example also runs SourceDetectionTask and SingleFrameMeasurementTask;
130 see @ref meas_algorithms_measurement_Example for more explanation.
132 Import the tasks (there are some other standard imports; read the file to see them all):
134 @skip SourceDetectionTask
135 @until MeasurePsfTask
137 We need to create the tasks before processing any data as the task constructor
138 can add an extra column to the schema, but first we need an almost-empty
141 @skipline makeMinimalSchema
143 We can now call the constructors for the tasks we need to find and characterize candidate
146 @skip SourceDetectionTask.ConfigClass
149 Note that we've chosen a minimal set of measurement plugins: we need the
150 outputs of @c base_SdssCentroid, @c base_SdssShape and @c base_CircularApertureFlux as
151 inputs to the PSF measurement algorithm, while @c base_PixelFlags identifies
152 and flags bad sources (e.g. with pixels too close to the edge) so they can be
155 Now we can create and configure the task that we're interested in:
158 @until measurePsfTask
160 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
161 task objects). First create the output table:
165 And process the image:
170 We can then unpack and use the results:
175 If you specified @c --doDisplay you can see the PSF candidates:
182 To investigate the @ref pipe_tasks_measurePsf_Debug, put something like
186 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
188 if name == "lsst.pipe.tasks.measurePsf" :
190 di.displayExposure = False # display the Exposure + spatialCells
191 di.displayPsfCandidates = True # show mosaic of candidates
192 di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy)
193 di.displayResiduals = True # show residuals
194 di.showBadCandidates = True # Include bad candidates
195 di.normalizeResiduals = False # Normalise residuals by object amplitude
199 lsstDebug.Info = DebugInfo
201 into your debug.py file and run measurePsfTask.py with the @c --debug flag.
203 ConfigClass = MeasurePsfConfig
204 _DefaultName =
"measurePsf"
207 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
209 @param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
210 @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
212 If schema is not None, 'calib_psf_candidate' and 'calib_psf_used' fields will be added to
213 identify which stars were employed in the PSF estimation.
215 @note This task can add fields to the schema, so any code calling this task must ensure that
216 these fields are indeed present in the input table.
219 pipeBase.Task.__init__(self, **kwargs)
220 if schema
is not None:
222 "calib_psf_candidate", type=
"Flag",
223 doc=(
"Flag set if the source was a candidate for PSF determination, "
224 "as determined by the star selector.")
227 "calib_psf_used", type=
"Flag",
228 doc=(
"Flag set if the source was actually used for PSF determination, "
229 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
234 self.makeSubtask(
"starSelector")
235 self.makeSubtask(
"makePsfCandidates")
236 self.makeSubtask(
"psfDeterminer", schema=schema)
237 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
238 doc=
"set if source was reserved from PSF determination")
241 def run(self, exposure, sources, expId=0, matches=None):
244 @param[in,out] exposure Exposure to process; measured PSF will be added.
245 @param[in,out] sources Measured sources on exposure; flag fields will be set marking
246 stars chosen by the star selector and the PSF determiner if a schema
247 was passed to the task constructor.
248 @param[in] expId Exposure id used for generating random seed.
249 @param[in] matches A list of lsst.afw.table.ReferenceMatch objects
250 (@em i.e. of lsst.afw.table.Match
251 with @c first being of type lsst.afw.table.SimpleRecord and @c second
252 type lsst.afw.table.SourceRecord --- the reference object and detected
253 object respectively) as returned by @em e.g. the AstrometryTask.
254 Used by star selectors that choose to refer to an external catalog.
256 @return a pipe.base.Struct with fields:
257 - psf: The measured PSF (also set in the input exposure)
258 - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
259 as returned by the psf determiner.
261 self.log.
info(
"Measuring PSF")
267 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
275 stars = self.starSelector.
run(sourceCat=sources, matches=matches, exposure=exposure)
276 selectionResult = self.makePsfCandidates.
run(stars.sourceCat, exposure=exposure)
277 self.log.
info(
"PSF star selector found %d candidates", len(selectionResult.psfCandidates))
278 reserveResult = self.reserve.
run(selectionResult.goodStarCat, expId=expId)
280 psfDeterminerList = [cand
for cand, use
281 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
283 if selectionResult.psfCandidates
and self.
candidateKeycandidateKey
is not None:
284 for cand
in selectionResult.psfCandidates:
285 source = cand.getSource()
288 self.log.
info(
"Sending %d candidates to PSF determiner", len(psfDeterminerList))
293 disp = afwDisplay.Display(frame=frame)
294 disp.mtv(exposure, title=
"psf determination")
299 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
301 self.log.
info(
"PSF determination using %d/%d stars.",
302 self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars"))
309 disp = afwDisplay.Display(frame=frame)
313 if displayPsfCandidates:
319 showBadCandidates=showBadCandidates,
320 normalizeResiduals=normalizeResiduals,
323 disp = afwDisplay.Display(frame=frame)
324 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
325 disp.scale(
"linear", 0, 1)
328 return pipeBase.Struct(
335 """Return True if this task makes use of the "matches" argument to the run method"""
336 return self.starSelector.usesMatches
344 disp = afwDisplay.Display(frame=frame)
345 maUtils.showPsfSpatialCells(exposure, cellSet,
346 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
347 size=4, display=disp)
348 for cell
in cellSet.getCellList():
349 for cand
in cell.begin(
not showBadCandidates):
350 status = cand.getStatus()
351 disp.dot(
'+', *cand.getSource().getCentroid(),
352 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
353 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
358 for cell
in cellSet.getCellList():
359 for cand
in cell.begin(
not showBadCandidates):
361 im = cand.getMaskedImage()
363 chi2 = cand.getChi2()
369 stamps.append((im,
"%d%s" %
370 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
375 mos = afwDisplay.utils.Mosaic()
376 disp = afwDisplay.Display(frame=frame)
377 for im, label, status
in stamps:
378 im =
type(im)(im,
True)
381 except NotImplementedError:
384 mos.append(im, label,
385 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
386 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
389 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
392 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
393 psf = exposure.getPsf()
394 disp = afwDisplay.Display(frame=frame)
397 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
398 normalize=normalizeResiduals,
399 showBadCandidates=showBadCandidates)
401 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
402 normalize=normalizeResiduals,
403 showBadCandidates=showBadCandidates,
407 if not showBadCandidates:
408 showBadCandidates =
True
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)