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.")
544 colorTypes = [stars[0].data.properties[
'colorType']]
546 meanColors = np.mean(colors)
547 colorType = list(set(colorTypes))
548 if len(colorType) > 1:
549 raise ValueError(f
"More than one colorType was provided:{colorType}")
550 colorType = colorType[0]
552 if not np.isfinite(s.data.properties[
'colorValue']):
553 s.data.properties[
'colorValue'] = meanColors
554 s.data.properties[
'colorType'] = colorType
555 keys.append(
'colorValue')
556 orders.append(self.config.colorOrder)
558 if self.config.piffPsfConfigYaml
is None:
563 'scale': scale * self.config.samplingSize,
564 'size': self.config.modelSize,
565 'interp': self.config.interpolant,
566 'centered': self.config.piffPixelGridFitCenter,
569 'type':
'BasisPolynomial',
572 'solver': self.config.piffBasisPolynomialSolver,
576 'nsigma': self.config.outlierNSigma,
577 'max_remove': self.config.outlierMaxRemove,
579 'max_iter': self.config.piffMaxIter
582 piffConfig = yaml.safe_load(self.config.piffPsfConfigYaml)
584 def _get_threshold(nth_order):
585 if isinstance(nth_order, list):
587 freeParameters = ((nth_order[0] + 1) * (nth_order[0] + 2)) // 2
588 if len(nth_order) == 3:
589 freeParameters += nth_order[2]
592 freeParameters = ((nth_order + 1) * (nth_order + 2)) // 2
593 return freeParameters
595 if piffConfig[
'interp'][
'type']
in [
'BasisPolynomial',
'Polynomial']:
596 threshold = _get_threshold(piffConfig[
'interp'][
'order'])
597 if len(stars) < threshold:
598 if self.config.zerothOrderInterpNotEnoughStars:
600 "Only %d stars found, "
601 "but %d required. Using zeroth order interpolation."%((len(stars), threshold))
603 piffConfig[
'interp'][
'order'] = [0] * len(keys)
606 piffConfig[
'max_iter'] = 1
609 num_good_stars=len(stars),
610 minimum_dof=threshold,
611 poly_ndim=piffConfig[
'interp'][
'order'],
614 piffResult = piff.PSF.process(piffConfig)
617 piffResult.fit(stars, wcs, pointing, logger=self.
piffLogger)
619 nUsedStars = len([s
for s
in piffResult.stars
if not s.is_flagged
and not s.is_reserve])
621 if piffConfig[
'interp'][
'type']
in [
'BasisPolynomial',
'Polynomial']:
622 threshold = _get_threshold(piffConfig[
'interp'][
'order'])
623 if nUsedStars < threshold:
624 if self.config.zerothOrderInterpNotEnoughStars:
626 "Only %d after outlier rejection, "
627 "but %d required. Using zeroth order interpolation."%((nUsedStars, threshold))
629 piffConfig[
'interp'][
'order'] = [0] * len(keys)
632 piffConfig[
'max_iter'] = 1
633 piffResult.fit(stars, wcs, pointing, logger=self.
piffLogger)
634 nUsedStars = len(stars)
637 num_good_stars=nUsedStars,
638 minimum_dof=threshold,
639 poly_ndim=piffConfig[
'interp'][
'order'],
642 drawSize = 2*np.floor(0.5*stampSize/self.config.samplingSize) + 1
644 used_image_starId = {s.data.properties[
'starId']
for s
in piffResult.stars
645 if not s.is_flagged
and not s.is_reserve}
647 assert len(used_image_starId) == nUsedStars,
"Star IDs are not unique"
650 for candidate
in psfCandidateList:
651 source = candidate.getSource()
652 starId = source.getId()
653 if starId
in used_image_starId:
654 source.set(flagKey,
True)
656 if metadata
is not None:
657 metadata[
"spatialFitChi2"] = piffResult.chisq
658 metadata[
"numAvailStars"] = len(stars)
659 metadata[
"numGoodStars"] = nUsedStars
660 metadata[
"avgX"] = np.mean([s.x
for s
in piffResult.stars
661 if not s.is_flagged
and not s.is_reserve])
662 metadata[
"avgY"] = np.mean([s.y
for s
in piffResult.stars
663 if not s.is_flagged
and not s.is_reserve])
665 if not self.config.debugStarData:
666 for star
in piffResult.stars:
669 del star.fit.params_var
674 del star.data.orig_weight
676 return PiffPsf(drawSize, drawSize, piffResult),
None