26 import matplotlib.pyplot
as pyplot
38 from .
import algorithmsLib
42 fluxMin = pexConfig.Field(
43 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 kernelSize = pexConfig.Field(
56 doc =
"size of the Psf kernel to create",
60 borderWidth = pexConfig.Field(
61 doc =
"number of pixels to ignore around the edge of PSF candidate postage stamps",
65 badFlags = pexConfig.ListField(
66 doc =
"List of flags which cause a source to be rejected as bad",
68 default = [
"base_PixelFlags_flag_edge",
69 "base_PixelFlags_flag_interpolatedCenter",
70 "base_PixelFlags_flag_saturatedCenter",
71 "base_PixelFlags_flag_crCenter",
74 widthMin = pexConfig.Field(
75 doc =
"minimum width to include in histogram",
78 check =
lambda x: x >= 0.0,
80 widthMax = pexConfig.Field(
81 doc =
"maximum width to include in histogram",
84 check =
lambda x: x >= 0.0,
86 sourceFluxField = pexConfig.Field(
87 doc =
"Name of field in Source to use for flux measurement",
89 default =
"base_GaussianFlux_flux"
93 pexConfig.Config.validate(self)
95 raise pexConfig.FieldValidationError(
"widthMin (%f) > widthMax (%f)"
99 """A class to handle key strokes with matplotlib displays"""
100 def __init__(self, axes, xs, ys, x, y, frames=[0]):
108 self.
cid = self.axes.figure.canvas.mpl_connect(
'key_press_event', self)
111 if ev.inaxes != self.
axes:
114 if ev.key
and ev.key
in (
"p"):
115 dist = numpy.hypot(self.
xs - ev.xdata, self.
ys - ev.ydata)
116 dist[numpy.where(numpy.isnan(dist))] = 1e30
118 which = numpy.where(dist == min(dist))
123 ds9.pan(x, y, frame=frame)
124 ds9.cmdBuffer.flush()
131 """Return a vector of centerIds based on their distance to the centers"""
132 assert len(centers) > 0
134 minDist = numpy.nan*numpy.ones_like(yvec)
135 clusterId = numpy.empty_like(yvec)
136 clusterId.dtype = int
138 for i, mean
in enumerate(centers):
139 dist = abs(yvec - mean)
141 update = dist == dist
143 update = dist < minDist
145 minDist[update] = dist[update]
146 clusterId[update] = i
151 """A classic k-means algorithm, clustering yvec into nCluster clusters
153 Return the set of centres, and the cluster ID for each of the points
155 If useMedian is true, use the median of the cluster as its centre, rather than
158 Serge Monkewitz points out that there other (maybe smarter) ways of seeding the means:
159 "e.g. why not use the Forgy or random partition initialization methods"
160 however, the approach adopted here seems to work well for the particular sorts of things
161 we're clustering in this application
166 mean0 = sorted(yvec)[len(yvec)//10]
167 centers = mean0*numpy.arange(1, nCluster + 1)
169 func = numpy.median
if useMedian
else numpy.mean
171 clusterId = numpy.zeros_like(yvec) - 1
172 clusterId.dtype = int
174 oclusterId = clusterId
177 if numpy.all(clusterId == oclusterId):
180 for i
in range(nCluster):
181 centers[i] = func(yvec[clusterId == i])
183 return centers, clusterId
185 def _improveCluster(yvec, centers, clusterId, nsigma=2.0, nIteration=10, clusterNum=0):
186 """Improve our estimate of one of the clusters (clusterNum) by sigma-clipping around its median"""
188 nMember =
sum(clusterId == clusterNum)
191 for iter
in range(nIteration):
192 old_nMember = nMember
194 inCluster0 = clusterId == clusterNum
195 yv = yvec[inCluster0]
197 centers[clusterNum] = numpy.median(yv)
198 stdev = numpy.std(yv)
201 stdev_iqr = 0.741*(syv[int(0.75*nMember)] - syv[int(0.25*nMember)])
203 sd = stdev
if stdev < stdev_iqr
else stdev_iqr
206 print "sigma(iqr) = %.3f, sigma = %.3f" % (stdev_iqr, numpy.std(yv))
207 newCluster0 = abs(yvec - centers[clusterNum]) < nsigma*sd
208 clusterId[numpy.logical_and(inCluster0, newCluster0)] = clusterNum
209 clusterId[numpy.logical_and(inCluster0, numpy.logical_not(newCluster0))] = -1
211 nMember =
sum(clusterId == clusterNum)
212 if nMember == old_nMember:
217 def plot(mag, width, centers, clusterId, marker="o", markersize=2, markeredgewidth=0, ltype='-',
222 fig = pyplot.figure()
229 axes = fig.add_axes((0.1, 0.1, 0.85, 0.80));
231 xmin = sorted(mag)[int(0.05*len(mag))]
232 xmax = sorted(mag)[int(0.95*len(mag))]
234 axes.set_xlim(-17.5, -13)
235 axes.set_xlim(xmin - 0.1*(xmax - xmin), xmax + 0.1*(xmax - xmin))
238 colors = [
"r", "g", "b", "c", "m", "k",]
239 for k, mean
in enumerate(centers):
241 axes.plot(axes.get_xlim(), (mean, mean,),
"k%s" % ltype)
244 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
245 color=colors[k%len(colors)])
247 l = (clusterId == -1)
248 axes.plot(mag[l], width[l], marker, markersize=markersize, markeredgewidth=markeredgewidth,
252 axes.set_xlabel(
"model")
253 axes.set_ylabel(
r"$\sqrt{I_{xx} + I_{yy}}$")
261 A measurePsfTask star selector
263 ConfigClass = ObjectSizeStarSelectorConfig
267 Construct a star selector that looks for a cluster of small objects in a size-magnitude plot.
269 \param[in] config An instance of ObjectSizeStarSelectorConfig
281 """!Return a list of PSF candidates that represent likely stars
283 A list of PSF candidates may be used by a PSF fitter to construct a PSF.
285 \param[in] exposure the exposure containing the sources
286 \param[in] catalog a SourceCatalog containing sources that may be stars
287 \param[in] matches astrometric matches; ignored by this star selector
289 \return psfCandidateList a list of PSF candidates.
298 logger =
pexLogging.Log(pexLogging.getDefaultLog(),
"meas.algorithms.objectSizeStarSelector")
300 detector = exposure.getDetector()
301 pixToTanXYTransform =
None
302 if detector
is not None:
303 tanSys = detector.makeCameraSys(cameraGeom.TAN_PIXELS)
304 pixToTanXYTransform = detector.getTransformMap().get(tanSys)
310 xx = numpy.empty(len(catalog))
311 xy = numpy.empty_like(xx)
312 yy = numpy.empty_like(xx)
313 for i, source
in enumerate(catalog):
314 Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
315 if pixToTanXYTransform:
317 linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear()
318 m = geomEllip.Quadrupole(Ixx, Iyy, Ixy)
319 m.transform(linTransform)
320 Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()
322 xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy
324 width = numpy.sqrt(xx + yy)
328 bad = reduce(
lambda x, y: numpy.logical_or(x, catalog.get(y)), badFlags,
False)
329 bad = numpy.logical_or(bad, flux < self.
_fluxMin)
330 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width)))
331 bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux)))
332 bad = numpy.logical_or(bad, width < self.
_widthMin)
333 bad = numpy.logical_or(bad, width > self.
_widthMax)
335 bad = numpy.logical_or(bad, flux > self.
_fluxMax)
336 good = numpy.logical_not(bad)
338 if not numpy.any(good):
339 raise RuntimeError(
"No objects passed our cuts for consideration as psf stars")
341 mag = -2.5*numpy.log10(flux[good])
348 import os, cPickle
as pickle
351 pickleFile = os.path.expanduser(os.path.join(
"~",
"widths-%d.pkl" % _ii))
352 if not os.path.exists(pickleFile):
356 with open(pickleFile,
"wb")
as fd:
357 pickle.dump(mag, fd, -1)
358 pickle.dump(width, fd, -1)
360 centers, clusterId =
_kcenters(width, nCluster=4, useMedian=
True)
362 if display
and plotMagSize
and pyplot:
363 fig =
plot(mag, width, centers, clusterId,
364 marker=
"+", markersize=3, markeredgewidth=
None, ltype=
':', clear=
True)
370 if display
and plotMagSize
and pyplot:
371 plot(mag, width, centers, clusterId, marker=
"x", markersize=3, markeredgewidth=
None)
373 stellar = (clusterId == 0)
380 if display
and displayExposure:
381 ds9.mtv(exposure.getMaskedImage(), frame=frame, title=
"PSF candidates")
384 eventHandler =
EventHandler(fig.get_axes()[0], mag, width,
385 catalog.getX()[good], catalog.getY()[good], frames=[frame])
393 reply = raw_input(
"continue? [c h(elp) q(uit) p(db)] ").strip()
400 We cluster the points; red are the stellar candidates and the other colours are other clusters.
401 Points labelled + are rejects from the cluster (only for cluster 0).
403 At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text
405 If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in ds9.
407 elif reply[0] ==
"p":
408 import pdb; pdb.set_trace()
409 elif reply[0] ==
'q':
414 if display
and displayExposure:
415 mi = exposure.getMaskedImage()
417 with ds9.Buffering():
418 for i, source
in enumerate(catalog):
424 ds9.dot(
"+", source.getX() - mi.getX0(),
425 source.getY() - mi.getY0(), frame=frame, ctype=ctype)
429 with ds9.Buffering():
430 psfCandidateList = []
431 for isStellar, source
in zip(stellar, [s
for g, s
in zip(good, catalog)
if g]):
436 psfCandidate = algorithmsLib.makePsfCandidate(source, exposure)
440 if psfCandidate.getWidth() == 0:
445 im = psfCandidate.getMaskedImage().getImage()
447 if not numpy.isfinite(vmax):
449 psfCandidateList.append(psfCandidate)
451 if display
and displayExposure:
452 ds9.dot(
"o", source.getX() - mi.getX0(), source.getY() - mi.getY0(),
453 size=4, frame=frame, ctype=ds9.CYAN)
454 except Exception
as err:
455 logger.log(pexLogging.Log.INFO,
456 "Failed to make a psfCandidate from source %d: %s" % (source.getId(), err))
458 return psfCandidateList
460 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.
boost::enable_if< typename ExpressionTraits< Scalar >::IsScalar, Scalar >::type sum(Scalar const &scalar)
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.