def makePixelToTanPixel(bbox, orientation, focalPlaneToPupil, pixelSizeMm): """!Make an XYTransform whose forward direction converts PIXEL to TAN_PIXEL 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] focalPlaneToPupil an lsst.afw.math.XYTransform that converts from focal plane (mm) to pupil coordinates (radians) in the forward direction @param[in] pixelSizeMm size of the pixel in mm in X and Y (an lsst.afw.geom.Extent2D) """ pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) pixelToPupil = afwGeom.MultiXYTransform( (pixelToFocalPlane, focalPlaneToPupil)) # pupilToTanPix is affine and matches pupilToPix at pupil center # Note: focal plane to pupil is typically a radial transform, # and linearizing the inverse transform of that may fail, # so linearize the forward direction instead. (pixelToPupil is pixelToFocalPlane, # an affine transform, followed by focalPlaneToPupil, # so the same consideration applies to pixelToPupil) pixAtPupilCtr = pixelToPupil.reverseTransform(afwGeom.Point2D(0, 0)) tanPixToPupilAffine = pixelToPupil.linearizeForwardTransform(pixAtPupilCtr) pupilToTanPix = afwGeom.AffineXYTransform(tanPixToPupilAffine.invert()) return afwGeom.MultiXYTransform((pixelToPupil, pupilToTanPix))
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 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 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 makePixelToTanPixel(bbox, orientation, focalPlaneToPupil, pixelSizeMm, plateScale): """!Make an XYTransform whose forward direction converts PIXEL to TAN_PIXEL 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] focalPlaneToPupil an lsst.afw.math.XYTransform that converts from focal plane (mm) to pupil coordinates (radians) in the forward direction @param[in] pixelSizeMm size of the pixel in mm in X and Y (an lsst.afw.geom.Extent2D) @param[in] plateScale plate scale of the camera in arcsec/mm (a double) """ pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) meanPixelSizeMm = (pixelSizeMm[0] + pixelSizeMm[1]) / 2.0 radPerMeanPix = afwGeom.Angle( plateScale, afwGeom.arcseconds).asRadians() * meanPixelSizeMm detCtrPix = afwGeom.Box2D(bbox).getCenter() detCtrTanPix = detCtrPix # by definition detCtrPupil = focalPlaneToPupil.forwardTransform( pixelToFocalPlane.forwardTransform(detCtrPix)) pupilTanPixAngRad = -orientation.getYaw().asRadians() pupilTanPixSin = math.sin(pupilTanPixAngRad) pupilTanPixCos = math.cos(pupilTanPixAngRad) tanPixToPupilRotMat = numpy.array(( (pupilTanPixCos, pupilTanPixSin), (-pupilTanPixSin, pupilTanPixCos), )) * radPerMeanPix tanPixToPupilRotTransform = afwGeom.AffineTransform(tanPixToPupilRotMat) tanPixCtrMinus0Pupil = tanPixToPupilRotTransform(detCtrTanPix) tanPix0Pupil = numpy.array(detCtrPupil) - numpy.array(tanPixCtrMinus0Pupil) tanPixToPupilAffine = afwGeom.AffineTransform(tanPixToPupilRotMat, numpy.array(tanPix0Pupil)) pupilToTanPix = afwGeom.AffineXYTransform(tanPixToPupilAffine.invert()) return afwGeom.MultiXYTransform( (pixelToFocalPlane, focalPlaneToPupil, pupilToTanPix))