2424import numpy as np
2525
2626from lsstDebug import getDebugFrame
27+ import lsst .afw .math as afwMath
2728import lsst .afw .table as afwTable
2829import lsst .pex .config as pexConfig
2930import lsst .pipe .base as pipeBase
3031import lsst .daf .base as dafBase
3132import lsst .pipe .base .connectionTypes as cT
32- from lsst .afw .math import BackgroundList
3333from lsst .afw .table import SourceTable
3434from lsst .meas .algorithms import (
3535 SubtractBackgroundTask ,
@@ -131,6 +131,52 @@ class CharacterizeImageConfig(pipeBase.PipelineTaskConfig,
131131 "estimate PSF. If useSimplePsf is True then 2 should be plenty; "
132132 "otherwise more may be wanted." ,
133133 )
134+ maxUnNormPsfEllipticityPerBand = pexConfig .DictField (
135+ keytype = str ,
136+ itemtype = float ,
137+ default = {
138+ "u" : 3.8 ,
139+ "g" : 3.8 ,
140+ "r" : 3.8 ,
141+ "i" : 3.8 ,
142+ "z" : 3.8 ,
143+ "y" : 3.8 ,
144+ },
145+ doc = "Maximum unnormalized ellipticity (defined as hypot(Ixx - Iyy, 2Ixy)) of the PSF model "
146+ "deemed good enough for further consideration. Values above this threshold raise "
147+ "UnprocessableDataError." ,
148+ )
149+ maxUnNormPsfEllipticityFallback = pexConfig .Field (
150+ dtype = float ,
151+ default = 3.8 ,
152+ doc = "Fallback maximum unnormalized ellipticity (defined as hypot(Ixx - Iyy, 2Ixy)) of the "
153+ "PSF model deemed good enough for further consideration if the current band is not in "
154+ "the config.maxUnNormPsfEllipticityPerBand dict. Values above this threshold "
155+ "raise UnprocessableDataError." ,
156+ )
157+ maxPsfEllipticityPerBand = pexConfig .DictField (
158+ keytype = str ,
159+ itemtype = float ,
160+ default = {
161+ "u" : 0.34 ,
162+ "g" : 0.34 ,
163+ "r" : 0.34 ,
164+ "i" : 0.34 ,
165+ "z" : 0.34 ,
166+ "y" : 0.34 ,
167+ },
168+ doc = "Value of the PSF model ellipticity deemed good enough for further consideration, "
169+ "regardless of the value of the unnormalized PSF model ellipticity. Values above "
170+ "this threshold raise UnprocessableDataError." ,
171+ )
172+ maxPsfEllipticityFallback = pexConfig .Field (
173+ dtype = float ,
174+ default = 0.34 ,
175+ doc = "Fallback maximum ellipticity of the PSF model deemed good enough for further "
176+ "consideration if the current band is not in the config.maxPsfEllipticityPerBand "
177+ "dict. Values above this threshold raise UnprocessableDataError." ,
178+ )
179+
134180 background = pexConfig .ConfigurableField (
135181 target = SubtractBackgroundTask ,
136182 doc = "Configuration for initial background estimation" ,
@@ -403,6 +449,10 @@ def run(self, exposure, background=None, idGenerator=None):
403449 ------
404450 RuntimeError
405451 Raised if PSF sigma is NaN.
452+ UnprocessableDataError
453+ Raised if the unnormalized model PSF ellipticity is greater than
454+ maxUnNormPsfEllipticity or the model PSF ellipticity is greater
455+ than maxPsfEllipticity.
406456 """
407457 self ._frame = self ._initialFrame # reset debug display frame
408458
@@ -423,17 +473,49 @@ def run(self, exposure, background=None, idGenerator=None):
423473 idGenerator = idGenerator ,
424474 background = background ,
425475 )
426-
427476 psf = dmeRes .exposure .getPsf ()
428477 # Just need a rough estimate; average positions are fine
429478 psfAvgPos = psf .getAveragePosition ()
430- psfSigma = psf .computeShape (psfAvgPos ).getDeterminantRadius ()
479+ psfShape = psf .computeShape (psfAvgPos )
480+ psfSigma = psfShape .getDeterminantRadius ()
481+ psfE1 = (psfShape .getIxx () - psfShape .getIyy ())/ (psfShape .getIxx () + psfShape .getIyy ())
482+ psfE2 = 2.0 * psfShape .getIxy ()/ (psfShape .getIxx () + psfShape .getIyy ())
483+ psfE = np .sqrt (psfE1 ** 2.0 + psfE2 ** 2.0 )
484+ unNormPsfE = np .hypot (psfShape .getIxx () - psfShape .getIyy (), 2.0 * psfShape .getIxy ())
485+
431486 psfDimensions = psf .computeImage (psfAvgPos ).getDimensions ()
432487 medBackground = np .median (dmeRes .background .getImage ().getArray ())
433- self .log .info ("iter %s; PSF sigma=%0.4f, dimensions=%s; median background=%0.2f" ,
434- i + 1 , psfSigma , psfDimensions , medBackground )
488+ self .log .info (
489+ "iter %s; PSF sigma=%0.4f, psfE=%.3f, unNormPsfE=%.2f dimensions=%s; "
490+ "median background=%0.2f" ,
491+ i + 1 , psfSigma , psfE , unNormPsfE , psfDimensions , medBackground )
435492 if np .isnan (psfSigma ):
436493 raise RuntimeError ("PSF sigma is NaN, cannot continue PSF determination." )
494+ band = exposure .filter .bandLabel
495+ if band in self .config .maxUnNormPsfEllipticityPerBand :
496+ maxUnNormPsfEllipticity = self .config .maxUnNormPsfEllipticityPerBand [band ]
497+ else :
498+ maxUnNormPsfEllipticity = self .config .maxUnNormPsfEllipticityFallback
499+ self .log .warning (
500+ f"Band { band } was not included in self.config.maxUnNormPsfEllipticityPerBand. "
501+ f"Setting maxUnNormPsfEllipticity to fallback value of { maxUnNormPsfEllipticity } ."
502+ )
503+ if band in self .config .maxPsfEllipticityPerBand :
504+ maxPsfEllipticity = self .config .maxPsfEllipticityPerBand [band ]
505+ else :
506+ maxPsfEllipticity = self .config .maxPsfEllipticityFallback
507+ self .log .warning (
508+ f"Band { band } was not included in self.config.maxPsfEllipticityPerBand. "
509+ f"Setting maxUnNormPsfEllipticity to fallback value of { maxPsfEllipticity :.2f} ."
510+ )
511+
512+ if unNormPsfE > maxUnNormPsfEllipticity or psfE > maxPsfEllipticity :
513+ raise pipeBase .UnprocessableDataError (
514+ "Either the unnormalized model PSF ellipticity is greater than the maximum allowed "
515+ f"for band { band } ({ maxUnNormPsfEllipticity :.2f} ) or the model PSF ellipticity "
516+ f"is greater than the maximum allowed({ maxPsfEllipticity :.2f} )"
517+ f"(unNormPsfE = { unNormPsfE :.2f} , psfE = { psfE :.2f} )"
518+ )
437519
438520 self .display ("psf" , exposure = dmeRes .exposure , sourceCat = dmeRes .sourceCat )
439521
@@ -558,7 +640,7 @@ def detectMeasureAndEstimatePsf(self, exposure, idGenerator, background):
558640 self .display ("repair_iter" , exposure = exposure )
559641
560642 if background is None :
561- background = BackgroundList ()
643+ background = afwMath . BackgroundList ()
562644
563645 sourceIdFactory = idGenerator .make_table_id_factory ()
564646 table = SourceTable .make (self .schema , sourceIdFactory )
0 commit comments