def makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm): """!Make a Transform whose forward direction converts PIXELS to TAN_PIXELS for one detector PIXELS and TAN_PIXELS are defined in @ref afwCameraGeomCoordSys in doc/cameraGeom.dox @param[in] bbox detector bounding box (an lsst.afw.geom.Box2I) @param[in] orientation orientation of detector in focal plane (an lsst.afw.cameraGeom.Orientation) @param[in] focalPlaneToField an lsst.afw.geom.Transform that converts from focal plane (mm) to field angle coordinates (radians) in the forward direction @param[in] pixelSizeMm size of the pixel in mm in X and Y (an lsst.afw.geom.Extent2D) @return a TransformPoint2ToPoint2 whose forward direction converts PIXELS to TAN_PIXELS """ pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) pixelToField = pixelToFocalPlane.then(focalPlaneToField) # fieldToTanPix is affine and matches fieldToPix at field center # Note: focal plane to field angle is typically a radial transform, # and linearizing the inverse transform of that may fail, # so linearize the forward direction instead. (pixelToField is pixelToFocalPlane, # an affine transform, followed by focalPlaneToField, # so the same consideration applies to pixelToField) pixAtFieldCtr = pixelToField.applyInverse(afwGeom.Point2D(0, 0)) tanPixToFieldAffine = afwGeom.linearizeTransform(pixelToField, pixAtFieldCtr) fieldToTanPix = afwGeom.makeTransform(tanPixToFieldAffine.invert()) return pixelToField.then(fieldToTanPix)
def plantSources(x0, y0, nx, ny, sky, nObj, wid, detector, useRandom=False): pixToTanPix = detector.getTransform(cameraGeom.PIXELS, cameraGeom.TAN_PIXELS) img0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) img = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) ixx0, iyy0, ixy0 = wid * wid, wid * wid, 0.0 edgeBuffer = 40.0 * wid flux = 1.0e4 nkx, nky = int(10 * wid) + 1, int(10 * wid) + 1 xhwid, yhwid = nkx // 2, nky // 2 nRow = int(math.sqrt(nObj)) xstep = (nx - 1 - 0.0 * edgeBuffer) // (nRow + 1) ystep = (ny - 1 - 0.0 * edgeBuffer) // (nRow + 1) if useRandom: nObj = nRow * nRow goodAdded0 = [] goodAdded = [] for i in range(nObj): # get our position if useRandom: xcen0, ycen0 = np.random.uniform(nx), np.random.uniform(ny) else: xcen0, ycen0 = xstep * ( (i % nRow) + 1), ystep * (int(i / nRow) + 1) ixcen0, iycen0 = int(xcen0), int(ycen0) # distort position and shape pTan = afwGeom.Point2D(xcen0, ycen0) p = pixToTanPix.applyInverse(pTan) linTransform = afwGeom.linearizeTransform(pixToTanPix, p).invert().getLinear() m = afwGeom.Quadrupole(ixx0, iyy0, ixy0) m.transform(linTransform) xcen, ycen = xcen0, ycen0 # p.getX(), p.getY() if (xcen < 1.0 * edgeBuffer or (nx - xcen) < 1.0 * edgeBuffer or ycen < 1.0 * edgeBuffer or (ny - ycen) < 1.0 * edgeBuffer): continue ixcen, iycen = int(xcen), int(ycen) ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy() # plant the object tmp = 0.25 * (ixx - iyy)**2 + ixy**2 a2 = 0.5 * (ixx + iyy) + np.sqrt(tmp) b2 = 0.5 * (ixx + iyy) - np.sqrt(tmp) theta = 0.5 * np.arctan2(2.0 * ixy, ixx - iyy) a = np.sqrt(a2) b = np.sqrt(b2) c, s = math.cos(theta), math.sin(theta) good0, good = True, True for y in range(nky): iy = iycen + y - yhwid iy0 = iycen0 + y - yhwid for x in range(nkx): ix = ixcen + x - xhwid ix0 = ixcen0 + x - xhwid if ix >= 0 and ix < nx and iy >= 0 and iy < ny: dx, dy = ix - xcen, iy - ycen u = c * dx + s * dy v = -s * dx + c * dy I0 = flux / (2 * math.pi * a * b) val = I0 * math.exp(-0.5 * ((u / a)**2 + (v / b)**2)) if val < 0: val = 0 prevVal = img.get(ix, iy) img.set(ix, iy, val + prevVal) else: good = False if ix0 >= 0 and ix0 < nx and iy0 >= 0 and iy0 < ny: dx, dy = ix - xcen, iy - ycen I0 = flux / (2 * math.pi * wid * wid) val = I0 * math.exp(-0.5 * ((dx / wid)**2 + (dy / wid)**2)) if val < 0: val = 0 prevVal = img0.get(ix0, iy0) img0.set(ix0, iy0, val + prevVal) else: good0 = False if good0: goodAdded0.append([xcen, ycen]) if good: goodAdded.append([xcen, ycen]) # add sky and noise img += sky img0 += sky noise = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) noise0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) for i in range(nx): for j in range(ny): noise.set(i, j, np.random.poisson(img.get(i, j))) noise0.set(i, j, np.random.poisson(img0.get(i, j))) edgeWidth = int(0.5 * edgeBuffer) mask = afwImage.Mask(afwGeom.ExtentI(nx, ny)) left = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.ExtentI(edgeWidth, ny)) right = afwGeom.Box2I(afwGeom.Point2I(nx - edgeWidth, 0), afwGeom.ExtentI(edgeWidth, ny)) top = afwGeom.Box2I(afwGeom.Point2I(0, ny - edgeWidth), afwGeom.ExtentI(nx, edgeWidth)) bottom = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.ExtentI(nx, edgeWidth)) for pos in [left, right, top, bottom]: msk = afwImage.Mask(mask, pos, deep=False) msk.set(msk.getPlaneBitMask('EDGE')) expos = afwImage.makeExposure( afwImage.makeMaskedImage(noise, mask, afwImage.ImageF(noise, True))) expos0 = afwImage.makeExposure( afwImage.makeMaskedImage(noise0, mask, afwImage.ImageF(noise0, True))) im = expos.getMaskedImage().getImage() im0 = expos0.getMaskedImage().getImage() im -= sky im0 -= sky return expos, goodAdded, expos0, goodAdded0
def checkLinearize(self, transform, invertible): """Test whether a specific transform is correctly linearized. Parameters ---------- transform: `lsst.afw.geom.Transform` the transform whose linearization will be tested. Should not be strongly curved within ~1 unit of the origin, or the test may rule the approximation isn't good enough. invertible: `bool` whether `transform` is invertible. The test will verify that the linearized form is invertible iff `transform` is. If `transform` is invertible, the test will also verify that the inverse of the linearization approximates the inverse of `transform`. """ fromEndpoint = transform.fromEndpoint toEndpoint = transform.toEndpoint nIn = fromEndpoint.nAxes nOut = toEndpoint.nAxes msg = "TransformClass={}, nIn={}, nOut={}".format( type(transform).__name__, nIn, nOut) rawLinPoint = self.makeRawPointData(nIn) linPoint = fromEndpoint.pointFromData(rawLinPoint) affine = afwGeom.linearizeTransform(transform, linPoint) self.assertIsInstance(affine, afwGeom.AffineTransform) # Does affine match exact transform at linPoint? outPoint = transform.applyForward(linPoint) outPointLinearized = affine(linPoint) assert_allclose(toEndpoint.dataFromPoint(outPoint), toEndpoint.dataFromPoint(outPointLinearized), err_msg=msg) jacobian = transform.getJacobian(linPoint) jacobianLinearized = affine.getLinear().getMatrix() assert_allclose(jacobian, jacobianLinearized) # Is affine a local approximation around linPoint? for deltaFrom in (np.zeros(nIn), np.full(nIn, 0.1), np.array([0.1, -0.15, 0.20, -0.05, 0.0, -0.1][0:nIn])): tweakedInPoint = fromEndpoint.pointFromData(rawLinPoint + deltaFrom) tweakedOutPoint = transform.applyForward(tweakedInPoint) tweakedOutPointLinearized = affine(tweakedInPoint) assert_allclose( toEndpoint.dataFromPoint(tweakedOutPoint), toEndpoint.dataFromPoint(tweakedOutPointLinearized), atol=1e-3, err_msg=msg) # Is affine invertible? # AST lets all-zero MatrixMaps be invertible though inverse # ill-defined; exclude this case if invertible: rng = np.random.RandomState(42) nDelta = 100 inverse = affine.invert() deltaFrom = rng.normal(0.0, 10.0, (nIn, nDelta)) for i in range(nDelta): pointMsg = "{}, point={}".format(msg, tweakedInPoint) tweakedInPoint = fromEndpoint.pointFromData(rawLinPoint + deltaFrom[:, i]) tweakedOutPoint = affine(tweakedInPoint) roundTrip = inverse(tweakedOutPoint) assert_allclose(roundTrip, tweakedInPoint, err_msg=pointMsg) assert_allclose(inverse.getLinear().getMatrix(), np.linalg.inv(jacobian), err_msg=pointMsg) else: # TODO: replace with correct type after fixing DM-11248 with self.assertRaises(Exception): affine.invert()
def plantSources(x0, y0, nx, ny, sky, nObj, wid, detector, useRandom=False): pixToTanPix = detector.getTransform(cameraGeom.PIXELS, cameraGeom.TAN_PIXELS) img0 = afwImage.ImageF(lsst.geom.ExtentI(nx, ny)) img = afwImage.ImageF(lsst.geom.ExtentI(nx, ny)) ixx0, iyy0, ixy0 = wid*wid, wid*wid, 0.0 edgeBuffer = 40.0*wid flux = 1.0e4 nkx, nky = int(10*wid) + 1, int(10*wid) + 1 xhwid, yhwid = nkx//2, nky//2 nRow = int(math.sqrt(nObj)) xstep = (nx - 1 - 0.0*edgeBuffer)//(nRow+1) ystep = (ny - 1 - 0.0*edgeBuffer)//(nRow+1) if useRandom: nObj = nRow*nRow goodAdded0 = [] goodAdded = [] for i in range(nObj): # get our position if useRandom: xcen0, ycen0 = np.random.uniform(nx), np.random.uniform(ny) else: xcen0, ycen0 = xstep*((i % nRow) + 1), ystep*(int(i/nRow) + 1) ixcen0, iycen0 = int(xcen0), int(ycen0) # distort position and shape pTan = lsst.geom.Point2D(xcen0, ycen0) p = pixToTanPix.applyInverse(pTan) linTransform = afwGeom.linearizeTransform(pixToTanPix, p).inverted().getLinear() m = afwGeom.Quadrupole(ixx0, iyy0, ixy0) m.transform(linTransform) xcen, ycen = xcen0, ycen0 # p.getX(), p.getY() if (xcen < 1.0*edgeBuffer or (nx - xcen) < 1.0*edgeBuffer or ycen < 1.0*edgeBuffer or (ny - ycen) < 1.0*edgeBuffer): continue ixcen, iycen = int(xcen), int(ycen) ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy() # plant the object tmp = 0.25*(ixx-iyy)**2 + ixy**2 a2 = 0.5*(ixx+iyy) + np.sqrt(tmp) b2 = 0.5*(ixx+iyy) - np.sqrt(tmp) theta = 0.5*np.arctan2(2.0*ixy, ixx-iyy) a = np.sqrt(a2) b = np.sqrt(b2) c, s = math.cos(theta), math.sin(theta) good0, good = True, True for y in range(nky): iy = iycen + y - yhwid iy0 = iycen0 + y - yhwid for x in range(nkx): ix = ixcen + x - xhwid ix0 = ixcen0 + x - xhwid if ix >= 0 and ix < nx and iy >= 0 and iy < ny: dx, dy = ix - xcen, iy - ycen u = c*dx + s*dy v = -s*dx + c*dy I0 = flux/(2*math.pi*a*b) val = I0*math.exp(-0.5*((u/a)**2 + (v/b)**2)) if val < 0: val = 0 prevVal = img[ix, iy, afwImage.LOCAL] img[ix, iy, afwImage.LOCAL] = val+prevVal else: good = False if ix0 >= 0 and ix0 < nx and iy0 >= 0 and iy0 < ny: dx, dy = ix - xcen, iy - ycen I0 = flux/(2*math.pi*wid*wid) val = I0*math.exp(-0.5*((dx/wid)**2 + (dy/wid)**2)) if val < 0: val = 0 prevVal = img0[ix0, iy0, afwImage.LOCAL] img0[ix0, iy0, afwImage.LOCAL] = val+prevVal else: good0 = False if good0: goodAdded0.append([xcen, ycen]) if good: goodAdded.append([xcen, ycen]) # add sky and noise img += sky img0 += sky noise = afwImage.ImageF(lsst.geom.ExtentI(nx, ny)) noise0 = afwImage.ImageF(lsst.geom.ExtentI(nx, ny)) for i in range(nx): for j in range(ny): noise[i, j, afwImage.LOCAL] = np.random.poisson(img[i, j, afwImage.LOCAL]) noise0[i, j, afwImage.LOCAL] = np.random.poisson(img0[i, j, afwImage.LOCAL]) edgeWidth = int(0.5*edgeBuffer) mask = afwImage.Mask(lsst.geom.ExtentI(nx, ny)) left = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.ExtentI(edgeWidth, ny)) right = lsst.geom.Box2I(lsst.geom.Point2I(nx - edgeWidth, 0), lsst.geom.ExtentI(edgeWidth, ny)) top = lsst.geom.Box2I(lsst.geom.Point2I(0, ny - edgeWidth), lsst.geom.ExtentI(nx, edgeWidth)) bottom = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.ExtentI(nx, edgeWidth)) for pos in [left, right, top, bottom]: msk = afwImage.Mask(mask, pos, deep=False) msk.set(msk.getPlaneBitMask('EDGE')) expos = afwImage.makeExposure(afwImage.makeMaskedImage(noise, mask, afwImage.ImageF(noise, True))) expos0 = afwImage.makeExposure(afwImage.makeMaskedImage(noise0, mask, afwImage.ImageF(noise0, True))) im = expos.getMaskedImage().getImage() im0 = expos0.getMaskedImage().getImage() im -= sky im0 -= sky return expos, goodAdded, expos0, goodAdded0
def selectSources(self, sourceCat, matches=None, exposure=None): """Return a selection of PSF candidates that represent likely stars. A list of PSF candidates may be used by a PSF fitter to construct a PSF. Parameters: ----------- sourceCat : `lsst.afw.table.SourceCatalog` Catalog of sources to select from. This catalog must be contiguous in memory. matches : `list` of `lsst.afw.table.ReferenceMatch` or None Ignored in this SourceSelector. exposure : `lsst.afw.image.Exposure` or None The exposure the catalog was built from; used to get the detector to transform to TanPix, and for debug display. Return ------ struct : `lsst.pipe.base.Struct` The struct contains the following data: - selected : `array` of `bool`` Boolean array of sources that were selected, same length as 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 = None pixToTanPix = None if exposure: detector = exposure.getDetector() if detector: pixToTanPix = detector.getTransform(PIXELS, TAN_PIXELS) # # Look at the distribution of stars in the magnitude-size plane # flux = sourceCat.get(self.config.sourceFluxField) fluxErr = sourceCat.get(self.config.sourceFluxField + "Err") 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 pixToTanPix: p = lsst.geom.Point2D(source.getX(), source.getY()) linTransform = afwGeom.linearizeTransform(pixToTanPix, p).getLinear() m = afwGeom.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)) with numpy.errstate(invalid="ignore"): # suppress NAN warnings bad = reduce(lambda x, y: numpy.logical_or(x, sourceCat.get(y)), self.config.badFlags, False) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width))) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux))) if self.config.doFluxLimit: bad = numpy.logical_or(bad, flux < self.config.fluxMin) if self.config.fluxMax > 0: bad = numpy.logical_or(bad, flux > self.config.fluxMax) if self.config.doSignalToNoiseLimit: bad = numpy.logical_or( bad, flux / fluxErr < self.config.signalToNoiseMin) if self.config.signalToNoiseMax > 0: bad = numpy.logical_or( bad, flux / fluxErr > self.config.signalToNoiseMax) bad = numpy.logical_or(bad, width < self.config.widthMin) bad = numpy.logical_or(bad, width > self.config.widthMax) 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: disp = afwDisplay.Display(frame=frame) disp.mtv(exposure.getMaskedImage(), 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 the image display. """) 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 disp.Buffering(): for i, source in enumerate(sourceCat): if good[i]: ctype = afwDisplay.GREEN # star candidate else: ctype = afwDisplay.RED # not star disp.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(), ctype=ctype) # stellar only applies to good==True objects mask = good == True # noqa (numpy bool comparison): E712 good[mask] = stellar return Struct(selected=good)
def selectSources(self, sourceCat, matches=None, exposure=None): """Return a selection of PSF candidates that represent likely stars. A list of PSF candidates may be used by a PSF fitter to construct a PSF. Parameters: ----------- sourceCat : `lsst.afw.table.SourceCatalog` Catalog of sources to select from. This catalog must be contiguous in memory. matches : `list` of `lsst.afw.table.ReferenceMatch` or None Ignored in this SourceSelector. exposure : `lsst.afw.image.Exposure` or None The exposure the catalog was built from; used to get the detector to transform to TanPix, and for debug display. Return ------ struct : `lsst.pipe.base.Struct` The struct contains the following data: - selected : `array` of `bool`` Boolean array of sources that were selected, same length as 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 = None pixToTanPix = None if exposure: detector = exposure.getDetector() if detector: pixToTanPix = detector.getTransform(PIXELS, TAN_PIXELS) # # Look at the distribution of stars in the magnitude-size plane # flux = sourceCat.get(self.config.sourceFluxField) fluxErr = sourceCat.get(self.config.sourceFluxField + "Err") 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 pixToTanPix: p = lsst.geom.Point2D(source.getX(), source.getY()) linTransform = afwGeom.linearizeTransform(pixToTanPix, p).getLinear() m = afwGeom.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)) with numpy.errstate(invalid="ignore"): # suppress NAN warnings bad = reduce(lambda x, y: numpy.logical_or(x, sourceCat.get(y)), self.config.badFlags, False) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width))) bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux))) if self.config.doFluxLimit: bad = numpy.logical_or(bad, flux < self.config.fluxMin) if self.config.fluxMax > 0: bad = numpy.logical_or(bad, flux > self.config.fluxMax) if self.config.doSignalToNoiseLimit: bad = numpy.logical_or(bad, flux/fluxErr < self.config.signalToNoiseMin) if self.config.signalToNoiseMax > 0: bad = numpy.logical_or(bad, flux/fluxErr > self.config.signalToNoiseMax) bad = numpy.logical_or(bad, width < self.config.widthMin) bad = numpy.logical_or(bad, width > self.config.widthMax) 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: disp = afwDisplay.Display(frame=frame) disp.mtv(exposure.getMaskedImage(), 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 the image display. """) 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 disp.Buffering(): for i, source in enumerate(sourceCat): if good[i]: ctype = afwDisplay.GREEN # star candidate else: ctype = afwDisplay.RED # not star disp.dot("+", source.getX() - mi.getX0(), source.getY() - mi.getY0(), ctype=ctype) # stellar only applies to good==True objects mask = good == True # noqa (numpy bool comparison): E712 good[mask] = stellar return Struct(selected=good)
def checkLinearize(self, transform, invertible): """Test whether a specific transform is correctly linearized. Parameters ---------- transform: `lsst.afw.geom.Transform` the transform whose linearization will be tested. Should not be strongly curved within ~1 unit of the origin, or the test may rule the approximation isn't good enough. invertible: `bool` whether `transform` is invertible. The test will verify that the linearized form is invertible iff `transform` is. If `transform` is invertible, the test will also verify that the inverse of the linearization approximates the inverse of `transform`. """ fromEndpoint = transform.fromEndpoint toEndpoint = transform.toEndpoint nIn = fromEndpoint.nAxes nOut = toEndpoint.nAxes msg = "TransformClass={}, nIn={}, nOut={}".format(type(transform).__name__, nIn, nOut) rawLinPoint = self.makeRawPointData(nIn) linPoint = fromEndpoint.pointFromData(rawLinPoint) affine = afwGeom.linearizeTransform(transform, linPoint) self.assertIsInstance(affine, lsst.geom.AffineTransform) # Does affine match exact transform at linPoint? outPoint = transform.applyForward(linPoint) outPointLinearized = affine(linPoint) assert_allclose(toEndpoint.dataFromPoint(outPoint), toEndpoint.dataFromPoint(outPointLinearized), err_msg=msg) jacobian = transform.getJacobian(linPoint) jacobianLinearized = affine.getLinear().getMatrix() assert_allclose(jacobian, jacobianLinearized) # Is affine a local approximation around linPoint? for deltaFrom in ( np.zeros(nIn), np.full(nIn, 0.1), np.array([0.1, -0.15, 0.20, -0.05, 0.0, -0.1][0:nIn]) ): tweakedInPoint = fromEndpoint.pointFromData( rawLinPoint + deltaFrom) tweakedOutPoint = transform.applyForward(tweakedInPoint) tweakedOutPointLinearized = affine(tweakedInPoint) assert_allclose( toEndpoint.dataFromPoint(tweakedOutPoint), toEndpoint.dataFromPoint(tweakedOutPointLinearized), atol=1e-3, err_msg=msg) # Is affine invertible? # AST lets all-zero MatrixMaps be invertible though inverse # ill-defined; exclude this case if invertible: rng = np.random.RandomState(42) nDelta = 100 inverse = affine.inverted() deltaFrom = rng.normal(0.0, 10.0, (nIn, nDelta)) for i in range(nDelta): pointMsg = "{}, point={}".format(msg, tweakedInPoint) tweakedInPoint = fromEndpoint.pointFromData( rawLinPoint + deltaFrom[:, i]) tweakedOutPoint = affine(tweakedInPoint) roundTrip = inverse(tweakedOutPoint) assert_allclose( roundTrip, tweakedInPoint, err_msg=pointMsg) assert_allclose( inverse.getLinear().getMatrix(), np.linalg.inv(jacobian), err_msg=pointMsg) else: # TODO: replace with correct type after fixing DM-11248 with self.assertRaises(Exception): affine.inverted()