26 import matplotlib.pyplot
as pyplot
39 from .
import algorithmsLib
43 fluxMin = pexConfig.Field(
44 doc =
"specify the minimum psfFlux for good Psf Candidates",
48 check =
lambda x: x >= 0.0,
50 fluxMax = pexConfig.Field(
51 doc =
"specify the maximum psfFlux for good Psf Candidates (ignored if == 0)",
54 check =
lambda x: x >= 0.0,
56 kernelSize = pexConfig.Field(
57 doc =
"size of the Psf kernel to create",
61 borderWidth = pexConfig.Field(
62 doc =
"number of pixels to ignore around the edge of PSF candidate postage stamps",
66 badFlags = pexConfig.ListField(
67 doc =
"List of flags which cause a source to be rejected as bad",
69 default = [
"base_PixelFlags_flag_edge",
70 "base_PixelFlags_flag_interpolatedCenter",
71 "base_PixelFlags_flag_saturatedCenter",
72 "base_PixelFlags_flag_crCenter",
75 widthMin = pexConfig.Field(
76 doc =
"minimum width to include in histogram",
79 check =
lambda x: x >= 0.0,
81 widthMax = pexConfig.Field(
82 doc =
"maximum width to include in histogram",
85 check =
lambda x: x >= 0.0,
87 sourceFluxField = pexConfig.Field(
88 doc =
"Name of field in Source to use for flux measurement",
90 default =
"base_GaussianFlux_flux"
94 pexConfig.Config.validate(self)
96 raise pexConfig.FieldValidationError(
"widthMin (%f) > widthMax (%f)"
100 """A class to handle key strokes with matplotlib displays"""
101 def __init__(self, axes, xs, ys, x, y, frames=[0]):
109 self.
cid = self.axes.figure.canvas.mpl_connect(
'key_press_event', self)
112 if ev.inaxes != self.
axes:
115 if ev.key
and ev.key
in (
"p"):
116 dist = numpy.hypot(self.
xs - ev.xdata, self.
ys - ev.ydata)
117 dist[numpy.where(numpy.isnan(dist))] = 1e30
119 which = numpy.where(dist ==
min(dist))
124 ds9.pan(x, y, frame=frame)
125 ds9.cmdBuffer.flush()
132 """Return a vector of centerIds based on their distance to the centers"""
133 assert len(centers) > 0
135 minDist = numpy.nan*numpy.ones_like(yvec)
136 clusterId = numpy.empty_like(yvec)
137 clusterId.dtype = int
139 for i, mean
in enumerate(centers):
140 dist = abs(yvec - mean)
142 update = dist == dist
144 update = dist < minDist
146 minDist[update] = dist[update]
147 clusterId[update] = i
152 """A classic k-means algorithm, clustering yvec into nCluster clusters
154 Return the set of centres, and the cluster ID for each of the points
156 If useMedian is true, use the median of the cluster as its centre, rather than
159 Serge Monkewitz points out that there other (maybe smarter) ways of seeding the means:
160 "e.g. why not use the Forgy or random partition initialization methods"
161 however, the approach adopted here seems to work well for the particular sorts of things
162 we're clustering in this application
167 mean0 = sorted(yvec)[len(yvec)//10]
168 centers = mean0*numpy.arange(1, nCluster + 1)
170 func = numpy.median
if useMedian
else numpy.mean
172 clusterId = numpy.zeros_like(yvec) - 1
173 clusterId.dtype = int
175 oclusterId = clusterId
178 if numpy.all(clusterId == oclusterId):
181 for i
in range(nCluster):
182 centers[i] = func(yvec[clusterId == i])
184 return centers, clusterId
186 def _improveCluster(yvec, centers, clusterId, nsigma=2.0, nIteration=10, clusterNum=0):
187 """Improve our estimate of one of the clusters (clusterNum) by sigma-clipping around its median"""
189 nMember =
sum(clusterId == clusterNum)
192 for iter
in range(nIteration):
193 old_nMember = nMember
195 inCluster0 = clusterId == clusterNum
196 yv = yvec[inCluster0]
198 centers[clusterNum] = numpy.median(yv)
199 stdev = numpy.std(yv)
202 stdev_iqr = 0.741*(syv[int(0.75*nMember)] - syv[int(0.25*nMember)])
204 sd = stdev
if stdev < stdev_iqr
else stdev_iqr
207 print "sigma(iqr) = %.3f, sigma = %.3f" % (stdev_iqr, numpy.std(yv))
208 newCluster0 = abs(yvec - centers[clusterNum]) < nsigma*sd
209 clusterId[numpy.logical_and(inCluster0, newCluster0)] = clusterNum
210 clusterId[numpy.logical_and(inCluster0, numpy.logical_not(newCluster0))] = -1
212 nMember =
sum(clusterId == clusterNum)
213 if nMember == old_nMember:
218 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
223 fig = pyplot.figure()
230 axes = fig.add_axes((0.1, 0.1, 0.85, 0.80));
232 xmin = sorted(mag)[int(0.05*len(mag))]
233 xmax = sorted(mag)[int(0.95*len(mag))]
235 axes.set_xlim(-17.5, -13)
236 axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
239 colors = [
"r", "g", "b", "c", "m", "k",]
240 for k, mean
in enumerate(centers):
242 axes.plot(axes.get_xlim(), (mean, mean,),
"k%s" % ltype)
245 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
246 color=colors[k%len(colors)])
248 l = (clusterId == -1)
249 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
253 axes.set_xlabel(
"model")
254 axes.set_ylabel(
r"$\sqrt{I_{xx} + I_{yy}}$")
262 A measurePsfTask star selector
264 ConfigClass = ObjectSizeStarSelectorConfig
268 Construct a star selector that looks for a cluster of small objects in a size-magnitude plot.
270 \param[in] config An instance of ObjectSizeStarSelectorConfig
282 """!Return a list of PSF candidates that represent likely stars
284 A list of PSF candidates may be used by a PSF fitter to construct a PSF.
286 \param[in] exposure the exposure containing the sources
287 \param[in] catalog a SourceCatalog containing sources that may be stars
288 \param[in] matches astrometric matches; ignored by this star selector
290 \return psfCandidateList a list of PSF candidates.
299 logger =
pexLogging.Log(pexLogging.getDefaultLog(),
"meas.algorithms.objectSizeStarSelector")
301 detector = exposure.getDetector()
302 pixToTanXYTransform =
None
303 if detector
is not None:
304 tanSys = detector.makeCameraSys(cameraGeom.TAN_PIXELS)
305 pixToTanXYTransform = detector.getTransformMap().get(tanSys)
310 if catalog.getVersion() == 0
and self.
_sourceFluxField ==
"base_GaussianFlux_flux":
311 flux = catalog.get(
"flux.gaussian")
315 xx = numpy.empty(len(catalog))
316 xy = numpy.empty_like(xx)
317 yy = numpy.empty_like(xx)
318 for i, source
in enumerate(catalog):
319 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
320 if pixToTanXYTransform:
322 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
323 m = geomEllip.Quadrupole(Ixx, Iyy, Ixy)
324 m.transform(linTransform)
325 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
327 xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy
329 width = numpy.sqrt(xx + yy)
331 if catalog.getVersion() == 0:
336 bad = reduce(
lambda x, y: numpy.logical_or(x, catalog.get(y)), badFlags,
False)
337 bad = numpy.logical_or(bad, flux < self.
_fluxMin)
338 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width)))
339 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux)))
340 bad = numpy.logical_or(bad, width < self.
_widthMin)
341 bad = numpy.logical_or(bad, width > self.
_widthMax)
343 bad = numpy.logical_or(bad, flux > self.
_fluxMax)
344 good = numpy.logical_not(bad)
346 if not numpy.any(good):
347 raise RuntimeError(
"No objects passed our cuts for consideration as psf stars")
349 mag = -2.5*numpy.log10(flux[good])
356 import os, cPickle
as pickle
359 pickleFile = os.path.expanduser(os.path.join(
"~",
"widths-%d.pkl" % _ii))
360 if not os.path.exists(pickleFile):
364 with open(pickleFile,
"wb")
as fd:
365 pickle.dump(mag, fd, -1)
366 pickle.dump(width, fd, -1)
368 centers, clusterId =
_kcenters(width, nCluster=4, useMedian=
True)
370 if display
and plotMagSize
and pyplot:
371 fig =
plot(mag, width, centers, clusterId,
372 marker=
"+", markersize=3, markeredgewidth=
None, ltype=
':', clear=
True)
378 if display
and plotMagSize
and pyplot:
379 plot(mag, width, centers, clusterId, marker=
"x", markersize=3, markeredgewidth=
None)
381 stellar = (clusterId == 0)
388 if display
and displayExposure:
389 ds9.mtv(exposure.getMaskedImage(), frame=frame, title=
"PSF candidates")
392 eventHandler =
EventHandler(fig.get_axes()[0], mag, width,
393 catalog.getX()[good], catalog.getY()[good], frames=[frame])
401 reply = raw_input(
"continue? [c h(elp) q(uit) p(db)] ").strip()
408 We cluster the points; red are the stellar candidates and the other colours are other clusters.
409 Points labelled + are rejects from the cluster (only for cluster 0).
411 At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text
413 If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in ds9.
415 elif reply[0] ==
"p":
416 import pdb; pdb.set_trace()
417 elif reply[0] ==
'q':
422 if display
and displayExposure:
423 mi = exposure.getMaskedImage()
425 with ds9.Buffering():
426 for i, source
in enumerate(catalog):
432 ds9.dot(
"+", source.getX() - mi.getX0(),
433 source.getY() - mi.getY0(), frame=frame, ctype=ctype)
437 with ds9.Buffering():
438 psfCandidateList = []
439 for isStellar, source
in zip(stellar, [s
for g, s
in zip(good, catalog)
if g]):
444 psfCandidate = algorithmsLib.makePsfCandidate(source, exposure)
448 if psfCandidate.getWidth() == 0:
453 im = psfCandidate.getMaskedImage().getImage()
455 if not numpy.isfinite(vmax):
457 psfCandidateList.append(psfCandidate)
459 if display
and displayExposure:
460 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
461 size=4, frame=frame, ctype=ds9.CYAN)
462 except Exception
as err:
463 logger.log(pexLogging.Log.INFO,
464 "Failed to make a psfCandidate from source %d: %s" % (source.getId(), err))
466 return psfCandidateList
468 starSelectorRegistry.register(
"objectSize", ObjectSizeStarSelector)
def selectStars
Return a list of PSF candidates that represent likely stars.
A measurePsfTask star selector.
a place to record messages and descriptions of the state of processing.
def __init__
Construct a star selector that looks for a cluster of small objects in a size-magnitude plot...
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.