LSST Applications g0fba68d861+2e894914a0,g1ec0fe41b4+e220e2fb2f,g1f759649c2+d3ce33c3e0,g1fd858c14a+2b9bf32e51,g35bb328faa+fcb1d3bbc8,g4d2262a081+1dc91b7776,g53246c7159+fcb1d3bbc8,g56a49b3a55+1053ce1741,g60b5630c4e+d3ce33c3e0,g67b6fd64d1+fad15079a7,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g8180f54f50+9253e245c2,g8352419a5c+fcb1d3bbc8,g8852436030+f11a5d3b0b,g89139ef638+fad15079a7,g9125e01d80+fcb1d3bbc8,g94187f82dc+d3ce33c3e0,g989de1cb63+fad15079a7,g9ccd5d7f00+44d9ee3d90,g9d31334357+d3ce33c3e0,g9f33ca652e+9a8c17f5f6,gabe3b4be73+1e0a283bba,gabf8522325+94c30d56e9,gb1101e3267+90933e15fb,gb58c049af0+f03b321e39,gb89ab40317+fad15079a7,gc0af124501+26f6120d90,gcf25f946ba+f11a5d3b0b,gd6cbbdb0b4+8d7f1baacb,gd794735e4e+4bba874dfe,gdb1c4ca869+16879ca1a6,gde0f65d7ad+0609b2c34e,ge278dab8ac+4d6e48c014,ge410e46f29+fad15079a7,gf5e32f922b+fcb1d3bbc8,gf618743f1b+dd10d22602,gf67bdafdda+fad15079a7,w.2025.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask Class Reference
Inheritance diagram for lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask:
lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask

Public Member Functions

 __init__ (self, config, schema=None, **kwds)
 
 determinePsf (self, exposure, psfCandidateList, metadata=None, flagKey=None)
 
 downsampleCandidates (self, inputCandidateList)
 

Public Attributes

 piffLogger = lsst.utils.logging.getLogger(f"{self.log.name}.piff")
 

Static Public Attributes

bool usesMatches = False
 
 ConfigClass = BasePsfDeterminerConfig
 

Protected Attributes

 _piffConfig = piffConfig
 

Static Protected Attributes

str _DefaultName = "psfDeterminer"
 

Detailed Description

A measurePsfTask PSF estimator using Piff as the implementation.

Definition at line 394 of file piffPsfDeterminer.py.

Constructor & Destructor Documentation

◆ __init__()

lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask.__init__ ( self,
config,
schema = None,
** kwds )

Definition at line 400 of file piffPsfDeterminer.py.

400 def __init__(self, config, schema=None, **kwds):
401 BasePsfDeterminerTask.__init__(self, config, schema=schema, **kwds)
402
403 piffLoggingLevels = {
404 0: logging.CRITICAL,
405 1: logging.WARNING,
406 2: logging.INFO,
407 3: logging.DEBUG,
408 }
409 self.piffLogger = lsst.utils.logging.getLogger(f"{self.log.name}.piff")
410 self.piffLogger.setLevel(piffLoggingLevels[self.config.piffLoggingLevel])
411

Member Function Documentation

◆ determinePsf()

lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask.determinePsf ( self,
exposure,
psfCandidateList,
metadata = None,
flagKey = None )
Determine a Piff PSF model for an exposure given a list of PSF
candidates.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
   Exposure containing the PSF candidates.
psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
   A sequence of PSF candidates typically obtained by detecting sources
   and then running them through a star selector.
metadata : `lsst.daf.base import PropertyList` or `None`, optional
   A home for interesting tidbits of information.
flagKey : `str` or `None`, optional
   Schema key used to mark sources actually used in PSF determination.

Returns
-------
psf : `lsst.meas.extensions.piff.PiffPsf`
   The measured PSF model.
psfCellSet : `None`
   Unused by this PsfDeterminer.

Reimplemented from lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask.

Definition at line 412 of file piffPsfDeterminer.py.

