36 from .
import algorithmsLib
37 from .
import measurement
38 from .measurement
import SourceMeasurementTask, SourceMeasurementConfig
39 from lsst.meas.base.sfm import SingleFrameMeasurementTask, SingleFrameMeasurementConfig
43 fluxLim = pexConfig.Field(
44 doc =
"specify the minimum psfFlux for good Psf Candidates",
47 check =
lambda x: x >= 0.0,
49 fluxMax = pexConfig.Field(
50 doc =
"specify the maximum psfFlux for good Psf Candidates (ignored if == 0)",
53 check =
lambda x: x >= 0.0,
55 clumpNSigma = pexConfig.Field(
56 doc =
"candidate PSF's shapes must lie within this many sigma of the average shape",
59 check =
lambda x: x >= 0.0,
61 kernelSize = pexConfig.Field(
62 doc =
"size of the kernel to create",
66 borderWidth = pexConfig.Field(
67 doc =
"number of pixels to ignore around the edge of PSF candidate postage stamps",
71 badFlags = pexConfig.ListField(
72 doc =
"List of flags which cause a source to be rejected as bad",
74 default = [
"base_PixelFlags_flag_edge",
75 "base_PixelFlags_flag_interpolatedCenter",
76 "base_PixelFlags_flag_saturatedCenter",
77 "base_PixelFlags_flag_crCenter",
80 histSize = pexConfig.Field(
81 doc =
"Number of bins in moment histogram",
84 check =
lambda x: x > 0,
86 histMomentMax = pexConfig.Field(
87 doc =
"Maximum moment to consider",
90 check =
lambda x: x > 0,
92 histMomentMaxMultiplier = pexConfig.Field(
93 doc =
"Multiplier of mean for maximum moments histogram range",
96 check =
lambda x: x > 0,
98 histMomentClip = pexConfig.Field(
99 doc =
"Clipping threshold for moments histogram range",
102 check =
lambda x: x > 0,
104 histMomentMinMultiplier = pexConfig.Field(
105 doc =
"Multiplier of mean for minimum moments histogram range",
108 check =
lambda x: x > 0,
111 Clump = collections.namedtuple(
'Clump', [
'peak',
'x',
'y',
'ixx',
'ixy',
'iyy',
'a',
'b',
'c'])
114 """A functor to check whether a source has any flags set that should cause it to be labeled bad."""
116 def __init__(self, table, badFlags, fluxLim, fluxMax):
117 if table.getVersion() == 0:
119 self.
keys = [table.getSchema().find(name).key
for name
in badFlags]
120 self.keys.append(table.getCentroidFlagKey())
135 ConfigClass = SecondMomentStarSelectorConfig
138 """Construct a star selector that uses second moments
140 This is a naive algorithm and should be used with caution.
142 @param[in] config: An instance of SecondMomentStarSelectorConfig
147 """Return a list of PSF candidates that represent likely stars
149 A list of PSF candidates may be used by a PSF fitter to construct a PSF.
151 @param[in] exposure: the exposure containing the sources
152 @param[in] catalog: a SourceCatalog containing sources that may be stars
153 @param[in] matches: astrometric matches; ignored by this star selector
155 @return psfCandidateList: a list of PSF candidates.
162 isGoodSource =
CheckSource(catalog.getTable(), self.config.badFlags, self.config.fluxLim,
165 detector = exposure.getDetector()
167 mi = exposure.getMaskedImage()
175 ixx, iyy = s.getIxx(), s.getIyy()
177 if (ixx == ixx
and ixx < self.config.histMomentMax
and
178 iyy == iyy
and iyy < self.config.histMomentMax
and
180 iqqList.append(s.getIxx())
181 iqqList.append(s.getIyy())
183 iqqMean = stat.getValue(afwMath.MEANCLIP)
184 iqqStd = stat.getValue(afwMath.STDEVCLIP)
185 iqqMax = stat.getValue(afwMath.MAX)
187 iqqLimit =
max(iqqMean + self.config.histMomentClip*iqqStd,
188 self.config.histMomentMaxMultiplier*iqqMean)
190 if iqqLimit > iqqMax:
191 iqqLimit =
max(self.config.histMomentMinMultiplier*iqqMean, iqqMax)
193 psfHist =
_PsfShapeHistogram(detector=detector, xSize=self.config.histSize, ySize=self.config.histSize,
194 ixxMax=iqqLimit, iyyMax=iqqLimit)
196 if display
and displayExposure:
198 ds9.mtv(mi, frame=frame, title=
"PSF candidates")
200 with ds9.Buffering():
201 for source
in catalog:
202 if isGoodSource(source):
203 if psfHist.insert(source):
210 if display
and displayExposure:
211 ds9.dot(
"o", source.getX() - mi.getX0(),
212 source.getY() - mi.getY0(), frame=frame, ctype=ctype)
214 clumps = psfHist.getClumps(catalog.getTable().getVersion(), display=display)
222 psfCandidateList = []
224 pixToTanXYTransform =
None
225 if detector
is not None:
226 tanSys = detector.makeCameraSys(cameraGeom.TAN_PIXELS)
227 pixToTanXYTransform = detector.getTransformMap().get(tanSys)
232 for source
in catalog:
233 if not isGoodSource(source):
continue
234 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
235 if pixToTanXYTransform:
237 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
238 m = geomEllip.Quadrupole(Ixx, Iyy, Ixy)
239 m.transform(linTransform)
240 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
242 x, y = psfHist.momentsToPixel(Ixx, Iyy)
244 dx, dy = (x - clump.x), (y - clump.y)
246 if math.sqrt(clump.a*dx*dx + 2*clump.b*dx*dy + clump.c*dy*dy) < 2*self.config.clumpNSigma:
248 if not isGoodSource(source):
251 psfCandidate = algorithmsLib.makePsfCandidate(source, exposure)
256 if psfCandidate.getWidth() == 0:
257 psfCandidate.setBorderWidth(self.config.borderWidth)
258 psfCandidate.setWidth(self.config.kernelSize + 2*self.config.borderWidth)
259 psfCandidate.setHeight(self.config.kernelSize + 2*self.config.borderWidth)
261 im = psfCandidate.getMaskedImage().getImage()
264 psfCandidateList.append(psfCandidate)
266 if display
and displayExposure:
267 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
268 size=4, frame=frame, ctype=ds9.CYAN)
269 except Exception
as err:
273 return psfCandidateList
276 """A class to represent a histogram of (Ixx, Iyy)
278 def __init__(self, xSize=32, ySize=32, ixxMax=30, iyyMax=30, detector=None, xy0=afwGeom.Point2D(0,0)):
279 """Construct a _PsfShapeHistogram
281 The maximum seeing FWHM that can be tolerated is [xy]Max/2.35 pixels.
282 The 'resolution' of stars vs galaxies/CRs is provided by [xy]Size/[xy]Max.
283 A larger (better) resolution may thresh the peaks, but a smaller (worse)
284 resolution will allow stars and galaxies/CRs to mix. The disadvantages of
285 a larger (better) resolution can be compensated (some) by using multiple
288 @input[in] [xy]Size: the size of the psfImage (in pixels)
289 @input[in] ixxMax, iyyMax: the maximum values for I[xy][xy]
292 self._xMax, self.
_yMax = ixxMax, iyyMax
302 """Insert source into the histogram."""
304 ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy()
306 tanSys = self.detector.makeCameraSys(cameraGeom.TAN_PIXELS)
307 if tanSys
in self.detector.getTransformMap():
308 pixToTanXYTransform = self.detector.getTransformMap()[tanSys]
310 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
311 m = geomEllip.Quadrupole(ixx, iyy, ixy)
312 m.transform(linTransform)
313 ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy()
322 if i
in range(0, self._xSize)
and j
in range(0, self.
_ySize):
324 self._psfImage.set(i, j, self._psfImage.get(i, j) + 1)
333 x = ixx * self._xSize / self._xMax
338 """Given a peak position in self._psfImage, return the corresponding (Ixx, Iyy)"""
342 ixx = x*self._xMax/self._xSize
346 def getClumps(self, tableVersion, sigma=1.0, display=False):
348 raise RuntimeError(
"No candidate PSF sources")
354 width, height = psfImage.getWidth(), psfImage.getHeight()
359 subLargeImg = psfImage.Factory(largeImg, bbox, afwImage.LOCAL)
360 subLargeImg <<= psfImage
365 msk = afwImage.MaskU(largeImg.getDimensions())
367 var = afwImage.ImageF(largeImg.getDimensions())
369 mpsfImage = afwImage.MaskedImageF(largeImg, msk, var)
379 threshold = maxVal - sigma*math.sqrt(maxVal)
390 schema = afwTable.SourceTable.makeMinimalSchema()
391 schema.setVersion(tableVersion)
392 if tableVersion == 0:
393 psfImageConfig = SourceMeasurementConfig()
394 psfImageConfig.slots.centroid =
"centroid.sdss"
395 psfImageConfig.slots.psfFlux =
"flux.psf"
396 psfImageConfig.slots.apFlux =
"flux.naive"
397 psfImageConfig.slots.modelFlux =
None
398 psfImageConfig.slots.instFlux =
None
399 psfImageConfig.slots.shape =
"shape.sdss"
400 psfImageConfig.algorithms.names = [
"flags.pixel",
"shape.sdss",
401 "flux.psf",
"flux.naive"]
402 psfImageConfig.centroider.name =
"centroid.sdss"
403 psfImageConfig.algorithms[
"flux.naive"].radius = 3.0
404 psfImageConfig.validate()
405 task = SourceMeasurementTask(schema, config=psfImageConfig)
408 psfImageConfig.slots.centroid =
"base_SdssCentroid"
409 psfImageConfig.slots.psfFlux =
"base_PsfFlux"
410 psfImageConfig.slots.apFlux =
"base_NaiveFlux"
411 psfImageConfig.slots.modelFlux =
None
412 psfImageConfig.slots.instFlux =
None
413 psfImageConfig.slots.shape =
"base_SdssShape"
414 psfImageConfig.algorithms.names = [
"base_SdssCentroid",
"base_PsfFlux",
"base_NaiveFlux",
415 "base_PixelFlags",
"base_SdssShape"]
416 psfImageConfig.algorithms[
"base_NaiveFlux"].radius = 3.0
417 psfImageConfig.validate()
423 exposure.setPsf(algorithmsLib.DoubleGaussianPsf(11, 11, gaussianWidth))
425 ds.makeSources(catalog)
434 ds9.mtv(dispImage,title=
"PSF Selection Image", frame=frame)
440 IzzMax = (self._xSize/8.0)**2
443 task.run(exposure, catalog)
444 for i, source
in enumerate(catalog):
445 if source.getCentroidFlag():
447 x, y = source.getX(), source.getY()
449 apFluxes.append(source.getApFlux())
451 val = mpsfImage.getImage().get(int(x) + width, int(y) + height)
453 psfClumpIxx = source.getIxx()
454 psfClumpIxy = source.getIxy()
455 psfClumpIyy = source.getIyy()
459 ds9.pan(x, y, frame=frame)
461 ds9.dot(
"+", x, y, ctype=ds9.YELLOW, frame=frame)
462 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
463 ctype=ds9.YELLOW, frame=frame)
465 if psfClumpIxx < IzzMin
or psfClumpIyy < IzzMin:
466 psfClumpIxx =
max(psfClumpIxx, IzzMin)
467 psfClumpIyy =
max(psfClumpIyy, IzzMin)
469 ds9.dot(
"@:%g,%g,%g" % (psfClumpIxx, psfClumpIxy, psfClumpIyy), x, y,
470 ctype=ds9.RED, frame=frame)
472 det = psfClumpIxx*psfClumpIyy - psfClumpIxy*psfClumpIxy
474 a, b, c = psfClumpIyy/det, -psfClumpIxy/det, psfClumpIxx/det
475 except ZeroDivisionError:
476 a, b, c = 1e4, 0, 1e4
478 clumps.append(
Clump(peak=val, x=x, y=y, a=a, b=b, c=c,
479 ixx=psfClumpIxx, ixy=psfClumpIxy, iyy=psfClumpIyy))
482 msg =
"Failed to determine center of PSF clump"
485 raise RuntimeError(msg)
499 if clump.ixx < IzzMax
and clump.iyy < IzzMax:
500 goodClumps.append(clump)
503 if len(goodClumps) == 0:
507 iBestClump = numpy.argsort(apFluxes)[0]
508 clumps = [clumps[iBestClump]]
A subtask for measuring the properties of sources on a single exposure.
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.
Config class for single frame measurement driver task.
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 >())