36 from .
import algorithmsLib
37 from lsst.meas.base import SingleFrameMeasurementTask, SingleFrameMeasurementConfig
40 fluxLim = pexConfig.Field(
41 doc =
"specify the minimum psfFlux for good Psf Candidates",
44 check =
lambda x: x >= 0.0,
46 fluxMax = pexConfig.Field(
47 doc =
"specify the maximum psfFlux for good Psf Candidates (ignored if == 0)",
50 check =
lambda x: x >= 0.0,
52 clumpNSigma = pexConfig.Field(
53 doc =
"candidate PSF's shapes must lie within this many sigma of the average shape",
56 check =
lambda x: x >= 0.0,
58 kernelSize = pexConfig.Field(
59 doc =
"size of the kernel to create",
63 borderWidth = pexConfig.Field(
64 doc =
"number of pixels to ignore around the edge of PSF candidate postage stamps",
68 badFlags = pexConfig.ListField(
69 doc =
"List of flags which cause a source to be rejected as bad",
71 default = [
"base_PixelFlags_flag_edge",
72 "base_PixelFlags_flag_interpolatedCenter",
73 "base_PixelFlags_flag_saturatedCenter",
74 "base_PixelFlags_flag_crCenter",
77 histSize = pexConfig.Field(
78 doc =
"Number of bins in moment histogram",
81 check =
lambda x: x > 0,
83 histMomentMax = pexConfig.Field(
84 doc =
"Maximum moment to consider",
87 check =
lambda x: x > 0,
89 histMomentMaxMultiplier = pexConfig.Field(
90 doc =
"Multiplier of mean for maximum moments histogram range",
93 check =
lambda x: x > 0,
95 histMomentClip = pexConfig.Field(
96 doc =
"Clipping threshold for moments histogram range",
99 check =
lambda x: x > 0,
101 histMomentMinMultiplier = pexConfig.Field(
102 doc =
"Multiplier of mean for minimum moments histogram range",
105 check =
lambda x: x > 0,
108 Clump = collections.namedtuple(
'Clump', [
'peak',
'x',
'y',
'ixx',
'ixy',
'iyy',
'a',
'b',
'c'])
111 """A functor to check whether a source has any flags set that should cause it to be labeled bad."""
113 def __init__(self, table, badFlags, fluxLim, fluxMax):
114 self.
keys = [table.getSchema().find(name).key
for name
in badFlags]
115 self.keys.append(table.getCentroidFlagKey())
130 ConfigClass = SecondMomentStarSelectorConfig
133 """Construct a star selector that uses second moments
135 This is a naive algorithm and should be used with caution.
137 @param[in] config: An instance of SecondMomentStarSelectorConfig
142 """Return a list of PSF candidates that represent likely stars
144 A list of PSF candidates may be used by a PSF fitter to construct a PSF.
146 @param[in] exposure: the exposure containing the sources
147 @param[in] catalog: a SourceCatalog containing sources that may be stars
148 @param[in] matches: astrometric matches; ignored by this star selector
150 @return psfCandidateList: a list of PSF candidates.
157 isGoodSource =
CheckSource(catalog.getTable(), self.config.badFlags, self.config.fluxLim,
160 detector = exposure.getDetector()
162 mi = exposure.getMaskedImage()
170 ixx, iyy = s.getIxx(), s.getIyy()
172 if (ixx == ixx
and ixx < self.config.histMomentMax
and
173 iyy == iyy
and iyy < self.config.histMomentMax
and
175 iqqList.append(s.getIxx())
176 iqqList.append(s.getIyy())
178 iqqMean = stat.getValue(afwMath.MEANCLIP)
179 iqqStd = stat.getValue(afwMath.STDEVCLIP)
180 iqqMax = stat.getValue(afwMath.MAX)
182 iqqLimit = max(iqqMean + self.config.histMomentClip*iqqStd,
183 self.config.histMomentMaxMultiplier*iqqMean)
185 if iqqLimit > iqqMax:
186 iqqLimit = max(self.config.histMomentMinMultiplier*iqqMean, iqqMax)
188 psfHist =
_PsfShapeHistogram(detector=detector, xSize=self.config.histSize, ySize=self.config.histSize,
189 ixxMax=iqqLimit, iyyMax=iqqLimit)
191 if display
and displayExposure:
193 ds9.mtv(mi, frame=frame, title=
"PSF candidates")
195 with ds9.Buffering():
196 for source
in catalog:
197 if isGoodSource(source):
198 if psfHist.insert(source):
205 if display
and displayExposure:
206 ds9.dot(
"o", source.getX() - mi.getX0(),
207 source.getY() - mi.getY0(), frame=frame, ctype=ctype)
209 clumps = psfHist.getClumps(display=display)
217 psfCandidateList = []
219 pixToTanXYTransform =
None
220 if detector
is not None:
221 tanSys = detector.makeCameraSys(cameraGeom.TAN_PIXELS)
222 pixToTanXYTransform = detector.getTransformMap().get(tanSys)
227 for source
in catalog:
228 if not isGoodSource(source):
continue
229 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
230 if pixToTanXYTransform:
232 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
233 m = geomEllip.Quadrupole(Ixx, Iyy, Ixy)
234 m.transform(linTransform)
235 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
237 x, y = psfHist.momentsToPixel(Ixx, Iyy)
239 dx, dy = (x - clump.x), (y - clump.y)
241 if math.sqrt(clump.a*dx*dx + 2*clump.b*dx*dy + clump.c*dy*dy) < 2*self.config.clumpNSigma:
243 if not isGoodSource(source):
246 psfCandidate = algorithmsLib.makePsfCandidate(source, exposure)
251 if psfCandidate.getWidth() == 0:
252 psfCandidate.setBorderWidth(self.config.borderWidth)
253 psfCandidate.setWidth(self.config.kernelSize + 2*self.config.borderWidth)
254 psfCandidate.setHeight(self.config.kernelSize + 2*self.config.borderWidth)
256 im = psfCandidate.getMaskedImage().getImage()
259 psfCandidateList.append(psfCandidate)
261 if display
and displayExposure:
262 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
263 size=4, frame=frame, ctype=ds9.CYAN)
264 except Exception
as err:
268 return psfCandidateList
271 """A class to represent a histogram of (Ixx, Iyy)
273 def __init__(self, xSize=32, ySize=32, ixxMax=30, iyyMax=30, detector=None, xy0=afwGeom.Point2D(0,0)):
274 """Construct a _PsfShapeHistogram
276 The maximum seeing FWHM that can be tolerated is [xy]Max/2.35 pixels.
277 The 'resolution' of stars vs galaxies/CRs is provided by [xy]Size/[xy]Max.
278 A larger (better) resolution may thresh the peaks, but a smaller (worse)
279 resolution will allow stars and galaxies/CRs to mix. The disadvantages of
280 a larger (better) resolution can be compensated (some) by using multiple
283 @input[in] [xy]Size: the size of the psfImage (in pixels)
284 @input[in] ixxMax, iyyMax: the maximum values for I[xy][xy]
287 self._xMax, self.
_yMax = ixxMax, iyyMax
297 """Insert source into the histogram."""
299 ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy()
301 tanSys = self.detector.makeCameraSys(cameraGeom.TAN_PIXELS)
302 if tanSys
in self.detector.getTransformMap():
303 pixToTanXYTransform = self.detector.getTransformMap()[tanSys]
305 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
306 m = geomEllip.Quadrupole(ixx, iyy, ixy)
307 m.transform(linTransform)
308 ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy()
317 if i
in range(0, self._xSize)
and j
in range(0, self.
_ySize):
319 self._psfImage.set(i, j, self._psfImage.get(i, j) + 1)
328 x = ixx * self._xSize / self._xMax
333 """Given a peak position in self._psfImage, return the corresponding (Ixx, Iyy)"""
337 ixx = x*self._xMax/self._xSize
343 raise RuntimeError(
"No candidate PSF sources")
349 width, height = psfImage.getWidth(), psfImage.getHeight()
354 subLargeImg = psfImage.Factory(largeImg, bbox, afwImage.LOCAL)
355 subLargeImg <<= psfImage
360 msk = afwImage.MaskU(largeImg.getDimensions())
362 var = afwImage.ImageF(largeImg.getDimensions())
364 mpsfImage = afwImage.MaskedImageF(largeImg, msk, var)
374 threshold = maxVal - sigma*math.sqrt(maxVal)
385 schema = afwTable.SourceTable.makeMinimalSchema()
386 psfImageConfig = SingleFrameMeasurementConfig()
387 psfImageConfig.doApplyApCorr =
"no"
388 psfImageConfig.slots.centroid =
"base_SdssCentroid"
389 psfImageConfig.slots.psfFlux =
None
390 psfImageConfig.slots.apFlux =
"base_CircularApertureFlux_3_0"
391 psfImageConfig.slots.modelFlux =
None
392 psfImageConfig.slots.instFlux =
None
393 psfImageConfig.slots.calibFlux =
None
394 psfImageConfig.slots.shape =
"base_SdssShape"
397 psfImageConfig.algorithms.names = [
"base_SdssCentroid",
"base_CircularApertureFlux",
"base_SdssShape"]
398 psfImageConfig.algorithms[
"base_CircularApertureFlux"].radii = [3.0]
399 psfImageConfig.validate()
400 task = SingleFrameMeasurementTask(schema, config=psfImageConfig)
405 exposure.setPsf(algorithmsLib.DoubleGaussianPsf(11, 11, gaussianWidth))
407 ds.makeSources(catalog)
416 ds9.mtv(dispImage,title=
"PSF Selection Image", frame=frame)
422 IzzMax = (self._xSize/8.0)**2
425 task.run(exposure, catalog)
426 for i, source
in enumerate(catalog):
427 if source.getCentroidFlag():
429 x, y = source.getX(), source.getY()
431 apFluxes.append(source.getApFlux())
433 val = mpsfImage.getImage().get(int(x) + width, int(y) + height)
435 psfClumpIxx = source.getIxx()
436 psfClumpIxy = source.getIxy()
437 psfClumpIyy = source.getIyy()
441 ds9.pan(x, y, frame=frame)
443 ds9.dot(
"+", x, y, ctype=ds9.YELLOW, frame=frame)
444 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
445 ctype=ds9.YELLOW, frame=frame)
447 if psfClumpIxx < IzzMin
or psfClumpIyy < IzzMin:
448 psfClumpIxx = max(psfClumpIxx, IzzMin)
449 psfClumpIyy = max(psfClumpIyy, IzzMin)
451 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
452 ctype=ds9.RED, frame=frame)
454 det = psfClumpIxx*psfClumpIyy - psfClumpIxy*psfClumpIxy
456 a, b, c = psfClumpIyy/det, -psfClumpIxy/det, psfClumpIxx/det
457 except ZeroDivisionError:
458 a, b, c = 1e4, 0, 1e4
460 clumps.append(
Clump(peak=val, x=x, y=y, a=a, b=b, c=c,
461 ixx=psfClumpIxx, ixy=psfClumpIxy, iyy=psfClumpIyy))
464 msg =
"Failed to determine center of PSF clump"
467 raise RuntimeError(msg)
481 if clump.ixx < IzzMax
and clump.iyy < IzzMax:
482 goodClumps.append(clump)
485 if len(goodClumps) == 0:
489 iBestClump = numpy.argsort(apFluxes)[0]
490 clumps = [clumps[iBestClump]]
A Threshold is used to pass a threshold value to detection algorithms.
An integer coordinate rectangle.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Exposure< ImagePixelT, MaskPixelT, VariancePixelT >::Ptr makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, boost::shared_ptr< Wcs const > wcs=boost::shared_ptr< Wcs const >())