414 ):
415 """Determine a Piff PSF model for an exposure given a list of PSF
416 candidates.
417
418 Parameters
419 ----------
420 exposure : `lsst.afw.image.Exposure`
421 Exposure containing the PSF candidates.
422 psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
423 A sequence of PSF candidates typically obtained by detecting sources
424 and then running them through a star selector.
425 metadata : `lsst.daf.base import PropertyList` or `None`, optional
426 A home for interesting tidbits of information.
427 flagKey : `str` or `None`, optional
428 Schema key used to mark sources actually used in PSF determination.
429
430 Returns
431 -------
432 psf : `lsst.meas.extensions.piff.PiffPsf`
433 The measured PSF model.
434 psfCellSet : `None`
435 Unused by this PsfDeterminer.
436 """
437 psfCandidateList = self.downsampleCandidates(psfCandidateList)
438
439 if self.config.stampSize:
440 stampSize = self.config.stampSize
441 if stampSize > psfCandidateList[0].getWidth():
442 self.log.warning("stampSize is larger than the PSF candidate size. Using candidate size.")
443 stampSize = psfCandidateList[0].getWidth()
444 else: # TODO: Only the if block should stay after DM-36311
445 self.log.debug("stampSize not set. Using candidate size.")
446 stampSize = psfCandidateList[0].getWidth()
447
448 scale = exposure.getWcs().getPixelScale(exposure.getBBox().getCenter()).asArcseconds()
449
450 match self.config.useCoordinates:
451 case 'field':
452 detector = exposure.getDetector()
453 pix_to_field = detector.getTransform(PIXELS, FIELD_ANGLE)
454 gswcs = UVWcsWrapper(pix_to_field)
455 pointing = None
456
457 case 'sky':
458 gswcs = CelestialWcsWrapper(exposure.getWcs())
459 skyOrigin = exposure.getWcs().getSkyOrigin()
460 ra = skyOrigin.getLongitude().asDegrees()
461 dec = skyOrigin.getLatitude().asDegrees()
462 pointing = galsim.CelestialCoord(
463 ra*galsim.degrees,
464 dec*galsim.degrees
465 )
466
467 case 'pixel':
468 gswcs = galsim.PixelScale(scale)
469 pointing = None
470
471 stars = []
472 for candidate in psfCandidateList:
473 cmi = candidate.getMaskedImage(stampSize, stampSize)
474 good = getGoodPixels(cmi, self.config.zeroWeightMaskBits)
475 fracGood = np.sum(good)/good.size
476 if fracGood < self.config.minimumUnmaskedFraction:
477 continue
478 weight = computeWeight(cmi, self.config.maxSNR, good)
479
480 bbox = cmi.getBBox()
481 bds = galsim.BoundsI(
482 galsim.PositionI(*bbox.getMin()),
483 galsim.PositionI(*bbox.getMax())
484 )
485 gsImage = galsim.Image(bds, wcs=gswcs, dtype=float)
486 gsImage.array[:] = cmi.image.array
487 gsWeight = galsim.Image(bds, wcs=gswcs, dtype=float)
488 gsWeight.array[:] = weight
489
490 source = candidate.getSource()
491 starId = source.getId()
492 image_pos = galsim.PositionD(source.getX(), source.getY())
493
494 data = piff.StarData(
495 gsImage,
496 image_pos,
497 weight=gsWeight,
498 pointing=pointing
499 )
500 star = piff.Star(data, None)
501 star.data.properties['starId'] = starId
502 stars.append(star)
503
504 if self.config.piffPsfConfigYaml is None:
505 # The following is mostly accommodating unittests that don't have
506 # the filter attribute set on the mock exposure.
507 if hasattr(exposure.filter, "bandLabel"):
508 band = exposure.filter.bandLabel
509 else:
510 band = None
511 spatialOrder = self.config.spatialOrderPerBand.get(band, self.config.spatialOrder)
512 piffConfig = {
513 'type': 'Simple',
514 'model': {
515 'type': 'PixelGrid',
516 'scale': scale * self.config.samplingSize,
517 'size': self.config.modelSize,
518 'interp': self.config.interpolant,
519 'centered': self.config.piffPixelGridFitCenter,
520 },
521 'interp': {
522 'type': 'BasisPolynomial',
523 'order': spatialOrder,
524 'solver': self.config.piffBasisPolynomialSolver,
525 },
526 'outliers': {
527 'type': 'Chisq',
528 'nsigma': self.config.outlierNSigma,
529 'max_remove': self.config.outlierMaxRemove,
530 },
531 'max_iter': self.config.piffMaxIter
532 }
533 else:
534 piffConfig = yaml.safe_load(self.config.piffPsfConfigYaml)
535
536 def _get_threshold(nth_order):
537 # number of free parameter in the polynomial interpolation
538 freeParameters = ((nth_order + 1) * (nth_order + 2)) // 2
539 return freeParameters
540
541 if piffConfig['interp']['type'] in ['BasisPolynomial', 'Polynomial']:
542 threshold = _get_threshold(piffConfig['interp']['order'])
543 if len(stars) < threshold:
544 if self.config.zerothOrderInterpNotEnoughStars:
545 self.log.warning(
546 "Only %d stars found, "
547 "but %d required. Using zeroth order interpolation."%((len(stars), threshold))
548 )
549 piffConfig['interp']['order'] = 0
550 # No need to do any outlier rejection assume
551 # PSF to be average of few stars.
552 piffConfig['max_iter'] = 1
553 else:
554 raise PiffTooFewGoodStarsError(
555 num_good_stars=len(stars),
556 minimum_dof=threshold,
557 poly_ndim=piffConfig['interp']['order'],
558 )
559
560 self._piffConfig = piffConfig
561 piffResult = piff.PSF.process(piffConfig)
562 wcs = {0: gswcs}
563
564 piffResult.fit(stars, wcs, pointing, logger=self.piffLogger)
565
566 nUsedStars = len([s for s in piffResult.stars if not s.is_flagged and not s.is_reserve])
567
568 if piffConfig['interp']['type'] in ['BasisPolynomial', 'Polynomial']:
569 threshold = _get_threshold(piffConfig['interp']['order'])
570 if nUsedStars < threshold:
571 if self.config.zerothOrderInterpNotEnoughStars:
572 self.log.warning(
573 "Only %d after outlier rejection, "
574 "but %d required. Using zeroth order interpolation."%((nUsedStars, threshold))
575 )
576 piffConfig['interp']['order'] = 0
577 # No need to do any outlier rejection assume
578 # PSF to be average of few stars.
579 piffConfig['max_iter'] = 1
580 piffResult.fit(stars, wcs, pointing, logger=self.piffLogger)
581 nUsedStars = len(stars)
582 else:
583 raise PiffTooFewGoodStarsError(
584 num_good_stars=nUsedStars,
585 minimum_dof=threshold,
586 poly_ndim=piffConfig['interp']['order'],
587 )
588
589 drawSize = 2*np.floor(0.5*stampSize/self.config.samplingSize) + 1
590
591 used_image_starId = {s.data.properties['starId'] for s in piffResult.stars
592 if not s.is_flagged and not s.is_reserve}
593
594 assert len(used_image_starId) == nUsedStars, "Star IDs are not unique"
595
596 if flagKey:
597 for candidate in psfCandidateList:
598 source = candidate.getSource()
599 starId = source.getId()
600 if starId in used_image_starId:
601 source.set(flagKey, True)
602
603 if metadata is not None:
604 metadata["spatialFitChi2"] = piffResult.chisq
605 metadata["numAvailStars"] = len(stars)
606 metadata["numGoodStars"] = nUsedStars
607 metadata["avgX"] = np.mean([s.x for s in piffResult.stars
608 if not s.is_flagged and not s.is_reserve])
609 metadata["avgY"] = np.mean([s.y for s in piffResult.stars
610 if not s.is_flagged and not s.is_reserve])
611
612 if not self.config.debugStarData:
613 for star in piffResult.stars:
614 # Remove large data objects from the stars
615 del star.fit.params
616 del star.fit.params_var
617 del star.fit.A
618 del star.fit.b
619 del star.data.image
620 del star.data.weight
621 del star.data.orig_weight
622
623 return PiffPsf(drawSize, drawSize, piffResult), None
624
625

