23 from builtins
import range
24 from builtins
import object
39 from .psfCandidate
import makePsfCandidate
40 from .doubleGaussianPsf
import DoubleGaussianPsf
41 from lsst.meas.base import SingleFrameMeasurementTask, SingleFrameMeasurementConfig
42 from .starSelector
import BaseStarSelectorTask, starSelectorRegistry
46 fluxLim = pexConfig.Field(
47 doc=
"specify the minimum psfFlux for good Psf Candidates",
50 check=
lambda x: x >= 0.0,
52 fluxMax = pexConfig.Field(
53 doc=
"specify the maximum psfFlux for good Psf Candidates (ignored if == 0)",
56 check=
lambda x: x >= 0.0,
58 clumpNSigma = pexConfig.Field(
59 doc=
"candidate PSF's shapes must lie within this many sigma of the average shape",
62 check=
lambda x: x >= 0.0,
64 histSize = pexConfig.Field(
65 doc=
"Number of bins in moment histogram",
68 check=
lambda x: x > 0,
70 histMomentMax = pexConfig.Field(
71 doc=
"Maximum moment to consider",
74 check=
lambda x: x > 0,
76 histMomentMaxMultiplier = pexConfig.Field(
77 doc=
"Multiplier of mean for maximum moments histogram range",
80 check=
lambda x: x > 0,
82 histMomentClip = pexConfig.Field(
83 doc=
"Clipping threshold for moments histogram range",
86 check=
lambda x: x > 0,
88 histMomentMinMultiplier = pexConfig.Field(
89 doc=
"Multiplier of mean for minimum moments histogram range",
92 check=
lambda x: x > 0,
96 BaseStarSelectorTask.ConfigClass.setDefaults(self)
98 "base_PixelFlags_flag_edge",
99 "base_PixelFlags_flag_interpolatedCenter",
100 "base_PixelFlags_flag_saturatedCenter",
101 "base_PixelFlags_flag_crCenter",
105 Clump = collections.namedtuple(
'Clump', [
'peak',
'x',
'y',
'ixx',
'ixy',
'iyy',
'a',
'b',
'c'])
110 """A functor to check whether a source has any flags set that should cause it to be labeled bad.""" 112 def __init__(self, table, badFlags, fluxLim, fluxMax):
113 self.
keys = [table.getSchema().find(name).key
for name
in badFlags]
122 if self.
fluxLim is not None and source.getPsfFlux() < self.
fluxLim:
137 """!A star selector based on second moments 139 @anchor SecondMomentStarSelectorTask_ 141 @section meas_algorithms_secondMomentStarSelector_Contents Contents 143 - @ref meas_algorithms_secondMomentStarSelector_Purpose 144 - @ref meas_algorithms_secondMomentStarSelector_Initialize 145 - @ref meas_algorithms_secondMomentStarSelector_IO 146 - @ref meas_algorithms_secondMomentStarSelector_Config 147 - @ref meas_algorithms_secondMomentStarSelector_Debug 149 @section meas_algorithms_secondMomentStarSelector_Purpose Description 151 A star selector based on second moments. 153 @warning This is a naive algorithm; use with caution. 155 @section meas_algorithms_secondMomentStarSelector_Initialize Task initialisation 157 @copydoc \_\_init\_\_ 159 @section meas_algorithms_secondMomentStarSelector_IO Invoking the Task 161 Like all star selectors, the main method is `run`. 163 @section meas_algorithms_secondMomentStarSelector_Config Configuration parameters 165 See @ref SecondMomentStarSelectorConfig 167 @section meas_algorithms_secondMomentStarSelector_Debug Debug variables 169 SecondMomentStarSelectorTask has a debug dictionary with the following keys: 172 <dd>bool; if True display debug information 174 display = lsstDebug.Info(__name__).display 175 displayExposure = lsstDebug.Info(__name__).displayExposure 176 pauseAtEnd = lsstDebug.Info(__name__).pauseAtEnd 178 For example, put something like: 182 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 183 if name.endswith("catalogStarSelector"): 188 lsstDebug.Info = DebugInfo 190 into your `debug.py` file and run your task with the `--debug` flag. 192 ConfigClass = SecondMomentStarSelectorConfig
196 """!Return a list of PSF candidates that represent likely stars 198 A list of PSF candidates may be used by a PSF fitter to construct a PSF. 200 @param[in] exposure the exposure containing the sources 201 @param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) 202 @param[in] matches astrometric matches; ignored by this star selector 204 @return an lsst.pipe.base.Struct containing: 205 - starCat catalog of selected stars (a subset of sourceCat) 210 isGoodSource =
CheckSource(sourceCat.getTable(), self.config.badFlags, self.config.fluxLim,
213 detector = exposure.getDetector()
215 mi = exposure.getMaskedImage()
223 ixx, iyy = s.getIxx(), s.getIyy()
225 if (ixx == ixx
and ixx < self.config.histMomentMax
and 226 iyy == iyy
and iyy < self.config.histMomentMax
and 228 iqqList.append(s.getIxx())
229 iqqList.append(s.getIyy())
231 iqqMean = stat.getValue(afwMath.MEANCLIP)
232 iqqStd = stat.getValue(afwMath.STDEVCLIP)
233 iqqMax = stat.getValue(afwMath.MAX)
235 iqqLimit =
max(iqqMean + self.config.histMomentClip*iqqStd,
236 self.config.histMomentMaxMultiplier*iqqMean)
238 if iqqLimit > iqqMax:
239 iqqLimit =
max(self.config.histMomentMinMultiplier*iqqMean, iqqMax)
242 xSize=self.config.histSize, ySize=self.config.histSize,
243 ixxMax=iqqLimit, iyyMax=iqqLimit)
247 ds9.mtv(mi, frame=frame, title=
"PSF candidates")
250 for source
in sourceCat:
251 good = isGoodSource(source)
253 notRejected = psfHist.insert(source)
257 ctypes.append(ds9.GREEN)
259 ctypes.append(ds9.MAGENTA)
261 ctypes.append(ds9.RED)
264 with ds9.Buffering():
265 for source, ctype
in zip(sourceCat, ctypes):
266 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
267 frame=frame, ctype=ctype)
269 clumps = psfHist.getClumps(display=display)
280 if detector
is not None:
281 pixToTanPix = detector.getTransform(PIXELS, TAN_PIXELS)
286 for source
in sourceCat:
287 if not isGoodSource(source):
289 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
294 m.transform(linTransform)
295 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
297 x, y = psfHist.momentsToPixel(Ixx, Iyy)
299 dx, dy = (x - clump.x), (y - clump.y)
301 if math.sqrt(clump.a*dx*dx + 2*clump.b*dx*dy + clump.c*dy*dy) < 2*self.config.clumpNSigma:
303 if not isGoodSource(source):
311 if psfCandidate.getWidth() == 0:
312 psfCandidate.setBorderWidth(self.config.borderWidth)
313 psfCandidate.setWidth(self.config.kernelSize + 2*self.config.borderWidth)
314 psfCandidate.setHeight(self.config.kernelSize + 2*self.config.borderWidth)
316 im = psfCandidate.getMaskedImage().getImage()
319 starCat.append(source)
322 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
323 size=4, frame=frame, ctype=ds9.CYAN)
324 except Exception
as err:
325 self.log.
error(
"Failed on source %s: %s" % (source.getId(), err))
335 """A class to represent a histogram of (Ixx, Iyy) 338 def __init__(self, xSize=32, ySize=32, ixxMax=30, iyyMax=30, detector=None, xy0=afwGeom.Point2D(0, 0)):
339 """Construct a _PsfShapeHistogram 341 The maximum seeing FWHM that can be tolerated is [xy]Max/2.35 pixels. 342 The 'resolution' of stars vs galaxies/CRs is provided by [xy]Size/[xy]Max. 343 A larger (better) resolution may thresh the peaks, but a smaller (worse) 344 resolution will allow stars and galaxies/CRs to mix. The disadvantages of 345 a larger (better) resolution can be compensated (some) by using multiple 348 @input[in] [xy]Size: the size of the psfImage (in pixels) 349 @input[in] ixxMax, iyyMax: the maximum values for I[xy][xy] 351 self._xSize, self.
_ySize = xSize, ySize
352 self._xMax, self.
_yMax = ixxMax, iyyMax
362 """Insert source into the histogram.""" 364 ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy()
366 tanSys = self.
detector.makeCameraSys(TAN_PIXELS)
367 if tanSys
in self.
detector.getTransformMap():
368 pixToTanPix = self.
detector.getTransform(PIXELS, TAN_PIXELS)
372 m.transform(linTransform)
373 ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy()
382 if i
in range(0, self._xSize)
and j
in range(0, self.
_ySize):
393 x = ixx * self._xSize / self._xMax
398 """Given a peak position in self._psfImage, return the corresponding (Ixx, Iyy)""" 402 ixx = x*self._xMax/self._xSize
408 raise RuntimeError(
"No candidate PSF sources")
414 width, height = psfImage.getWidth(), psfImage.getHeight()
419 largeImg.assign(psfImage, bbox, afwImage.LOCAL)
425 var = afwImage.ImageF(largeImg.getDimensions())
427 mpsfImage = afwImage.MaskedImageF(largeImg, msk, var)
437 threshold = maxVal - sigma*math.sqrt(maxVal)
448 schema = SourceTable.makeMinimalSchema()
450 psfImageConfig.slots.centroid =
"base_SdssCentroid" 451 psfImageConfig.plugins[
"base_SdssCentroid"].doFootprintCheck =
False 452 psfImageConfig.slots.psfFlux =
None 453 psfImageConfig.slots.apFlux =
"base_CircularApertureFlux_3_0" 454 psfImageConfig.slots.modelFlux =
None 455 psfImageConfig.slots.instFlux =
None 456 psfImageConfig.slots.calibFlux =
None 457 psfImageConfig.slots.shape =
"base_SdssShape" 460 psfImageConfig.algorithms.names = [
"base_SdssCentroid",
"base_CircularApertureFlux",
"base_SdssShape"]
461 psfImageConfig.algorithms[
"base_CircularApertureFlux"].radii = [3.0]
462 psfImageConfig.validate()
468 exposure.setPsf(DoubleGaussianPsf(11, 11, gaussianWidth))
470 ds.makeSources(sourceCat)
479 ds9.mtv(dispImage, title=
"PSF Selection Image", frame=frame)
484 IzzMax = (self._xSize/8.0)**2
486 task.run(sourceCat, exposure)
487 for i, source
in enumerate(sourceCat):
488 if source.getCentroidFlag():
490 x, y = source.getX(), source.getY()
492 apFluxes.append(source.getApFlux())
494 val = mpsfImage.getImage().get(int(x) + width, int(y) + height)
496 psfClumpIxx = source.getIxx()
497 psfClumpIxy = source.getIxy()
498 psfClumpIyy = source.getIyy()
502 ds9.pan(x, y, frame=frame)
504 ds9.dot(
"+", x, y, ctype=ds9.YELLOW, frame=frame)
505 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
506 ctype=ds9.YELLOW, frame=frame)
508 if psfClumpIxx < IzzMin
or psfClumpIyy < IzzMin:
509 psfClumpIxx =
max(psfClumpIxx, IzzMin)
510 psfClumpIyy =
max(psfClumpIyy, IzzMin)
512 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
513 ctype=ds9.RED, frame=frame)
515 det = psfClumpIxx*psfClumpIyy - psfClumpIxy*psfClumpIxy
517 a, b, c = psfClumpIyy/det, -psfClumpIxy/det, psfClumpIxx/det
518 except ZeroDivisionError:
519 a, b, c = 1e4, 0, 1e4
521 clumps.append(
Clump(peak=val, x=x, y=y, a=a, b=b, c=c,
522 ixx=psfClumpIxx, ixy=psfClumpIxy, iyy=psfClumpIyy))
525 msg =
"Failed to determine center of PSF clump" 528 raise RuntimeError(msg)
542 if clump.ixx < IzzMax
and clump.iyy < IzzMax:
543 goodClumps.append(clump)
546 if len(goodClumps) == 0:
550 iBestClump = numpy.argsort(apFluxes)[0]
551 clumps = [clumps[iBestClump]]
555 starSelectorRegistry.register(
"secondMoment", SecondMomentStarSelectorTask)
An ellipse core with quadrupole moments as parameters.
def __init__(self, xSize=32, ySize=32, ixxMax=30, iyyMax=30, detector=None, xy0=afwGeom.Point2D(0, 0))
Base class for star selectors.
A subtask for measuring the properties of sources on a single exposure.
A Threshold is used to pass a threshold value to detection algorithms.
def __call__(self, source)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
daf::base::PropertySet * set
A star selector based on second moments.
An integer coordinate rectangle.
AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, Point2D const &inPoint)
Approximate a Transform by its local linearization.
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
Represent a 2-dimensional array of bitmask pixels.
Config class for single frame measurement driver task.
std::shared_ptr< PsfCandidate< PixelT > > makePsfCandidate(boost::shared_ptr< afw::table::SourceRecord > const &source, boost::shared_ptr< afw::image::Exposure< PixelT > > image)
Return a PsfCandidate of the right sort.
def momentsToPixel(self, ixx, iyy)
def getClumps(self, sigma=1.0, display=False)
def pixelToMoments(self, x, y)
def __init__(self, table, badFlags, fluxLim, fluxMax)
def selectStars(self, exposure, sourceCat, matches=None)
Return a list of PSF candidates that represent likely stars.
daf::base::PropertyList * list