431 self, exposure, psfCandidateList, metadata=None, flagKey=None,
433 """Determine a Piff PSF model for an exposure given a list of PSF
438 exposure : `lsst.afw.image.Exposure`
439 Exposure containing the PSF candidates.
440 psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
441 A sequence of PSF candidates typically obtained by detecting sources
442 and then running them through a star selector.
443 metadata : `lsst.daf.base import PropertyList` or `None`, optional
444 A home for interesting tidbits of information.
445 flagKey : `str` or `None`, optional
446 Schema key used to mark sources actually used in PSF determination.
450 psf : `lsst.meas.extensions.piff.PiffPsf`
451 The measured PSF model.
453 Unused by this PsfDeterminer.
457 if self.config.stampSize:
458 stampSize = self.config.stampSize
459 if stampSize > psfCandidateList[0].getWidth():
460 self.log.warning(
"stampSize is larger than the PSF candidate size. Using candidate size.")
461 stampSize = psfCandidateList[0].getWidth()
463 self.log.debug(
"stampSize not set. Using candidate size.")
464 stampSize = psfCandidateList[0].getWidth()
466 scale = exposure.getWcs().getPixelScale(exposure.getBBox().getCenter()).asArcseconds()
468 match self.config.useCoordinates:
470 detector = exposure.getDetector()
471 pix_to_field = detector.getTransform(PIXELS, FIELD_ANGLE)
478 skyOrigin = exposure.getWcs().getSkyOrigin()
479 ra = skyOrigin.getLongitude().asDegrees()
480 dec = skyOrigin.getLatitude().asDegrees()
481 pointing = galsim.CelestialCoord(
488 gswcs = galsim.PixelScale(scale)
493 for candidate
in psfCandidateList:
494 cmi = candidate.getMaskedImage(stampSize, stampSize)
496 fracGood = np.sum(good)/good.size
497 if fracGood < self.config.minimumUnmaskedFraction:
502 bds = galsim.BoundsI(
503 galsim.PositionI(*bbox.getMin()),
504 galsim.PositionI(*bbox.getMax())
506 gsImage = galsim.Image(bds, wcs=gswcs, dtype=float)
507 gsImage.array[:] = cmi.image.array
508 gsWeight = galsim.Image(bds, wcs=gswcs, dtype=float)
509 gsWeight.array[:] = weight
511 source = candidate.getSource()
512 starId = source.getId()
513 image_pos = galsim.PositionD(source.getX(), source.getY())
515 data = piff.StarData(
521 star = piff.Star(data,
None)
522 star.data.properties[
'starId'] = starId
523 star.data.properties[
'colorValue'] = candidate.getPsfColorValue()
524 star.data.properties[
'colorType'] = candidate.getPsfColorType()
529 if hasattr(exposure.filter,
"bandLabel"):
530 band = exposure.filter.bandLabel
533 spatialOrder = self.config.spatialOrderPerBand.get(band, self.config.spatialOrder)
534 orders = [spatialOrder] * len(keys)
536 if self.config.useColor:
537 colors = [s.data.properties[
'colorValue']
for s
in stars
538 if np.isfinite(s.data.properties[
'colorValue'])]
539 colorTypes = [s.data.properties[
'colorType']
for s
in stars
540 if np.isfinite(s.data.properties[
'colorValue'])]
542 self.log.warning(
"No color informations for PSF candidates, Set PSF colors to 0s.")
545 meanColors = np.mean(colors)
546 colorType = list(set(colorTypes))
547 if len(colorType) > 1:
548 raise ValueError(f
"More than one colorType was providen:{colorType}")
549 colorType = colorType[0]
551 if not np.isfinite(s.data.properties[
'colorValue']):
552 s.data.properties[
'colorValue'] = meanColors
553 s.data.properties[
'colorType'] = colorType
554 keys.append(
'colorValue')
555 orders.append(self.config.colorOrder)
557 if self.config.piffPsfConfigYaml
is None:
562 'scale': scale * self.config.samplingSize,
563 'size': self.config.modelSize,
564 'interp': self.config.interpolant,
565 'centered': self.config.piffPixelGridFitCenter,
568 'type':
'BasisPolynomial',
571 'solver': self.config.piffBasisPolynomialSolver,
575 'nsigma': self.config.outlierNSigma,
576 'max_remove': self.config.outlierMaxRemove,
578 'max_iter': self.config.piffMaxIter
581 piffConfig = yaml.safe_load(self.config.piffPsfConfigYaml)
583 def _get_threshold(nth_order):
584 if isinstance(nth_order, list):
586 freeParameters = ((nth_order[0] + 1) * (nth_order[0] + 2)) // 2
587 if len(nth_order) == 3:
588 freeParameters += nth_order[2]
591 freeParameters = ((nth_order + 1) * (nth_order + 2)) // 2
592 return freeParameters
594 if piffConfig[
'interp'][
'type']
in [
'BasisPolynomial',
'Polynomial']:
595 threshold = _get_threshold(piffConfig[
'interp'][
'order'])
596 if len(stars) < threshold:
597 if self.config.zerothOrderInterpNotEnoughStars:
599 "Only %d stars found, "
600 "but %d required. Using zeroth order interpolation."%((len(stars), threshold))
602 piffConfig[
'interp'][
'order'] = [0] * len(keys)
605 piffConfig[
'max_iter'] = 1
608 num_good_stars=len(stars),
609 minimum_dof=threshold,
610 poly_ndim=piffConfig[
'interp'][
'order'],
613 piffResult = piff.PSF.process(piffConfig)
616 piffResult.fit(stars, wcs, pointing, logger=self.
piffLogger)
618 nUsedStars = len([s
for s
in piffResult.stars
if not s.is_flagged
and not s.is_reserve])
620 if piffConfig[
'interp'][
'type']
in [
'BasisPolynomial',
'Polynomial']:
621 threshold = _get_threshold(piffConfig[
'interp'][
'order'])
622 if nUsedStars < threshold:
623 if self.config.zerothOrderInterpNotEnoughStars:
625 "Only %d after outlier rejection, "
626 "but %d required. Using zeroth order interpolation."%((nUsedStars, threshold))
628 piffConfig[
'interp'][
'order'] = [0] * len(keys)
631 piffConfig[
'max_iter'] = 1
632 piffResult.fit(stars, wcs, pointing, logger=self.
piffLogger)
633 nUsedStars = len(stars)
636 num_good_stars=nUsedStars,
637 minimum_dof=threshold,
638 poly_ndim=piffConfig[
'interp'][
'order'],
641 drawSize = 2*np.floor(0.5*stampSize/self.config.samplingSize) + 1
643 used_image_starId = {s.data.properties[
'starId']
for s
in piffResult.stars
644 if not s.is_flagged
and not s.is_reserve}
646 assert len(used_image_starId) == nUsedStars,
"Star IDs are not unique"
649 for candidate
in psfCandidateList:
650 source = candidate.getSource()
651 starId = source.getId()
652 if starId
in used_image_starId:
653 source.set(flagKey,
True)
655 if metadata
is not None:
656 metadata[
"spatialFitChi2"] = piffResult.chisq
657 metadata[
"numAvailStars"] = len(stars)
658 metadata[
"numGoodStars"] = nUsedStars
659 metadata[
"avgX"] = np.mean([s.x
for s
in piffResult.stars
660 if not s.is_flagged
and not s.is_reserve])
661 metadata[
"avgY"] = np.mean([s.y
for s
in piffResult.stars
662 if not s.is_flagged
and not s.is_reserve])
664 if not self.config.debugStarData:
665 for star
in piffResult.stars:
668 del star.fit.params_var
673 del star.data.orig_weight
675 return PiffPsf(drawSize, drawSize, piffResult),
None