def warped_from_fits(self, fitsfile): """Return a warped exposure computed from an LSST exposure""" exp = afwImage.ExposureF(fitsfile) wcs_in = exp.getWcs() wcs_out = self.make_wcs() warper = afwMath.Warper(self.kernel) warpedExposure = warper.warpExposure(destWcs=wcs_out, srcExposure=exp) return warpedExposure
def compareToSwarp(self, kernelName, useSubregion=False, useDeepCopy=False, interpLength=10, cacheSize=100000, rtol=4e-05, atol=1e-2): """Compare warpExposure to swarp for given warping kernel. Note that swarp only warps the image plane, so only test that plane. Inputs: - kernelName: name of kernel in the form used by afwImage.makeKernel - useSubregion: if True then the original source exposure (from which the usual test exposure was extracted) is read and the correct subregion extracted - useDeepCopy: if True then the copy of the subimage is a deep copy, else it is a shallow copy; ignored if useSubregion is False - interpLength: interpLength argument for lsst.afw.math.warpExposure - cacheSize: cacheSize argument for lsst.afw.math.SeparableKernel.computeCache; 0 disables the cache 10000 gives some speed improvement but less accurate results (atol must be increased) 100000 gives better accuracy but no speed improvement in this test - rtol: relative tolerance as used by numpy.allclose - atol: absolute tolerance as used by numpy.allclose """ warper = afwMath.Warper(kernelName) originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage( kernelName=kernelName, useSubregion=useSubregion, useDeepCopy=useDeepCopy) maxBBox = afwGeom.Box2I( afwGeom.Point2I(swarpedImage.getX0(), swarpedImage.getY0()), afwGeom.Extent2I(swarpedImage.getWidth(), swarpedImage.getHeight())) # warning: this test assumes that the swarped image is smaller than it needs to be # to hold all of the warped pixels afwWarpedExposure = warper.warpExposure( destWcs=swarpedWcs, srcExposure=originalExposure, maxBBox=maxBBox, ) afwWarpedMaskedImage = afwWarpedExposure.getMaskedImage() afwWarpedMask = afwWarpedMaskedImage.getMask() noDataBitMask = afwImage.MaskU.getPlaneBitMask("NO_DATA") noDataMask = afwWarpedMask.getArray() & noDataBitMask msg = "afw and swarp %s-warped %s (ignoring bad pixels)" self.assertImagesNearlyEqual(afwWarpedMaskedImage.getImage(), swarpedImage, skipMask=noDataMask, rtol=rtol, atol=atol, msg=msg)
def testBBox(self): """Test that the default bounding box includes all warped pixels """ kernelName = "lanczos2" warper = afwMath.Warper(kernelName) originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage( kernelName=kernelName, useSubregion=True, useDeepCopy=False) filterPolicyFile = pexPolicy.DefaultPolicyFile("afw", "SdssFilters.paf", "tests") filterPolicy = pexPolicy.Policy.createPolicy( filterPolicyFile, filterPolicyFile.getRepositoryPath(), True) imageUtils.defineFiltersFromPolicy(filterPolicy, reset=True) originalFilter = afwImage.Filter("i") originalCalib = afwImage.Calib() originalCalib.setFluxMag0(1.0e5, 1.0e3) originalExposure.setFilter(originalFilter) originalExposure.setCalib(originalCalib) warpedExposure1 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure) # the default size must include all good pixels, so growing the bbox # should not add any warpedExposure2 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure, border=1) # a bit of excess border is allowed, but surely not as much as 10 (in # fact it is approx. 5) warpedExposure3 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure, border=-10) # assert that warpedExposure and warpedExposure2 have the same number of non-no_data pixels # and that warpedExposure3 has fewer noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA") mask1Arr = warpedExposure1.getMaskedImage().getMask().getArray() mask2Arr = warpedExposure2.getMaskedImage().getMask().getArray() mask3Arr = warpedExposure3.getMaskedImage().getMask().getArray() nGood1 = (mask1Arr & noDataBitMask == 0).sum() nGood2 = (mask2Arr & noDataBitMask == 0).sum() nGood3 = (mask3Arr & noDataBitMask == 0).sum() self.assertEqual(nGood1, nGood2) self.assertLess(nGood3, nGood1) self.assertEqual(warpedExposure1.getFilter().getName(), originalFilter.getName()) self.assertEqual(warpedExposure1.getCalib().getFluxMag0(), originalCalib.getFluxMag0())
def rotateExposure(exp, nDegrees, kernelName='lanczos4', logger=None): """Rotate an exposure by nDegrees clockwise. Parameters ---------- exp : `lsst.afw.image.exposure.Exposure` The exposure to rotate nDegrees : `float` Number of degrees clockwise to rotate by kernelName : `str` Name of the warping kernel, used to instantiate the warper. logger : `lsst.log.Log` Logger for logging warnings Returns ------- rotatedExp : `lsst.afw.image.exposure.Exposure` A copy of the input exposure, rotated by nDegrees """ nDegrees = nDegrees % 360 if not logger: logger = lsstLog.getLogger('atmospec.utils') wcs = exp.getWcs() if not wcs: logger.warn( "Can't rotate exposure without a wcs - returning exp unrotated") return exp.clone( ) # return a clone so it's always returning a copy as this is what default does warper = afwMath.Warper(kernelName) if isinstance(exp, afwImage.ExposureU): # TODO: remove once this bug is fixed - DM-20258 logger.info('Converting ExposureU to ExposureF due to bug') logger.info('Remove this workaround after DM-20258') exp = afwImage.ExposureF(exp, deep=True) affineRotTransform = geom.AffineTransform.makeRotation(nDegrees * geom.degrees) transformP2toP2 = afwGeom.makeTransform(affineRotTransform) rotatedWcs = afwGeom.makeModifiedWcs(transformP2toP2, wcs, False) rotatedExp = warper.warpExposure(rotatedWcs, exp) # rotatedExp.setXY0(geom.Point2I(0, 0)) # TODO: check no longer required return rotatedExp
def testDestBBox(self): """Test that the destBBox argument works """ kernelName = "lanczos2" warper = afwMath.Warper(kernelName) originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage( kernelName=kernelName, useSubregion=True, useDeepCopy=False) bbox = afwGeom.Box2I(afwGeom.Point2I(100, 25), afwGeom.Extent2I(3, 7)) warpedExposure = warper.warpExposure( destWcs=swarpedWcs, srcExposure=originalExposure, destBBox=bbox, border=-2, # should be ignored maxBBox=afwGeom.Box2I(afwGeom.Point2I(1, 2), afwGeom.Extent2I(8, 9)), # should be ignored ) self.assertTrue(bbox == warpedExposure.getBBox(afwImage.PARENT))
def testBBox(self): """Test that the default bounding box includes all warped pixels """ kernelName = "lanczos2" warper = afwMath.Warper(kernelName) originalExposure, swarpedImage, swarpedWcs = self.getSwarpedImage( kernelName=kernelName, useSubregion=True, useDeepCopy=False) originalFilterLabel = afwImage.FilterLabel(band="i") originalPhotoCalib = afwImage.PhotoCalib(1.0e5, 1.0e3) originalExposure.setFilterLabel(originalFilterLabel) originalExposure.setPhotoCalib(originalPhotoCalib) warpedExposure1 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure) # the default size must include all good pixels, so growing the bbox # should not add any warpedExposure2 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure, border=1) # a bit of excess border is allowed, but surely not as much as 10 (in # fact it is approx. 5) warpedExposure3 = warper.warpExposure(destWcs=swarpedWcs, srcExposure=originalExposure, border=-10) # assert that warpedExposure and warpedExposure2 have the same number of non-no_data pixels # and that warpedExposure3 has fewer noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA") mask1Arr = warpedExposure1.getMaskedImage().getMask().getArray() mask2Arr = warpedExposure2.getMaskedImage().getMask().getArray() mask3Arr = warpedExposure3.getMaskedImage().getMask().getArray() nGood1 = (mask1Arr & noDataBitMask == 0).sum() nGood2 = (mask2Arr & noDataBitMask == 0).sum() nGood3 = (mask3Arr & noDataBitMask == 0).sum() self.assertEqual(nGood1, nGood2) self.assertLess(nGood3, nGood1) self.assertEqual(warpedExposure1.getFilterLabel().bandLabel, originalFilterLabel.bandLabel) self.assertEqual(warpedExposure1.getPhotoCalib(), originalPhotoCalib)
# Use one of the input images as a base for dimensions. d_ang = d_angs[0] nx, ny = ghostIms[d_ang].getDimensions() # pixel scale in ghost images is 10" per pixel pixScale = afwGeom.Angle(10.0/60./60./1.) # Create the destination image. destIm, destWcs = makeDest(nx, ny, pixScale, stars['raCen'], stars['decCen']) destArr = destIm.getArray() # Calculate star positions. stars = calcStarPos(stars) #print stars['d_ang'].min(), stars['d_ang'].max(), stars['rotAng'].min(), stars['rotAng'].max() # Set up warper to map input image to output WCS. # There are other warping kernels, but lanczos2 is the standard one warper = afwMath.Warper("lanczos2") idx = numpy.argsort(stars['mag']) if args.nStars > 0: idx = idx[0:args.nStars] for i in (idx): ## For each star, choose appropriate ghost & rotate. Add. # Find the closest ghost image. d_ang = stars['d_ang'][i] # Skip star if too far outside the last ghost image. if d_ang > d_ang_max_use: continue d = abs(d_angs - d_ang) didx = numpy.where(d == d.min()) d_ang_closest = d_angs[didx][0] # Calculate scale for output image (assuming ghost source = 0 mag)
def testForced(self): """Check that forced photometry works in the presence of rotations and translations. """ kfac = 2.5 warper = afwMath.Warper("lanczos4") a = 13 for axisRatio in (0.25, 1.0): b = a * axisRatio for theta in (0, 30, 45): width, height = 256, 256 center = afwGeom.Point2D(0.5 * width, 0.5 * height) original = makeGalaxy(width, height, 1000.0, a, b, theta) msConfig = makeMeasurementConfig(forced=False, kfac=kfac) source = measureFree(original, center, msConfig) algMeta = source.getTable().getMetadata() self.assertTrue( algMeta.exists( 'ext_photometryKron_KronFlux_nRadiusForFlux')) if source.get("ext_photometryKron_KronFlux_flag"): continue angleList = [val * afwGeom.degrees for val in (45, 90)] scaleList = [1.0, 0.5] offsetList = [(1.23, 4.56), (12.3, 45.6)] for angle, scale, offset in itertools.product( angleList, scaleList, offsetList): dx, dy = offset pixelScale = original.getWcs().getPixelScale() * scale cdMatrix = afwGeom.makeCdMatrix(scale=pixelScale, orientation=angle, flipX=True) wcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(dx, dy), crval=afwGeom.SpherePoint( 0.0, 0.0, afwGeom.degrees), cdMatrix=cdMatrix) warped = warper.warpExposure(wcs, original) # add a Psf if there is none. The new SdssCentroid needs a Psf. if warped.getPsf() is None: warped.setPsf(afwDetection.GaussianPsf(11, 11, 0.01)) msConfig = makeMeasurementConfig(kfac=kfac, forced=True) forced = measureForced(warped, source, original.getWcs(), msConfig) algMeta = source.getTable().getMetadata() self.assertTrue( algMeta.exists( 'ext_photometryKron_KronFlux_nRadiusForFlux')) if display: disp1 = afwDisplay.Display(frame=1) disp1.mtv(original, title=self._testMethodName + ": original image") shape = source.getShape().clone() xc, yc = source.getCentroid() radius = source.get( "ext_photometryKron_KronFlux_radius") for r, ct in [ (radius, afwDisplay.BLUE), (radius * kfac, afwDisplay.CYAN), ]: shape.scale(r / shape.getDeterminantRadius()) disp1.dot(shape, xc, yc, ctype=ct) disp2 = afwDisplay.Display(frame=2) disp2.mtv(warped, title=self._testMethodName + ": warped image") transform = (wcs.linearizeSkyToPixel( source.getCoord(), lsst.geom.degrees) * original.getWcs().linearizePixelToSky( source.getCoord(), lsst.geom.degrees)) shape = shape.transform(transform.getLinear()) radius = source.get( "ext_photometryKron_KronFlux_radius") xc, yc = wcs.skyToPixel(source.getCoord()) for r, ct in [ (radius, afwDisplay.BLUE), (radius * kfac, afwDisplay.CYAN), ]: shape.scale(r / shape.getDeterminantRadius()) disp2.dot(shape, xc, yc, ctype=ct) try: self.assertFloatsAlmostEqual( source.get("ext_photometryKron_KronFlux_instFlux"), forced.get("ext_photometryKron_KronFlux_instFlux"), rtol=1.0e-3) self.assertFloatsAlmostEqual( source.get("ext_photometryKron_KronFlux_radius"), scale * forced.get("ext_photometryKron_KronFlux_radius"), rtol=1.0e-3) self.assertEqual( source.get("ext_photometryKron_KronFlux_flag"), forced.get("ext_photometryKron_KronFlux_flag")) except Exception: print(("Failed:", angle, scale, offset, [ (source.get(f), forced.get(f)) for f in ("ext_photometryKron_KronFlux_instFlux", "ext_photometryKron_KronFlux_radius", "ext_photometryKron_KronFlux_flag") ])) raise
def testForced(self): """Check that forced photometry works in the presence of rotations and translations""" kfac = 2.5 msConfig = makeSourceMeasurementConfig(kfac=kfac) warper = afwMath.Warper("lanczos4") a = 13 for axisRatio in (0.25, 1.0): b = a*axisRatio for theta in (0, 30, 45): width, height = 256, 256 center = afwGeom.Point2D(0.5*width, 0.5*height) original = makeGalaxy(width, height, 1000.0, a, b, theta) source = measureFree(original, center, msConfig) if source.get("flux.kron.flags"): continue angleList = [45, 90,] scaleList = [1.0, 0.5] offsetList = [(1.23, 4.56), (12.3, 45.6)] for angle, scale, offset in itertools.product(angleList, scaleList, offsetList): cosAngle = math.cos(math.radians(angle)) sinAngle = math.sin(math.radians(angle)) dx, dy = offset pixelScale = original.getWcs().pixelScale().asDegrees()*scale wcs = afwImage.makeWcs(afwCoord.Coord(0.0*afwGeom.degrees, 0.0*afwGeom.degrees), afwGeom.Point2D(dx, dy), pixelScale*cosAngle, pixelScale*sinAngle, -pixelScale*sinAngle, pixelScale*cosAngle) warped = warper.warpExposure(wcs, original) forced = measureForced(warped, source, original.getWcs(), msConfig) if display: ds9.mtv(original, frame=1) shape = source.getShape().clone() xc, yc = source.getCentroid() radius = source.get("flux.kron.radius") for r, ct in [(radius, ds9.BLUE), (radius*kfac, ds9.CYAN),]: shape.scale(r/shape.getDeterminantRadius()) ds9.dot(shape, xc, yc, ctype=ct, frame=1) ds9.mtv(warped, frame=2) transform = (wcs.linearizeSkyToPixel(source.getCoord())* original.getWcs().linearizePixelToSky(source.getCoord())) shape = shape.transform(transform.getLinear()) radius = forced.get("flux.kron.radius") xc, yc = wcs.skyToPixel(source.getCoord()) - afwGeom.Extent2D(warped.getXY0()) for r, ct in [(radius, ds9.BLUE), (radius*kfac, ds9.CYAN),]: shape.scale(r/shape.getDeterminantRadius()) ds9.dot(shape, xc, yc, ctype=ct, frame=2) try: self.assertClose(source.get("flux.kron"), forced.get("flux.kron"), rtol=2.0e-4, atol=None) self.assertClose(source.get("flux.kron.radius"), scale*forced.get("flux.kron.radius"), rtol=None, atol=1.0e-12) self.assertEqual(source.get("flux.kron.flags"), forced.get("flux.kron.flags")) except: print ("Failed:", angle, scale, offset, [(source.get(f), forced.get(f)) for f in ("flux.kron", "flux.kron.radius", "flux.kron.flags")]) raise