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