def insert(self, source): """Insert source into the histogram.""" ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy() if self.detector: tanSys = self.detector.makeCameraSys(TAN_PIXELS) if tanSys in self.detector.getTransformMap(): pixToTanXYTransform = self.detector.getTransformMap()[tanSys] p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform( p).getLinear() m = Quadrupole(ixx, iyy, ixy) m.transform(linTransform) ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy() try: pixel = self.momentsToPixel(ixx, iyy) i = int(pixel[0]) j = int(pixel[1]) except: return 0 if i in range(0, self._xSize) and j in range(0, self._ySize): if i != 0 or j != 0: self._psfImage.set(i, j, self._psfImage.get(i, j) + 1) self._num += 1 return 1 # success return 0 # failure
def insert(self, source): """Insert source into the histogram.""" ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy() if self.detector: tanSys = self.detector.makeCameraSys(TAN_PIXELS) if tanSys in self.detector.getTransformMap(): pixToTanXYTransform = self.detector.getTransformMap()[tanSys] p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear() m = Quadrupole(ixx, iyy, ixy) m.transform(linTransform) ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy() try: pixel = self.momentsToPixel(ixx, iyy) i = int(pixel[0]) j = int(pixel[1]) except: return 0 if i in range(0, self._xSize) and j in range(0, self._ySize): if i != 0 or j != 0: self._psfImage.set(i, j, self._psfImage.get(i, j) + 1) self._num += 1 return 1 # success return 0 # failure
def selectStars(self, exposure, sourceCat, matches=None): """!Return a list of PSF candidates that represent likely stars A list of PSF candidates may be used by a PSF fitter to construct a PSF. \param[in] exposure the exposure containing the sources \param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) \param[in] matches astrometric matches; ignored by this star selector \return an lsst.pipe.base.Struct containing: - starCat catalog of selected stars (a subset of sourceCat) """ import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = lsstDebug.Info( __name__).displayExposure # display the Exposure + spatialCells plotMagSize = lsstDebug.Info( __name__).plotMagSize # display the magnitude-size relation dumpData = lsstDebug.Info( __name__).dumpData # dump data to pickle file? detector = exposure.getDetector() pixToTanXYTransform = None if detector is not None: tanSys = detector.makeCameraSys(TAN_PIXELS) pixToTanXYTransform = detector.getTransformMap().get(tanSys) # # Look at the distribution of stars in the magnitude-size plane # flux = sourceCat.get(self.config.sourceFluxField) xx = numpy.empty(len(sourceCat)) xy = numpy.empty_like(xx) yy = numpy.empty_like(xx) for i, source in enumerate(sourceCat): Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy() if pixToTanXYTransform: p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform( p).getLinear() m = Quadrupole(Ixx, Iyy, Ixy) m.transform(linTransform) Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy() xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy width = numpy.sqrt(0.5 * (xx + yy)) bad = reduce(lambda x, y: numpy.logical_or(x, sourceCat.get(y)), self.config.badFlags, False) bad = numpy.logical_or(bad, flux < self.config.fluxMin) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width))) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux))) bad = numpy.logical_or(bad, width < self.config.widthMin) bad = numpy.logical_or(bad, width > self.config.widthMax) if self.config.fluxMax > 0: bad = numpy.logical_or(bad, flux > self.config.fluxMax) good = numpy.logical_not(bad) if not numpy.any(good): raise RuntimeError( "No objects passed our cuts for consideration as psf stars") mag = -2.5 * numpy.log10(flux[good]) width = width[good] # # Look for the maximum in the size histogram, then search upwards for the minimum that separates # the initial peak (of, we presume, stars) from the galaxies # if dumpData: import os import pickle as pickle _ii = 0 while True: pickleFile = os.path.expanduser( os.path.join("~", "widths-%d.pkl" % _ii)) if not os.path.exists(pickleFile): break _ii += 1 with open(pickleFile, "wb") as fd: pickle.dump(mag, fd, -1) pickle.dump(width, fd, -1) centers, clusterId = _kcenters( width, nCluster=4, useMedian=True, widthStdAllowed=self.config.widthStdAllowed) if display and plotMagSize: fig = plot( mag, width, centers, clusterId, magType=self.config.sourceFluxField.split(".")[-1].title(), marker="+", markersize=3, markeredgewidth=None, ltype=':', clear=True) else: fig = None clusterId = _improveCluster( width, centers, clusterId, nsigma=self.config.nSigmaClip, widthStdAllowed=self.config.widthStdAllowed) if display and plotMagSize: plot(mag, width, centers, clusterId, marker="x", markersize=3, markeredgewidth=None, clear=False) stellar = (clusterId == 0) # # We know enough to plot, if so requested # frame = 0 if fig: if display and displayExposure: ds9.mtv(exposure.getMaskedImage(), frame=frame, title="PSF candidates") global eventHandler eventHandler = EventHandler(fig.get_axes()[0], mag, width, sourceCat.getX()[good], sourceCat.getY()[good], frames=[frame]) fig.show() while True: try: reply = input("continue? [c h(elp) q(uit) p(db)] ").strip() except EOFError: reply = None if not reply: reply = "c" if reply: if reply[0] == "h": print("""\ We cluster the points; red are the stellar candidates and the other colours are other clusters. Points labelled + are rejects from the cluster (only for cluster 0). At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in ds9. """) elif reply[0] == "p": import pdb pdb.set_trace() elif reply[0] == 'q': sys.exit(1) else: break if display and displayExposure: mi = exposure.getMaskedImage() with ds9.Buffering(): for i, source in enumerate(sourceCat): if good[i]: ctype = ds9.GREEN # star candidate else: ctype = ds9.RED # not star ds9.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(), frame=frame, ctype=ctype) starCat = SourceCatalog(sourceCat.table) goodSources = [s for g, s in zip(good, sourceCat) if g] for isStellar, source in zip(stellar, goodSources): if isStellar: starCat.append(source) return Struct(starCat=starCat, )
def selectStars(self, exposure, sourceCat, matches=None): """!Return a list of PSF candidates that represent likely stars A list of PSF candidates may be used by a PSF fitter to construct a PSF. @param[in] exposure the exposure containing the sources @param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) @param[in] matches astrometric matches; ignored by this star selector @return an lsst.pipe.base.Struct containing: - starCat catalog of selected stars (a subset of sourceCat) """ import lsstDebug display = lsstDebug.Info(__name__).display isGoodSource = CheckSource(sourceCat.getTable(), self.config.badFlags, self.config.fluxLim, self.config.fluxMax) detector = exposure.getDetector() mi = exposure.getMaskedImage() # # Create an Image of Ixx v. Iyy, i.e. a 2-D histogram # # Use stats on our Ixx/yy values to determine the xMax/yMax range for clump image iqqList = [] for s in sourceCat: ixx, iyy = s.getIxx(), s.getIyy() # ignore NaN and unrealistically large values if (ixx == ixx and ixx < self.config.histMomentMax and iyy == iyy and iyy < self.config.histMomentMax and isGoodSource(s)): iqqList.append(s.getIxx()) iqqList.append(s.getIyy()) stat = afwMath.makeStatistics( iqqList, afwMath.MEANCLIP | afwMath.STDEVCLIP | afwMath.MAX) iqqMean = stat.getValue(afwMath.MEANCLIP) iqqStd = stat.getValue(afwMath.STDEVCLIP) iqqMax = stat.getValue(afwMath.MAX) iqqLimit = max(iqqMean + self.config.histMomentClip * iqqStd, self.config.histMomentMaxMultiplier * iqqMean) # if the max value is smaller than our range, use max as the limit, but don't go below N*mean if iqqLimit > iqqMax: iqqLimit = max(self.config.histMomentMinMultiplier * iqqMean, iqqMax) psfHist = _PsfShapeHistogram(detector=detector, xSize=self.config.histSize, ySize=self.config.histSize, ixxMax=iqqLimit, iyyMax=iqqLimit) if display: frame = 0 ds9.mtv(mi, frame=frame, title="PSF candidates") ctypes = [] for source in sourceCat: good = isGoodSource(source) if good: notRejected = psfHist.insert(source) if display: if good: if notRejected: ctypes.append(ds9.GREEN) # good else: ctypes.append(ds9.MAGENTA) # rejected else: ctypes.append(ds9.RED) # bad if display: with ds9.Buffering(): for source, ctype in zip(sourceCat, ctypes): ds9.dot("o", source.getX() - mi.getX0(), source.getY() - mi.getY0(), frame=frame, ctype=ctype) clumps = psfHist.getClumps(display=display) # # Go through and find all the PSF-like objects # # We'll split the image into a number of cells, each of which contributes only # one PSF candidate star # starCat = SourceCatalog(sourceCat.table) pixToTanXYTransform = None if detector is not None: tanSys = detector.makeCameraSys(TAN_PIXELS) pixToTanXYTransform = detector.getTransformMap().get(tanSys) # psf candidate shapes must lie within this many RMS of the average shape # N.b. if Ixx == Iyy, Ixy = 0 the criterion is # dx^2 + dy^2 < self.config.clumpNSigma*(Ixx + Iyy) == 2*self.config.clumpNSigma*Ixx for source in sourceCat: if not isGoodSource(source): continue Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy() if pixToTanXYTransform: p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform( p).getLinear() m = Quadrupole(Ixx, Iyy, Ixy) m.transform(linTransform) Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy() x, y = psfHist.momentsToPixel(Ixx, Iyy) for clump in clumps: dx, dy = (x - clump.x), (y - clump.y) if math.sqrt(clump.a * dx * dx + 2 * clump.b * dx * dy + clump.c * dy * dy) < 2 * self.config.clumpNSigma: # A test for > would be confused by NaN if not isGoodSource(source): continue try: psfCandidate = algorithmsLib.makePsfCandidate( source, exposure) # The setXXX methods are class static, but it's convenient to call them on # an instance as we don't know Exposure's pixel type # (and hence psfCandidate's exact type) if psfCandidate.getWidth() == 0: psfCandidate.setBorderWidth( self.config.borderWidth) psfCandidate.setWidth(self.config.kernelSize + 2 * self.config.borderWidth) psfCandidate.setHeight(self.config.kernelSize + 2 * self.config.borderWidth) im = psfCandidate.getMaskedImage().getImage() if not numpy.isfinite( afwMath.makeStatistics( im, afwMath.MAX).getValue()): continue starCat.append(source) if display: ds9.dot("o", source.getX() - mi.getX0(), source.getY() - mi.getY0(), size=4, frame=frame, ctype=ds9.CYAN) except Exception as err: self.log.error("Failed on source %s: %s" % (source.getId(), err)) break return Struct(starCat=starCat, )
def selectStars(self, exposure, sourceCat, matches=None): """!Return a list of PSF candidates that represent likely stars A list of PSF candidates may be used by a PSF fitter to construct a PSF. \param[in] exposure the exposure containing the sources \param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) \param[in] matches astrometric matches; ignored by this star selector \return an lsst.pipe.base.Struct containing: - starCat catalog of selected stars (a subset of sourceCat) """ import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells plotMagSize = lsstDebug.Info(__name__).plotMagSize # display the magnitude-size relation dumpData = lsstDebug.Info(__name__).dumpData # dump data to pickle file? detector = exposure.getDetector() pixToTanXYTransform = None if detector is not None: tanSys = detector.makeCameraSys(TAN_PIXELS) pixToTanXYTransform = detector.getTransformMap().get(tanSys) # # Look at the distribution of stars in the magnitude-size plane # flux = sourceCat.get(self.config.sourceFluxField) xx = numpy.empty(len(sourceCat)) xy = numpy.empty_like(xx) yy = numpy.empty_like(xx) for i, source in enumerate(sourceCat): Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy() if pixToTanXYTransform: p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear() m = Quadrupole(Ixx, Iyy, Ixy) m.transform(linTransform) Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy() xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy width = numpy.sqrt(0.5*(xx + yy)) bad = reduce(lambda x, y: numpy.logical_or(x, sourceCat.get(y)), self.config.badFlags, False) bad = numpy.logical_or(bad, flux < self.config.fluxMin) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width))) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux))) bad = numpy.logical_or(bad, width < self.config.widthMin) bad = numpy.logical_or(bad, width > self.config.widthMax) if self.config.fluxMax > 0: bad = numpy.logical_or(bad, flux > self.config.fluxMax) good = numpy.logical_not(bad) if not numpy.any(good): raise RuntimeError("No objects passed our cuts for consideration as psf stars") mag = -2.5*numpy.log10(flux[good]) width = width[good] # # Look for the maximum in the size histogram, then search upwards for the minimum that separates # the initial peak (of, we presume, stars) from the galaxies # if dumpData: import os import cPickle as pickle _ii = 0 while True: pickleFile = os.path.expanduser(os.path.join("~", "widths-%d.pkl" % _ii)) if not os.path.exists(pickleFile): break _ii += 1 with open(pickleFile, "wb") as fd: pickle.dump(mag, fd, -1) pickle.dump(width, fd, -1) centers, clusterId = _kcenters(width, nCluster=4, useMedian=True, widthStdAllowed=self.config.widthStdAllowed) if display and plotMagSize and pyplot: fig = plot(mag, width, centers, clusterId, magType=self.config.sourceFluxField.split(".")[-1].title(), marker="+", markersize=3, markeredgewidth=None, ltype=':', clear=True) else: fig = None clusterId = _improveCluster(width, centers, clusterId, nsigma = self.config.nSigmaClip, widthStdAllowed=self.config.widthStdAllowed) if display and plotMagSize and pyplot: plot(mag, width, centers, clusterId, marker="x", markersize=3, markeredgewidth=None, clear=False) stellar = (clusterId == 0) # # We know enough to plot, if so requested # frame = 0 if fig: if display and displayExposure: ds9.mtv(exposure.getMaskedImage(), frame=frame, title="PSF candidates") global eventHandler eventHandler = EventHandler(fig.get_axes()[0], mag, width, sourceCat.getX()[good], sourceCat.getY()[good], frames=[frame]) fig.show() #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- while True: try: reply = raw_input("continue? [c h(elp) q(uit) p(db)] ").strip() except EOFError: reply = None if not reply: reply = "c" if reply: if reply[0] == "h": print """\ We cluster the points; red are the stellar candidates and the other colours are other clusters. Points labelled + are rejects from the cluster (only for cluster 0). At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in ds9. """ elif reply[0] == "p": import pdb pdb.set_trace() elif reply[0] == 'q': sys.exit(1) else: break if display and displayExposure: mi = exposure.getMaskedImage() with ds9.Buffering(): for i, source in enumerate(sourceCat): if good[i]: ctype = ds9.GREEN # star candidate else: ctype = ds9.RED # not star ds9.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(), frame=frame, ctype=ctype) starCat = SourceCatalog(sourceCat.table) goodSources = [s for g, s in zip(good, sourceCat) if g] for isStellar, source in zip(stellar, goodSources): if isStellar: starCat.append(source) return Struct( starCat = starCat, )
def selectStars(self, exposure, sourceCat, matches=None): """!Return a list of PSF candidates that represent likely stars A list of PSF candidates may be used by a PSF fitter to construct a PSF. @param[in] exposure the exposure containing the sources @param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) @param[in] matches astrometric matches; ignored by this star selector @return an lsst.pipe.base.Struct containing: - starCat catalog of selected stars (a subset of sourceCat) """ import lsstDebug display = lsstDebug.Info(__name__).display isGoodSource = CheckSource(sourceCat.getTable(), self.config.badFlags, self.config.fluxLim, self.config.fluxMax) detector = exposure.getDetector() mi = exposure.getMaskedImage() # # Create an Image of Ixx v. Iyy, i.e. a 2-D histogram # # Use stats on our Ixx/yy values to determine the xMax/yMax range for clump image iqqList = [] for s in sourceCat: ixx, iyy = s.getIxx(), s.getIyy() # ignore NaN and unrealistically large values if (ixx == ixx and ixx < self.config.histMomentMax and iyy == iyy and iyy < self.config.histMomentMax and isGoodSource(s)): iqqList.append(s.getIxx()) iqqList.append(s.getIyy()) stat = afwMath.makeStatistics(iqqList, afwMath.MEANCLIP | afwMath.STDEVCLIP | afwMath.MAX) iqqMean = stat.getValue(afwMath.MEANCLIP) iqqStd = stat.getValue(afwMath.STDEVCLIP) iqqMax = stat.getValue(afwMath.MAX) iqqLimit = max(iqqMean + self.config.histMomentClip*iqqStd, self.config.histMomentMaxMultiplier*iqqMean) # if the max value is smaller than our range, use max as the limit, but don't go below N*mean if iqqLimit > iqqMax: iqqLimit = max(self.config.histMomentMinMultiplier*iqqMean, iqqMax) psfHist = _PsfShapeHistogram(detector=detector, xSize=self.config.histSize, ySize=self.config.histSize, ixxMax=iqqLimit, iyyMax=iqqLimit) if display: frame = 0 ds9.mtv(mi, frame=frame, title="PSF candidates") with ds9.Buffering(): for source in sourceCat: if isGoodSource(source): if psfHist.insert(source): # n.b. this call has the side effect of inserting ctype = ds9.GREEN # good else: ctype = ds9.MAGENTA # rejected else: ctype = ds9.RED # bad if display: ds9.dot("o", source.getX() - mi.getX0(), source.getY() - mi.getY0(), frame=frame, ctype=ctype) clumps = psfHist.getClumps(display=display) # # Go through and find all the PSF-like objects # # We'll split the image into a number of cells, each of which contributes only # one PSF candidate star # starCat = SourceCatalog(sourceCat.table) pixToTanXYTransform = None if detector is not None: tanSys = detector.makeCameraSys(TAN_PIXELS) pixToTanXYTransform = detector.getTransformMap().get(tanSys) # psf candidate shapes must lie within this many RMS of the average shape # N.b. if Ixx == Iyy, Ixy = 0 the criterion is # dx^2 + dy^2 < self.config.clumpNSigma*(Ixx + Iyy) == 2*self.config.clumpNSigma*Ixx for source in sourceCat: if not isGoodSource(source): continue Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy() if pixToTanXYTransform: p = afwGeom.Point2D(source.getX(), source.getY()) linTransform = pixToTanXYTransform.linearizeForwardTransform(p).getLinear() m = Quadrupole(Ixx, Iyy, Ixy) m.transform(linTransform) Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy() x, y = psfHist.momentsToPixel(Ixx, Iyy) for clump in clumps: dx, dy = (x - clump.x), (y - clump.y) if math.sqrt(clump.a*dx*dx + 2*clump.b*dx*dy + clump.c*dy*dy) < 2*self.config.clumpNSigma: # A test for > would be confused by NaN if not isGoodSource(source): continue try: psfCandidate = algorithmsLib.makePsfCandidate(source, exposure) # The setXXX methods are class static, but it's convenient to call them on # an instance as we don't know Exposure's pixel type # (and hence psfCandidate's exact type) if psfCandidate.getWidth() == 0: psfCandidate.setBorderWidth(self.config.borderWidth) psfCandidate.setWidth(self.config.kernelSize + 2*self.config.borderWidth) psfCandidate.setHeight(self.config.kernelSize + 2*self.config.borderWidth) im = psfCandidate.getMaskedImage().getImage() if not numpy.isfinite(afwMath.makeStatistics(im, afwMath.MAX).getValue()): continue starCat.append(source) if display: ds9.dot("o", source.getX() - mi.getX0(), source.getY() - mi.getY0(), size=4, frame=frame, ctype=ds9.CYAN) except Exception as err: self.log.error("Failed on source %s: %s" % (source.getId(), err)) break return Struct( starCat = starCat, )