29 from 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_
62 @brief Measure the PSF
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
75 A task that selects stars from a catalog of sources and uses those to measure the PSF.
77 The star selector is a subclass of
78 @ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask"
79 and the PSF determiner is a sublcass of
80 @ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask"
83 There is no establised set of configuration parameters for these algorithms, so once you start modifying
84 parameters (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
96 See @ref MeasurePsfConfig.
98 @section pipe_tasks_measurePsf_Debug Debug variables
100 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
101 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py files.
105 <DD> If True, display debugging plots
107 <DD> display the Exposure + spatialCells
108 <DT> displayPsfCandidates
109 <DD> show mosaic of candidates
110 <DT> showBadCandidates
111 <DD> Include bad candidates
112 <DT> displayPsfMosaic
113 <DD> show mosaic of reconstructed PSF(xy)
114 <DT> displayResiduals
116 <DT> normalizeResiduals
117 <DD> Normalise residuals by object amplitude
120 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support.
122 @section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask
124 This code is in @link measurePsfTask.py@endlink in the examples directory, and can be run as @em e.g.
126 examples/measurePsfTask.py --doDisplay
128 @dontinclude measurePsfTask.py
130 The example also runs SourceDetectionTask and SingleFrameMeasurementTask;
131 see @ref meas_algorithms_measurement_Example for more explanation.
133 Import the tasks (there are some other standard imports; read the file to see them all):
135 @skip SourceDetectionTask
136 @until MeasurePsfTask
138 We need to create the tasks before processing any data as the task constructor
139 can add an extra column to the schema, but first we need an almost-empty
142 @skipline makeMinimalSchema
144 We can now call the constructors for the tasks we need to find and characterize candidate
147 @skip SourceDetectionTask.ConfigClass
150 Note that we've chosen a minimal set of measurement plugins: we need the
151 outputs of @c base_SdssCentroid, @c base_SdssShape and @c base_CircularApertureFlux as
152 inputs to the PSF measurement algorithm, while @c base_PixelFlags identifies
153 and flags bad sources (e.g. with pixels too close to the edge) so they can be
156 Now we can create and configure the task that we're interested in:
159 @until measurePsfTask
161 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
162 task objects). First create the output table:
166 And process the image:
171 We can then unpack and use the results:
176 If you specified @c --doDisplay you can see the PSF candidates:
183 To investigate the @ref pipe_tasks_measurePsf_Debug, put something like
187 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
189 if name == "lsst.pipe.tasks.measurePsf" :
191 di.displayExposure = False # display the Exposure + spatialCells
192 di.displayPsfCandidates = True # show mosaic of candidates
193 di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy)
194 di.displayResiduals = True # show residuals
195 di.showBadCandidates = True # Include bad candidates
196 di.normalizeResiduals = False # Normalise residuals by object amplitude
200 lsstDebug.Info = DebugInfo
202 into your debug.py file and run measurePsfTask.py with the @c --debug flag.
204 ConfigClass = MeasurePsfConfig
205 _DefaultName =
"measurePsf"
208 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
210 @param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
211 @param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
213 If schema is not None, 'calib_psf_candidate' and 'calib_psf_used' fields will be added to
214 identify which stars were employed in the PSF estimation.
216 @note This task can add fields to the schema, so any code calling this task must ensure that
217 these fields are indeed present in the input table.
220 pipeBase.Task.__init__(self, **kwargs)
221 if schema
is not None:
223 "calib_psf_candidate", type=
"Flag",
224 doc=(
"Flag set if the source was a candidate for PSF determination, "
225 "as determined by the star selector.")
228 "calib_psf_used", type=
"Flag",
229 doc=(
"Flag set if the source was actually used for PSF determination, "
230 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
235 self.makeSubtask(
"starSelector")
236 self.makeSubtask(
"makePsfCandidates")
237 self.makeSubtask(
"psfDeterminer", schema=schema)
238 self.makeSubtask(
"reserve", columnName=
"calib_psf", schema=schema,
239 doc=
"set if source was reserved from PSF determination")
242 def run(self, exposure, sources, expId=0, matches=None):
245 @param[in,out] exposure Exposure to process; measured PSF will be added.
246 @param[in,out] sources Measured sources on exposure; flag fields will be set marking
247 stars chosen by the star selector and the PSF determiner if a schema
248 was passed to the task constructor.
249 @param[in] expId Exposure id used for generating random seed.
250 @param[in] matches A list of lsst.afw.table.ReferenceMatch objects
251 (@em i.e. of lsst.afw.table.Match
252 with @c first being of type lsst.afw.table.SimpleRecord and @c second
253 type lsst.afw.table.SourceRecord --- the reference object and detected
254 object respectively) as returned by @em e.g. the AstrometryTask.
255 Used by star selectors that choose to refer to an external catalog.
257 @return a pipe.base.Struct with fields:
258 - psf: The measured PSF (also set in the input exposure)
259 - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
260 as returned by the psf determiner.
262 self.log.
info(
"Measuring PSF")
268 displayPsfCandidates =
lsstDebug.Info(__name__).displayPsfCandidates
276 stars = self.starSelector.
run(sourceCat=sources, matches=matches, exposure=exposure)
277 selectionResult = self.makePsfCandidates.
run(stars.sourceCat, exposure=exposure)
278 self.log.
info(
"PSF star selector found %d candidates", len(selectionResult.psfCandidates))
279 reserveResult = self.reserve.
run(selectionResult.goodStarCat, expId=expId)
281 psfDeterminerList = [cand
for cand, use
282 in zip(selectionResult.psfCandidates, reserveResult.use)
if use]
284 if selectionResult.psfCandidates
and self.
candidateKeycandidateKey
is not None:
285 for cand
in selectionResult.psfCandidates:
286 source = cand.getSource()
289 self.log.
info(
"Sending %d candidates to PSF determiner", len(psfDeterminerList))
294 disp = afwDisplay.Display(frame=frame)
295 disp.mtv(exposure, title=
"psf determination")
300 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfDeterminerList, self.metadata,
302 self.log.
info(
"PSF determination using %d/%d stars.",
303 self.metadata.getScalar(
"numGoodStars"), self.metadata.getScalar(
"numAvailStars"))
310 disp = afwDisplay.Display(frame=frame)
314 if displayPsfCandidates:
320 showBadCandidates=showBadCandidates,
321 normalizeResiduals=normalizeResiduals,
324 disp = afwDisplay.Display(frame=frame)
325 maUtils.showPsfMosaic(exposure, psf, display=disp, showFwhm=
True)
326 disp.scale(
"linear", 0, 1)
329 return pipeBase.Struct(
336 """Return True if this task makes use of the "matches" argument to the run method"""
337 return self.starSelector.usesMatches
345 disp = afwDisplay.Display(frame=frame)
346 maUtils.showPsfSpatialCells(exposure, cellSet,
347 symb=
"o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW,
348 size=4, display=disp)
349 for cell
in cellSet.getCellList():
350 for cand
in cell.begin(
not showBadCandidates):
351 status = cand.getStatus()
352 disp.dot(
'+', *cand.getSource().getCentroid(),
353 ctype=afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
354 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
359 for cell
in cellSet.getCellList():
360 for cand
in cell.begin(
not showBadCandidates):
362 im = cand.getMaskedImage()
364 chi2 = cand.getChi2()
370 stamps.append((im,
"%d%s" %
371 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
376 mos = afwDisplay.utils.Mosaic()
377 disp = afwDisplay.Display(frame=frame)
378 for im, label, status
in stamps:
379 im =
type(im)(im,
True)
382 except NotImplementedError:
385 mos.append(im, label,
386 afwDisplay.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
387 afwDisplay.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else afwDisplay.RED)
390 disp.mtv(mos.makeMosaic(), title=
"Psf Candidates")
393 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
394 psf = exposure.getPsf()
395 disp = afwDisplay.Display(frame=frame)
398 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
399 normalize=normalizeResiduals,
400 showBadCandidates=showBadCandidates)
402 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, display=disp,
403 normalize=normalizeResiduals,
404 showBadCandidates=showBadCandidates,
408 if not showBadCandidates:
409 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)