22 from __future__
import absolute_import, division, print_function
24 import lsst.afw.math
as afwMath
25 import lsst.afw.display.ds9
as ds9
26 import lsst.meas.algorithms
as measAlg
27 import lsst.meas.algorithms.utils
as maUtils
28 import lsst.pex.config
as pexConfig
29 import lsst.pipe.base
as pipeBase
33 starSelector = measAlg.starSelectorRegistry.makeField(
"Star selection algorithm", default=
"objectSize")
34 psfDeterminer = measAlg.psfDeterminerRegistry.makeField(
"PSF Determination algorithm", default=
"pca")
35 reserveFraction = pexConfig.Field(
37 doc=
"Fraction of PSF candidates to reserve from fitting; none if <= 0",
40 reserveSeed = pexConfig.Field(
42 doc =
"This number will be multiplied by the exposure ID " 43 "to set the random seed for reserving candidates",
57 \anchor MeasurePsfTask_ 59 \brief Measure the PSF 61 \section pipe_tasks_measurePsf_Contents Contents 63 - \ref pipe_tasks_measurePsf_Purpose 64 - \ref pipe_tasks_measurePsf_Initialize 65 - \ref pipe_tasks_measurePsf_IO 66 - \ref pipe_tasks_measurePsf_Config 67 - \ref pipe_tasks_measurePsf_Debug 68 - \ref pipe_tasks_measurePsf_Example 70 \section pipe_tasks_measurePsf_Purpose Description 72 A task that selects stars from a catalog of sources and uses those to measure the PSF. 74 The star selector is a subclass of 75 \ref lsst.meas.algorithms.starSelector.BaseStarSelectorTask "lsst.meas.algorithms.BaseStarSelectorTask" 76 and the PSF determiner is a sublcass of 77 \ref lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask "lsst.meas.algorithms.BasePsfDeterminerTask" 80 There is no establised set of configuration parameters for these algorithms, so once you start modifying 81 parameters (as we do in \ref pipe_tasks_measurePsf_Example) your code is no longer portable. 83 \section pipe_tasks_measurePsf_Initialize Task initialisation 87 \section pipe_tasks_measurePsf_IO Invoking the Task 91 \section pipe_tasks_measurePsf_Config Configuration parameters 93 See \ref MeasurePsfConfig. 95 \section pipe_tasks_measurePsf_Debug Debug variables 97 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a 98 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files. 102 <DD> If True, display debugging plots 104 <DD> display the Exposure + spatialCells 105 <DT> displayPsfCandidates 106 <DD> show mosaic of candidates 107 <DT> showBadCandidates 108 <DD> Include bad candidates 109 <DT> displayPsfMosaic 110 <DD> show mosaic of reconstructed PSF(xy) 111 <DT> displayResiduals 113 <DT> normalizeResiduals 114 <DD> Normalise residuals by object amplitude 117 Additionally you can enable any debug outputs that your chosen star selector and psf determiner support. 119 \section pipe_tasks_measurePsf_Example A complete example of using MeasurePsfTask 121 This code is in \link measurePsfTask.py\endlink in the examples directory, and can be run as \em e.g. 123 examples/measurePsfTask.py --ds9 125 \dontinclude measurePsfTask.py 127 The example also runs SourceDetectionTask and SourceMeasurementTask; 128 see \ref meas_algorithms_measurement_Example for more explanation. 130 Import the tasks (there are some other standard imports; read the file to see them all): 132 \skip SourceDetectionTask 133 \until MeasurePsfTask 135 We need to create the tasks before processing any data as the task constructor 136 can add an extra column to the schema, but first we need an almost-empty 139 \skipline makeMinimalSchema 141 We can now call the constructors for the tasks we need to find and characterize candidate 144 \skip SourceDetectionTask.ConfigClass 147 Note that we've chosen a minimal set of measurement plugins: we need the 148 outputs of \c base_SdssCentroid, \c base_SdssShape and \c base_CircularApertureFlux as 149 inputs to the PSF measurement algorithm, while \c base_PixelFlags identifies 150 and flags bad sources (e.g. with pixels too close to the edge) so they can be 153 Now we can create and configure the task that we're interested in: 156 \until measurePsfTask 158 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same 159 task objects). First create the output table: 163 And process the image: 168 We can then unpack and use the results: 173 If you specified \c --ds9 you can see the PSF candidates: 180 To investigate the \ref pipe_tasks_measurePsf_Debug, put something like 184 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 186 if name == "lsst.pipe.tasks.measurePsf" : 188 di.displayExposure = False # display the Exposure + spatialCells 189 di.displayPsfCandidates = True # show mosaic of candidates 190 di.displayPsfMosaic = True # show mosaic of reconstructed PSF(xy) 191 di.displayResiduals = True # show residuals 192 di.showBadCandidates = True # Include bad candidates 193 di.normalizeResiduals = False # Normalise residuals by object amplitude 197 lsstDebug.Info = DebugInfo 199 into your debug.py file and run measurePsfTask.py with the \c --debug flag. 201 ConfigClass = MeasurePsfConfig
202 _DefaultName =
"measurePsf" 205 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task. 207 \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog 208 \param **kwargs Keyword arguments passed to lsst.pipe.base.task.Task.__init__. 210 If schema is not None, 'calib.psf.candidate' and 'calib.psf.used' fields will be added to 211 identify which stars were employed in the PSF estimation. 213 \note This task can add fields to the schema, so any code calling this task must ensure that 214 these fields are indeed present in the input table. 217 pipeBase.Task.__init__(self, **kwargs)
218 if schema
is not None:
220 "calib_psfCandidate", type=
"Flag",
221 doc=(
"Flag set if the source was a candidate for PSF determination, " 222 "as determined by the star selector.")
225 "calib_psfUsed", type=
"Flag",
226 doc=(
"Flag set if the source was actually used for PSF determination, " 227 "as determined by the '%s' PSF determiner.") % self.config.psfDeterminer.name
230 "calib_psfReserved", type=
"Flag",
231 doc=(
"Flag set if the source was selected as a PSF candidate, but was " 232 "reserved from the PSF fitting."))
236 self.makeSubtask(
"starSelector", schema=schema)
237 self.makeSubtask(
"psfDeterminer", schema=schema)
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")
263 display = lsstDebug.Info(__name__).display
264 displayExposure = lsstDebug.Info(__name__).displayExposure
265 displayPsfMosaic = lsstDebug.Info(__name__).displayPsfMosaic
266 displayPsfCandidates = lsstDebug.Info(__name__).displayPsfCandidates
267 displayResiduals = lsstDebug.Info(__name__).displayResiduals
268 showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
269 normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
275 psfCandidateList = self.starSelector.
run(exposure=exposure, sourceCat=sources,
276 matches=matches).psfCandidates
279 if self.config.reserveFraction > 0:
281 random = afwMath.Random(seed=self.config.reserveSeed*(expId
if expId
else 1))
283 n = len(psfCandidateList)
284 for i
in range(int(n*self.config.reserveFraction)):
285 index = random.uniformInt(n)
287 candidate = psfCandidateList[index]
288 psfCandidateList.remove(candidate)
289 reserveList.append(candidate)
292 for cand
in reserveList:
293 source = cand.getSource()
297 for cand
in psfCandidateList:
298 source = cand.getSource()
301 self.log.info(
"PSF star selector found %d candidates" % len(psfCandidateList))
302 if self.config.reserveFraction > 0:
303 self.log.info(
"Reserved %d candidates from the fitting" % len(reserveList))
308 ds9.mtv(exposure, frame=frame, title=
"psf determination")
314 psf, cellSet = self.psfDeterminer.determinePsf(exposure, psfCandidateList, self.metadata,
316 self.log.info(
"PSF determination using %d/%d stars." %
317 (self.metadata.get(
"numGoodStars"), self.metadata.get(
"numAvailStars")))
327 if displayPsfCandidates:
333 showBadCandidates=showBadCandidates,
334 normalizeResiduals=normalizeResiduals,
337 maUtils.showPsfMosaic(exposure, psf, frame=frame, showFwhm=
True)
338 ds9.scale(0, 1,
"linear", frame=frame)
341 return pipeBase.Struct(
348 """Return True if this task makes use of the "matches" argument to the run method""" 349 return self.starSelector.usesMatches
358 maUtils.showPsfSpatialCells(exposure, cellSet,
359 symb=
"o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW,
361 for cell
in cellSet.getCellList():
362 for cand
in cell.begin(
not showBadCandidates):
363 status = cand.getStatus()
364 ds9.dot(
'+', *cand.getSource().getCentroid(), frame=frame,
365 ctype=ds9.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else 366 ds9.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else ds9.RED)
370 import lsst.afw.display.utils
as displayUtils
373 for cell
in cellSet.getCellList():
374 for cand
in cell.begin(
not showBadCandidates):
376 im = cand.getMaskedImage()
378 chi2 = cand.getChi2()
384 stamps.append((im,
"%d%s" %
385 (maUtils.splitId(cand.getSource().getId(),
True)[
"objId"], chi2),
390 mos = displayUtils.Mosaic()
391 for im, label, status
in stamps:
392 im = type(im)(im,
True)
394 im /= afwMath.makeStatistics(im, afwMath.MAX).getValue()
395 except NotImplementedError:
398 mos.append(im, label,
399 ds9.GREEN
if status == afwMath.SpatialCellCandidate.GOOD
else 400 ds9.YELLOW
if status == afwMath.SpatialCellCandidate.UNKNOWN
else ds9.RED)
403 mos.makeMosaic(frame=frame, title=
"Psf Candidates")
406 def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2):
407 psf = exposure.getPsf()
410 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
411 normalize=normalizeResiduals,
412 showBadCandidates=showBadCandidates)
414 maUtils.showPsfCandidates(exposure, cellSet, psf=psf, frame=frame,
415 normalize=normalizeResiduals,
416 showBadCandidates=showBadCandidates,
420 if not showBadCandidates:
421 showBadCandidates =
True def run(self, exposure, sources, expId=0, matches=None)
Measure the PSF.
def plotPsfCandidates(cellSet, showBadCandidates=False, frame=1)
def plotResiduals(exposure, cellSet, showBadCandidates=False, normalizeResiduals=True, frame=2)
def showPsfSpatialCells(exposure, cellSet, showBadCandidates, frame=1)
def __init__(self, schema=None, kwargs)
Create the detection task.