def testSimpleCurvedFocalPlane(self): """Test a trivial curved focal plane with square pixels The CCD's lower left pixel is centered on the boresight pupil center = focal plane center CCD x is along focal plane x """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.02) plateScale = 25.0 # arcsec/mm yaw = 0 * afwGeom.degrees fpPosition = afwGeom.Point2D(0, 0) # focal-plane position of ref position on detector (mm) refPoint = afwGeom.Point2D(0, 0) # ref position on detector (pos of lower left corner) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform((0.0, plateScaleRad, 0.0, 0.001 * plateScaleRad)) pixelToPupil = afwGeom.MultiXYTransform((pixelToFocalPlane, focalPlaneToPupil)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToPupil=focalPlaneToPupil, pixelSizeMm=pixelSizeMm, ) # pupil center should be pixel position 0, 0 and tan pixel position 0, 0 pixAtPupilCtr = pixelToPupil.reverseTransform(afwGeom.Point2D(0, 0)) self.assertPairsNearlyEqual(pixAtPupilCtr, [0, 0]) tanPixAtPupilCr = pixelToTanPixel.forwardTransform(pixAtPupilCtr) self.assertPairsNearlyEqual(tanPixAtPupilCr, [0, 0]) # build same camera geometry transforms without optical distortion focalPlaneToPupilNoDistortion = afwGeom.RadialXYTransform((0.0, plateScaleRad)) pixelToPupilNoDistortion = afwGeom.MultiXYTransform( (pixelToFocalPlane, focalPlaneToPupilNoDistortion)) for x in (100, 200, 1000): for y in (100, 500, 800): pixPos = afwGeom.Point2D(x, y) tanPixPos = pixelToTanPixel.forwardTransform(pixPos) # pix to tan pix should be radial self.assertAlmostEqual( math.atan2(pixPos[1], pixPos[0]), math.atan2(tanPixPos[1], tanPixPos[0]), ) # for a given pupil angle (which, together with a pointing, gives a position on the sky): # - pupil to pixels gives pixPos # - undistorted pupil to pixels gives tanPixPos pupilPos = pixelToPupil.forwardTransform(pixPos) desTanPixPos = pixelToPupilNoDistortion.reverseTransform(pupilPos) self.assertPairsNearlyEqual(desTanPixPos, tanPixPos)
def testRadial(self): """Add a radial transform""" for order in (4, 5, 6): self.doTest("testRadial", afwGeom.RadialXYTransform([0, 1.001, 0.000003]), order=order, doPlot=False)
def testCurvedFocalPlane(self): """Test a curved focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.03) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) fpPosition = afwGeom.Point2D( 50, 25) # focal-plane position of ref position on detector (mm) refPoint = afwGeom.Point2D( -0.5, -0.5) # ref position on detector (pos of lower left corner) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform( (0.0, plateScaleRad, 0.0, 0.001 * plateScaleRad)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToPupil=focalPlaneToPupil, pixelSizeMm=pixelSizeMm, plateScale=plateScale, ) # the center point of the detector should not move ctrPointPix = afwGeom.Box2D(bbox).getCenter() ctrPointTanPix = pixelToTanPixel.forwardTransform(ctrPointPix) for i in range(2): self.assertAlmostEquals(ctrPointTanPix[i], ctrPointPix[i]) # two points separated by x pixels in tan pixels coordinates # should be separated x * rad/tanPix in pupil coordinates, # where rad/tanPix = plate scale in rad/MM * mean pixel size in mm radPerTanPixel = plateScaleRad * (pixelSizeMm[0] + pixelSizeMm[1]) / 2.0 pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) pixelToPupil = afwGeom.MultiXYTransform( (pixelToFocalPlane, focalPlaneToPupil)) prevPointPupil = None prevPointTanPix = None for pointPix in ( afwGeom.Point2D(0, 0), afwGeom.Point2D(1000, 2000), afwGeom.Point2D(-100.5, 27.23), afwGeom.Point2D(-95.3, 0.0), ): pointPupil = pixelToPupil.forwardTransform(pointPix) pointTanPix = pixelToTanPixel.forwardTransform(pointPix) if prevPointPupil: pupilSep = numpy.linalg.norm(pointPupil - prevPointPupil) tanPixSep = numpy.linalg.norm(pointTanPix - prevPointTanPix) self.assertAlmostEquals(tanPixSep * radPerTanPixel, pupilSep) prevPointPupil = pointPupil prevPointTanPix = pointTanPix
def testFlatFocalPlane(self): """Test an undistorted focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.03) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) fpPosition = afwGeom.Point2D(50, 25) # focal-plane position of ref position on detector (mm) refPoint = afwGeom.Point2D(-0.5, -0.5) # ref position on detector (pos of lower left corner) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform((0.0, plateScaleRad)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToPupil=focalPlaneToPupil, pixelSizeMm=pixelSizeMm, ) # with no distortion, this should be a unity transform for pointPix in ( afwGeom.Point2D(0, 0), afwGeom.Point2D(1000, 2000), afwGeom.Point2D(-100.5, 27.23), ): pointTanPix = pixelToTanPixel.forwardTransform(pointPix) self.assertPairsNearlyEqual(pointTanPix, pointPix)
def testTransform(self): """Test pixelToSky, skyToPixel, getTanWcs and getPixelToTanPixel """ pixelsToTanPixels = afwGeom.RadialXYTransform([0, 1.001, 0.00003]) distortedWcs = afwImage.DistortedTanWcs(self.tanWcs, pixelsToTanPixels) tanWcsCopy = distortedWcs.getTanWcs() pixToTanCopy = distortedWcs.getPixelToTanPixel() for x in (0, 1000, 5000): for y in (0, 560, 2000): pixPos = afwGeom.Point2D(x, y) tanPixPos = pixelsToTanPixels.forwardTransform(pixPos) tanPixPosCopy = pixToTanCopy.forwardTransform(pixPos) self.assertEqual(tanPixPos, tanPixPosCopy) predSky = self.tanWcs.pixelToSky(tanPixPos) predSkyCopy = tanWcsCopy.pixelToSky(tanPixPos) self.assertEqual(predSky, predSkyCopy) measSky = distortedWcs.pixelToSky(pixPos) self.assertLess( predSky.angularSeparation(measSky).asRadians(), 1e-7) pixPosRoundTrip = distortedWcs.skyToPixel(measSky) for i in range(2): self.assertAlmostEqual(pixPos[i], pixPosRoundTrip[i])
def testCurvedFocalPlane(self): """Test a curved focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.03) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) fpPosition = afwGeom.Point2D(50, 25) # focal-plane position of ref position on detector (mm) refPoint = afwGeom.Point2D(-0.5, -0.5) # ref position on detector (pos of lower left corner) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform((0.0, plateScaleRad, 0.0, 0.001 * plateScaleRad)) pixelToPupil = afwGeom.MultiXYTransform((pixelToFocalPlane, focalPlaneToPupil)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToPupil=focalPlaneToPupil, pixelSizeMm=pixelSizeMm, ) # the center point of the pupil frame should not move pixAtPupilCtr = pixelToPupil.reverseTransform(afwGeom.Point2D(0, 0)) tanPixAtPupilCr = pixelToTanPixel.forwardTransform(pixAtPupilCtr) self.assertPairsNearlyEqual(pixAtPupilCtr, tanPixAtPupilCr) # build same camera geometry transforms without optical distortion focalPlaneToPupilNoDistortion = afwGeom.RadialXYTransform((0.0, plateScaleRad)) pixelToPupilNoDistortion = afwGeom.MultiXYTransform( (pixelToFocalPlane, focalPlaneToPupilNoDistortion)) for x in (100, 200, 1000): for y in (100, 500, 800): pixPos = afwGeom.Point2D(x, y) tanPixPos = pixelToTanPixel.forwardTransform(pixPos) # for a given pupil position (which, together with a pointing, gives a position on the sky): # - pupil to pixels gives pixPos # - undistorted pupil to pixels gives tanPixPos pupilPos = pixelToPupil.forwardTransform(pixPos) desTanPixPos = pixelToPupilNoDistortion.reverseTransform(pupilPos) self.assertPairsNearlyEqual(desTanPixPos, tanPixPos)
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.RadialXYTransform([0, 1.02, 1e-6]) def radialDistortion(x, y): x, y = radialTransform.forwardTransform(afwGeom.Point2D(x, y)) return (x, y) self.doTest("testRadial", radialDistortion)
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.RadialXYTransform([0, 1.01, 1e-8]) def radialDistortion(x, y): x, y = radialTransform.forwardTransform(afwGeom.Point2D(x, y)) return (x, y) for order in (4, 5, 6): self.doTest("testRadial", radialDistortion, order=order)
def _makeRadialTransform(self, radialCoeffs): """Helper function to get the radial transform given the radial polynomial coefficients given in the constructor. @param[in] radialCoeffs List of coefficients describing a polynomial radial distortion in normalized units. @return RadialXYTransform object describing the radial distortion """ pScaleRad = afwGeom.arcsecToRad(self.plateScale) return afwGeom.RadialXYTransform( [el / pScaleRad for el in radialCoeffs])
def __init__(self): """Construct a TestCamera """ plateScale = afwGeom.Angle(13.55, afwGeom.arcseconds) # plate scale, in angle on sky/mm radialDistortion = 0. # radial distortion in mm/rad^2 radialCoeff = numpy.array((0.0, 1.0, 0.0, radialDistortion)) / plateScale.asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform(radialCoeff) pupilToFocalPlane = afwGeom.InvertedXYTransform(focalPlaneToPupil) cameraTransformMap = cameraGeom.CameraTransformMap(cameraGeom.FOCAL_PLANE, {cameraGeom.PUPIL: pupilToFocalPlane}) detectorList = self._makeDetectorList(pupilToFocalPlane, plateScale) cameraGeom.Camera.__init__(self, "ctio0m9", detectorList, cameraTransformMap)
def __init__(self, cameraYamlFile): """Construct a Camera """ with file(cameraYamlFile) as fd: cameraParams = yaml.load(fd, Loader=yaml.Loader) plateScale = afwGeom.Angle(cameraParams["plateScale"], afwGeom.arcseconds) radialCoeffs = np.array(cameraParams["radialCoeffs"])/plateScale.asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform(radialCoeffs) pupilToFocalPlane = afwGeom.InvertedXYTransform(focalPlaneToPupil) cameraTransformMap = cameraGeom.CameraTransformMap(cameraGeom.FOCAL_PLANE, {cameraGeom.PUPIL: pupilToFocalPlane}) detectorList = self._makeDetectorList(cameraParams["CCDs"], pupilToFocalPlane, plateScale) cameraGeom.Camera.__init__(self, cameraParams["name"], detectorList, cameraTransformMap)
def testLargeDistortion(self): # This transform is about as extreme as I can get: # using 0.0005 in the last value appears to produce numerical issues. # It produces a maximum deviation of 459 pixels, which should be sufficient. pixelsToTanPixels = afwGeom.RadialXYTransform([0.0, 1.1, 0.0004]) self.distortedWcs = afwImage.DistortedTanWcs(self.wcs, pixelsToTanPixels) def applyDistortion(src): out = src.table.copyRecord(src) out.set(out.table.getCentroidKey(), pixelsToTanPixels.reverseTransform(src.getCentroid())) return out self.singleTestInstance(self.filename, applyDistortion)
def __init__(self): """Construct a TestCamera """ # This is basically the same as specifying the scale in pixels plateScale = afwGeom.Angle( 1., afwGeom.arcseconds) # plate scale, in angle on sky/mm radialDistortion = 0. # radial distortion in mm/rad^2, i.e. no distortion radialCoeff = numpy.array( (0.0, 1.0, 0.0, radialDistortion)) / plateScale.asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform(radialCoeff) pupilToFocalPlane = afwGeom.InvertedXYTransform(focalPlaneToPupil) cameraTransformMap = cameraGeom.CameraTransformMap( cameraGeom.FOCAL_PLANE, {cameraGeom.PUPIL: pupilToFocalPlane}) detectorList = self._makeDetectorList(pupilToFocalPlane, plateScale) cameraGeom.Camera.__init__(self, "file", detectorList, cameraTransformMap)
def testBasics(self): pixelsToTanPixels = afwGeom.RadialXYTransform([0, 1.001, 0.00003]) distortedWcs = afwImage.DistortedTanWcs(self.tanWcs, pixelsToTanPixels) tanWcsCopy = distortedWcs.getTanWcs() self.assertEqual(self.tanWcs, tanWcsCopy) self.assertFalse(self.tanWcs.hasDistortion()) self.assertTrue(distortedWcs.hasDistortion()) try: self.tanWcs == distortedWcs self.fail("== should not be implemented for DistortedTanWcs") except Exception: pass try: distortedWcs == self.tanWcs self.fail("== should not be implemented for DistortedTanWcs") except Exception: pass
def testFlatFocalPlane(self): """Test an undistorted focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixSizeFactor = numpy.array((1.2, 0.8)) pixelSizeMm = afwGeom.Extent2D(0.02 * pixSizeFactor[0], 0.02 * pixSizeFactor[1]) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) fpPosition = afwGeom.Point2D( 50, 25) # focal-plane position of ref position on detector (mm) refPoint = afwGeom.Point2D( -0.5, -0.5) # ref position on detector (pos of lower left corner) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToPupil = afwGeom.RadialXYTransform((0.0, plateScaleRad)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToPupil=focalPlaneToPupil, pixelSizeMm=pixelSizeMm, plateScale=plateScale, ) # with no distortion, this should be a unity transform ctrPointPix = numpy.array(afwGeom.Box2D(bbox).getCenter()) for pointPix in ( afwGeom.Point2D(0, 0), afwGeom.Point2D(1000, 2000), afwGeom.Point2D(-100.5, 27.23), ): pointTanPix = pixelToTanPixel.forwardTransform(pointPix) predPointTanPix = ((numpy.array(pointPix) - ctrPointPix) * pixSizeFactor) + ctrPointPix for i in range(2): self.assertAlmostEquals(pointTanPix[i], predPointTanPix[i])
def testGetDistortedWcs(self): """Test utils.getDistortedWcs """ dw = DetectorWrapper() detector = dw.detector # the standard case: the exposure's WCS is pure TAN WCS and distortion information is available; # return a DistortedTanWcs exposure = afwImage.ExposureF(10, 10) exposure.setDetector(detector) exposure.setWcs(self.tanWcs) self.assertFalse(self.tanWcs.hasDistortion()) outWcs = getDistortedWcs(exposure.getInfo()) self.assertTrue(outWcs.hasDistortion()) self.assertIsInstance(outWcs, afwImage.DistortedTanWcs) del exposure # avoid accidental reuse del outWcs # return the original WCS if the exposure's WCS has distortion pixelsToTanPixels = afwGeom.RadialXYTransform([0, 1.001, 0.00003]) distortedWcs = afwImage.DistortedTanWcs(self.tanWcs, pixelsToTanPixels) self.assertTrue(distortedWcs.hasDistortion()) exposure = afwImage.ExposureF(10, 10) exposure.setWcs(distortedWcs) exposure.setDetector(detector) outWcs = getDistortedWcs(exposure.getInfo()) self.assertTrue(outWcs.hasDistortion()) self.assertIsInstance(outWcs, afwImage.DistortedTanWcs) del exposure del distortedWcs del outWcs # raise an exception if exposure has no WCS exposure = afwImage.ExposureF(10, 10) exposure.setDetector(detector) with self.assertRaises(Exception): getDistortedWcs(exposure.getInfo()) del exposure # return the original pure TAN WCS if the exposure has no detector exposure = afwImage.ExposureF(10, 10) exposure.setWcs(self.tanWcs) outWcs = getDistortedWcs(exposure.getInfo()) self.assertFalse(outWcs.hasDistortion()) self.assertIsInstance(outWcs, afwImage.TanWcs) self.assertNotIsInstance(outWcs, afwImage.DistortedTanWcs) del exposure del outWcs # return the original pure TAN WCS if the exposure's detector has no # TAN_PIXELS transform def removeTanPixels(detectorWrapper): tanPixSys = detector.makeCameraSys(TAN_PIXELS) detectorWrapper.transMap.pop(tanPixSys) detectorNoTanPix = DetectorWrapper(modFunc=removeTanPixels).detector exposure = afwImage.ExposureF(10, 10) exposure.setWcs(self.tanWcs) exposure.setDetector(detectorNoTanPix) outWcs = getDistortedWcs(exposure.getInfo()) self.assertFalse(outWcs.hasDistortion()) self.assertIsInstance(outWcs, afwImage.TanWcs) self.assertNotIsInstance(outWcs, afwImage.DistortedTanWcs) del exposure del outWcs
def __init__(self, name="detector 1", id=1, detType=SCIENCE, serial="xkcd722", bbox=None, # do not use mutable objects as defaults numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, modFunc=None, ): """!Construct a DetectorWrapper @param[in] name detector name @param[in] id detector ID (int) @param[in] detType detector type (an lsst.afw.cameraGeom.DetectorType) @param[in] serial serial "number" (a string) @param[in] bbox bounding box; defaults to (0, 0), (1024x1024) (an lsst.afw.geom.Box2I) @param[in] numAmps number of amplifiers (int) @param[in] pixelSize pixel size (mm) (an lsst.afw.geom.Point2D) @param[in] ampExtent dimensions of amplifier image bbox (an lsst.afw.geom.Extent2I) @param[in] orientation orientation of CCC in focal plane (lsst.afw.cameraGeom.Orientation) @param[in] plateScale plate scale in arcsec/mm; 20.0 is for LSST @param[in] radialDistortion radial distortion, in mm/rad^2 (the r^3 coefficient of the radial distortion polynomial that converts PUPIL in radians to FOCAL_PLANE in mm); 0.925 is the value Dave Monet measured for lsstSim data @param[in] modFunc a function that can modify attributes just before constructing the detector; modFunc receives one argument: a DetectorWrapper with all attributes except detector set. """ # note that (0., 0.) for the reference position is the center of the first pixel self.name = name self.id = int(id) self.type = detType self.serial = serial if bbox is None: bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1024, 1048)) self.bbox = bbox self.pixelSize = afwGeom.Extent2D(*pixelSize) self.ampExtent = afwGeom.Extent2I(*ampExtent) self.plateScale = float(plateScale) self.radialDistortion = float(radialDistortion) schema = afwTable.AmpInfoTable.makeMinimalSchema() self.ampInfo = afwTable.AmpInfoCatalog(schema) for i in range(numAmps): record = self.ampInfo.addNew() ampName = "amp %d" % (i + 1,) record.setName(ampName) record.setBBox(afwGeom.Box2I(afwGeom.Point2I(-1, 1), self.ampExtent)) record.setGain(1.71234e3) record.setReadNoise(0.521237e2) record.setReadoutCorner(afwTable.LL) record.setHasRawInfo(False) self.orientation = orientation # compute TAN_PIXELS transform pScaleRad = afwGeom.arcsecToRad(self.plateScale) radialDistortCoeffs = [0.0, 1.0/pScaleRad, 0.0, self.radialDistortion/pScaleRad] focalPlaneToPupil = afwGeom.RadialXYTransform(radialDistortCoeffs) pixelToTanPixel = makePixelToTanPixel( bbox = self.bbox, orientation = self.orientation, focalPlaneToPupil = focalPlaneToPupil, pixelSizeMm = self.pixelSize, plateScale = self.plateScale, ) self.transMap = { FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), CameraSys(TAN_PIXELS, self.name): pixelToTanPixel, CameraSys(ACTUAL_PIXELS, self.name): afwGeom.RadialXYTransform([0, 0.95, 0.01]), } if modFunc: modFunc(self) self.detector = Detector( self.name, self.id, self.type, self.serial, self.bbox, self.ampInfo, self.orientation, self.pixelSize, self.transMap, )
def testRadial(self): """Test fit with radial distortion The offset comes from the fact that the CCD is not centered """ self.doTest(afwGeom.RadialXYTransform([0, 1.01, 1e-7]))
def testWarnings(self): """Test that approximateWcs raises a UserWarning when it cannot achieve desired tolerance""" radialTransform = afwGeom.RadialXYTransform([0, 2.0, 3.0]) wcs = afwImage.DistortedTanWcs(self.tanWcs, radialTransform) with self.assertRaises(UserWarning): approximateWcs(wcs=wcs, bbox=self.bbox, order=2)
def setUp(self): self.nativeSys = cameraGeom.FOCAL_PLANE self.pupilTransform = afwGeom.RadialXYTransform([0, 0.5, 0.005]) transforms = {cameraGeom.PUPIL: self.pupilTransform} self.transformMap = cameraGeom.CameraTransformMap( self.nativeSys, transforms)