◆ downsampleCandidates()

lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask.downsampleCandidates ( self,
inputCandidateList )
inherited
Down-sample candidates from the input candidate list.

Parameters
----------
inputCandidateList : `list` [`lsst.meas.algorithms.PsfCandidate`]
    Input candidate list.

Returns
-------
outputCandidateList : `list` [`lsst.meas.algorithms.PsfCandidate`]
    Down-selected candidate list.

Definition at line 75 of file psfDeterminer.py.

75 def downsampleCandidates(self, inputCandidateList):
76 """Down-sample candidates from the input candidate list.
77
78 Parameters
79 ----------
80 inputCandidateList : `list` [`lsst.meas.algorithms.PsfCandidate`]
81 Input candidate list.
82
83 Returns
84 -------
85 outputCandidateList : `list` [`lsst.meas.algorithms.PsfCandidate`]
86 Down-selected candidate list.
87 """
88 if len(inputCandidateList) <= self.config.maxCandidates:
89 return inputCandidateList
90
91 rng = np.random.RandomState(seed=self.config.downsampleRandomSeed)
92
93 self.log.info(
94 "Down-sampling from %d to %d psf candidates.",
95 len(inputCandidateList),
96 self.config.maxCandidates,
97 )
98
99 selection = rng.choice(len(inputCandidateList), size=self.config.maxCandidates, replace=False)
100 selection = np.sort(selection)
101
102 outputCandidateList = [inputCandidateList[index] for index in selection]
103
104 return outputCandidateList
105

Member Data Documentation

◆ _DefaultName

str lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask._DefaultName = "psfDeterminer"
staticprotectedinherited

Definition at line 70 of file psfDeterminer.py.

◆ _piffConfig

lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask._piffConfig = piffConfig
protected

Definition at line 560 of file piffPsfDeterminer.py.

◆ ConfigClass

lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask.ConfigClass = BasePsfDeterminerConfig
staticinherited

Definition at line 69 of file psfDeterminer.py.

◆ piffLogger

lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask.piffLogger = lsst.utils.logging.getLogger(f"{self.log.name}.piff")

Definition at line 409 of file piffPsfDeterminer.py.

◆ usesMatches

bool lsst.meas.algorithms.psfDeterminer.BasePsfDeterminerTask.usesMatches = False
staticinherited

Definition at line 68 of file psfDeterminer.py.


The documentation for this class was generated from the following file: