22 from __future__
import absolute_import, division, print_function
25 import lsst.afw.math
as afwMath
26 import lsst.afw.display.ds9
as ds9
27 import lsst.meas.algorithms
as measAlg
28 import lsst.meas.algorithms.utils
as maUtils
29 import lsst.pex.config
as pexConfig
30 import lsst.pipe.base
as pipeBase
34 starSelector = measAlg.starSelectorRegistry.makeField(
"Star selection algorithm", default=
"objectSize")
35 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
"PSF Determination algorithm", default=
"pca")
36 reserveFraction = pexConfig.Field(
38 doc=
"Fraction of PSF candidates to reserve from fitting; none if <= 0",
41 reserveSeed = pexConfig.Field(
43 doc =
"This number will be multiplied by the exposure ID "
44 "to set the random seed for reserving candidates",
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 --ds9
126 \dontinclude measurePsfTask.py
128 The example also runs SourceDetectionTask and SourceMeasurementTask;
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 --ds9 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_psfCandidate", type=
"Flag",
222 doc=(
"Flag set if the source was a candidate for PSF determination, "
223 "as determined by the star selector.")
226 "calib_psfUsed", 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
231 "calib_psfReserved", type=
"Flag",
232 doc=(
"Flag set if the source was selected as a PSF candidate, but was "
233 "reserved from the PSF fitting."))
237 self.makeSubtask(
"starSelector", schema=schema)
238 self.makeSubtask(
"psfDeterminer", schema=schema)
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")
264 display = lsstDebug.Info(__name__).display
265 displayExposure = lsstDebug.Info(__name__).displayExposure
266 displayPsfMosaic = lsstDebug.Info(__name__).displayPsfMosaic
267 displayPsfCandidates = lsstDebug.Info(__name__).displayPsfCandidates
268 displayResiduals = lsstDebug.Info(__name__).displayResiduals
269 showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
270 normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
276 psfCandidateList = self.starSelector.run(exposure=exposure, sourceCat=sources,
277 matches=matches).psfCandidates
280 if self.config.reserveFraction > 0:
281 random.seed(self.config.reserveSeed*expId)
282 reserveList = random.sample(psfCandidateList,
283 int((self.config.reserveFraction)*len(psfCandidateList)))
285 for cand
in reserveList:
286 psfCandidateList.remove(cand)
289 for cand
in reserveList:
290 source = cand.getSource()
294 for cand
in psfCandidateList:
295 source = cand.getSource()
298 self.log.info(
"PSF star selector found %d candidates" % len(psfCandidateList))
299 if self.config.reserveFraction > 0:
300 self.log.info(
"Reserved %d candidates from the fitting" % len(reserveList))
305 ds9.mtv(exposure, frame=frame, title=
"psf determination")
311 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfCandidateList, self.metadata,
313 self.log.info(
"PSF determination using %d/%d stars." %
314 (self.metadata.get(
"numGoodStars"), self.metadata.get(
"numAvailStars")))
324 if displayPsfCandidates:
330 showBadCandidates=showBadCandidates,
331 normalizeResiduals=normalizeResiduals,
334 maUtils.showPsfMosaic(exposure, psf, frame=frame, showFwhm=
True)
335 ds9.scale(0, 1,
"linear", frame=frame)
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
355 maUtils.showPsfSpatialCells(exposure, cellSet,
356 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
358 for cell
in cellSet.getCellList():
359 for cand
in cell.begin(
not showBadCandidates):
360 status = cand.getStatus()
361 ds9.dot(
'+', *cand.getSource().getCentroid(), frame=frame,
362 ctype=ds9.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
363 ds9.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else ds9.RED)
367 import lsst.afw.display.utils
as displayUtils
370 for cell
in cellSet.getCellList():
371 for cand
in cell.begin(
not showBadCandidates):
373 im = cand.getMaskedImage()
375 chi2 = cand.getChi2()
381 stamps.append((im,
"%d%s" %
382 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
387 mos = displayUtils.Mosaic()
388 for im, label, status
in stamps:
389 im = type(im)(im,
True)
391 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
392 except NotImplementedError:
395 mos.append(im, label,
396 ds9.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else
397 ds9.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else ds9.RED)
400 mos.makeMosaic(frame=frame, title=
"Psf Candidates")
403 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
404 psf = exposure.getPsf()
407 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
408 normalize=normalizeResiduals,
409 showBadCandidates=showBadCandidates)
411 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
412 normalize=normalizeResiduals,
413 showBadCandidates=showBadCandidates,
417 if not showBadCandidates:
418 showBadCandidates =
True
def __init__
Create the detection task.