import math import pytest from photonlibpy.estimation import TargetModel, VisionEstimation from photonlibpy.photonCamera import PhotonCamera from photonlibpy.simulation import PhotonCameraSim, VisionSystemSim, VisionTargetSim from robotpy_apriltag import AprilTag, AprilTagFieldLayout from wpimath.geometry import ( Pose2d, Pose3d, Rotation2d, Rotation3d, Transform3d, Translation2d, Translation3d, ) from wpimath.units import feetToMeters, meters def test_VisibilityCupidShuffle() -> None: targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi)) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=1.0, height=1.0), 4774 ) ] ) # To the right, to the right robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(-70.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() # To the right, to the right robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(-95.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() # To the left, to the left robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(90.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() # To the left, to the left robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(65.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() # Now kick, now kick robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() # Now kick, now kick robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(-5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() # Now walk it by yourself robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(-179.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() # Now walk it by yourself visionSysSim.adjustCamera( cameraSim, Transform3d(Translation3d(), Rotation3d(0, 0, math.pi)) ) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() def test_NotVisibleVert1() -> None: targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi)) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=3.0, height=3.0), 4774 ) ] ) robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() visionSysSim.adjustCamera( cameraSim, Transform3d(Translation3d(0.0, 0.0, 5000.0), Rotation3d(0.0, 0.0, math.pi)), ) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() def test_NotVisibleVert2() -> None: targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi)) robotToCamera = Transform3d( Translation3d(0.0, 0.0, 1.0), Rotation3d(0.0, -math.pi / 4.0, 0.0) ) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, robotToCamera) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV( 4774, 4774, fovDiag=Rotation2d.fromDegrees(80.0) ) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774 ) ] ) robotPose = Pose2d(Translation2d(13.98, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() def test_NotVisibleTargetSize() -> None: targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi)) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) cameraSim.setMinTargetAreaPixels(20.0) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=0.1, height=0.1), 4774 ) ] ) robotPose = Pose2d(Translation2d(12.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() def test_NotVisibleTooFarLeds() -> None: targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi)) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) cameraSim.setMinTargetAreaPixels(1.0) cameraSim.setMaxSightRange(10.0) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=1.0, height=1.0), 4774 ) ] ) robotPose = Pose2d(Translation2d(10.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert camera.getLatestResult().hasTargets() robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0)) visionSysSim.update(robotPose) assert not camera.getLatestResult().hasTargets() @pytest.mark.parametrize( "expected_yaw", [-10.0, -5.0, -2.0, -1.0, 0.0, 5.0, 7.0, 10.23] ) def test_YawAngles(expected_yaw) -> None: targetPose = Pose3d( Translation3d(15.98, 0.0, 1.0), Rotation3d(0.0, 0.0, 3.0 * math.pi / 4.0) ) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) cameraSim.setMinTargetAreaPixels(0.0) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774 ) ] ) robotPose = Pose2d(Translation2d(10.0, 0.0), Rotation2d.fromDegrees(expected_yaw)) visionSysSim.update(robotPose) result = camera.getLatestResult() bestTarget = result.getBestTarget() assert bestTarget is not None assert bestTarget.getYaw() == pytest.approx(expected_yaw, abs=0.25) @pytest.mark.parametrize( "expected_pitch", [-10.0, -5.0, -2.0, -1.0, 0.0, 5.0, 7.0, 10.23] ) def test_PitchAngles(expected_pitch) -> None: targetPose = Pose3d( Translation3d(15.98, 0.0, 0.0), Rotation3d(0, 0, 3.0 * math.pi / 4.0) ) robotPose = Pose2d( Translation2d(10.0, 0.0), Rotation2d.fromDegrees(-expected_pitch) ) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV( 640, 480, fovDiag=Rotation2d.fromDegrees(120.0) ) cameraSim.setMinTargetAreaPixels(0.0) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774 ) ] ) visionSysSim.adjustCamera( cameraSim, Transform3d( Translation3d(), Rotation3d(0.0, math.radians(expected_pitch), 0.0) ), ) visionSysSim.update(robotPose) result = camera.getLatestResult() bestTarget = result.getBestTarget() assert bestTarget is not None assert bestTarget.getPitch() == pytest.approx(expected_pitch, abs=0.25) @pytest.mark.parametrize( "distParam, pitchParam, heightParam", [ (5, -15.98, 0), (6, -15.98, 1), (10, -15.98, 0), (15, -15.98, 2), (19.95, -15.98, 0), (20, -15.98, 0), (5, -42, 1), (6, -42, 0), (10, -42, 2), (15, -42, 0.5), (19.42, -15.98, 0), (20, -42, 0), (5, -55, 2), (6, -55, 0), (10, -54, 2.2), (15, -53, 0), (19.52, -15.98, 1.1), ], ) def test_distanceCalc(distParam, pitchParam, heightParam) -> None: distParam = feetToMeters(distParam) pitchParam = math.radians(pitchParam) heightParam = feetToMeters(heightParam) targetPose = Pose3d( Translation3d(15.98, 0.0, 1.0), Rotation3d(0.0, 0.0, 0.98 * math.pi) ) robotPose = Pose3d(Translation3d(15.98 - distParam, 0.0, 0.0), Rotation3d()) robotToCamera = Transform3d( Translation3d(0.0, 0.0, heightParam), Rotation3d(0.0, pitchParam, 0.0) ) visionSysSim = VisionSystemSim( "absurdlylongnamewhichshouldneveractuallyhappenbuteehwelltestitanywaysohowsyourdaygoingihopegoodhaveagreatrestofyourlife" ) camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV( 640, 480, fovDiag=Rotation2d.fromDegrees(160.0) ) cameraSim.setMinTargetAreaPixels(0.0) visionSysSim.adjustCamera(cameraSim, robotToCamera) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774 ) ] ) visionSysSim.update(robotPose) result = camera.getLatestResult() target = result.getBestTarget() assert target is not None assert target.getYaw() == pytest.approx(0.0, abs=0.5) # TODO Enable when PhotonUtils is ported # dist = PhotonUtils.calculateDistanceToTarget( # robotToCamera.Z(), targetPose.Z(), -pitchParam, math.degrees(target.getPitch()) # ) # assert dist == pytest.approx(distParam, abs=0.25) def test_MultipleTargets() -> None: targetPoseL = Pose3d(Translation3d(15.98, 2.0, 0.0), Rotation3d(0.0, 0.0, math.pi)) targetPoseC = Pose3d(Translation3d(15.98, 0.0, 0.0), Rotation3d(0.0, 0.0, math.pi)) targetPoseR = Pose3d(Translation3d(15.98, -2.0, 0.0), Rotation3d(0.0, 0.0, math.pi)) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0)) cameraSim.setMinTargetAreaPixels(20.0) visionSysSim.addVisionTargets( [ VisionTargetSim( targetPoseL.transformBy( Transform3d(Translation3d(0, 0, 0), Rotation3d()) ), TargetModel.AprilTag16h5(), 1, ), VisionTargetSim( targetPoseC.transformBy( Transform3d(Translation3d(0, 0, 0), Rotation3d()) ), TargetModel.AprilTag16h5(), 2, ), VisionTargetSim( targetPoseR.transformBy( Transform3d(Translation3d(0, 0, 0), Rotation3d()) ), TargetModel.AprilTag16h5(), 3, ), VisionTargetSim( targetPoseL.transformBy( Transform3d(Translation3d(0, 0, 1), Rotation3d()) ), TargetModel.AprilTag16h5(), 4, ), VisionTargetSim( targetPoseC.transformBy( Transform3d(Translation3d(0, 0, 1), Rotation3d()) ), TargetModel.AprilTag16h5(), 5, ), VisionTargetSim( targetPoseR.transformBy( Transform3d(Translation3d(0, 0, 1), Rotation3d()) ), TargetModel.AprilTag16h5(), 6, ), VisionTargetSim( targetPoseL.transformBy( Transform3d(Translation3d(0, 0, 0.5), Rotation3d()) ), TargetModel.AprilTag16h5(), 7, ), VisionTargetSim( targetPoseC.transformBy( Transform3d(Translation3d(0, 0, 0.5), Rotation3d()) ), TargetModel.AprilTag16h5(), 8, ), VisionTargetSim( targetPoseL.transformBy( Transform3d(Translation3d(0, 0, 0.75), Rotation3d()) ), TargetModel.AprilTag16h5(), 9, ), VisionTargetSim( targetPoseR.transformBy( Transform3d(Translation3d(0, 0, 0.75), Rotation3d()) ), TargetModel.AprilTag16h5(), 10, ), VisionTargetSim( targetPoseL.transformBy( Transform3d(Translation3d(0, 0, 0.25), Rotation3d()) ), TargetModel.AprilTag16h5(), 11, ), ] ) robotPose = Pose2d(Translation2d(6.0, 0.0), Rotation2d.fromDegrees(0.25)) visionSysSim.update(robotPose) res = camera.getLatestResult() assert res.hasTargets() tgtList = res.getTargets() assert len(tgtList) == 11 def test_PoseEstimation() -> None: visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, Transform3d()) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0)) cameraSim.setMinTargetAreaPixels(20.0) tagList: list[AprilTag] = [] at0 = AprilTag() at0.ID = 0 at0.pose = Pose3d(12.0, 3.0, 1.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at0) at1 = AprilTag() at1.ID = 1 at1.pose = Pose3d(12.0, 1.0, -1.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at1) at2 = AprilTag() at2.ID = 2 at2.pose = Pose3d(11.0, 0.0, 2.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at2) fieldLength: meters = 54.0 fieldWidth: meters = 27.0 layout = AprilTagFieldLayout(tagList, fieldLength, fieldWidth) robotPose = Pose2d(Translation2d(5.0, 1.0), Rotation2d.fromDegrees(5.0)) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[0].pose, TargetModel.AprilTag16h5(), 0)] ) visionSysSim.update(robotPose) camEigen = cameraSim.prop.getIntrinsics() distEigen = cameraSim.prop.getDistCoeffs() camResults = camera.getLatestResult() targets = camResults.getTargets() results = VisionEstimation.estimateCamPosePNP( camEigen, distEigen, targets, layout, TargetModel.AprilTag16h5() ) assert results is not None pose: Pose3d = Pose3d() + results.best assert pose.X() == pytest.approx(5.0, abs=0.01) assert pose.Y() == pytest.approx(1.0, abs=0.01) assert pose.Z() == pytest.approx(0.0, abs=0.01) assert pose.rotation().Z() == pytest.approx(math.radians(5.0), abs=0.01) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[1].pose, TargetModel.AprilTag16h5(), 1)] ) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[2].pose, TargetModel.AprilTag16h5(), 2)] ) visionSysSim.update(robotPose) camResults2 = camera.getLatestResult() targets2 = camResults2.getTargets() results2 = VisionEstimation.estimateCamPosePNP( camEigen, distEigen, targets2, layout, TargetModel.AprilTag16h5() ) assert results2 is not None pose2 = Pose3d() + results2.best assert pose2.X() == pytest.approx(robotPose.X(), abs=0.01) assert pose2.Y() == pytest.approx(robotPose.Y(), abs=0.01) assert pose2.Z() == pytest.approx(0.0, abs=0.01) assert pose2.rotation().Z() == pytest.approx(math.radians(5.0), abs=0.01) def test_PoseEstimationRotated() -> None: robotToCamera = Transform3d( Translation3d(6.0 * 0.0254, 6.0 * 0.0254, 6.0 * 0.0254), Rotation3d(0.0, math.radians(-30.0), math.radians(25.5)), ) visionSysSim = VisionSystemSim("Test") camera = PhotonCamera("camera") cameraSim = PhotonCameraSim(camera) visionSysSim.addCamera(cameraSim, robotToCamera) # Set massive FPS so timing isn't an issue cameraSim.prop.setFPS(1e6) cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0)) cameraSim.setMinTargetAreaPixels(20.0) tagList: list[AprilTag] = [] at0 = AprilTag() at0.ID = 0 at0.pose = Pose3d(12.0, 3.0, 1.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at0) at1 = AprilTag() at1.ID = 1 at1.pose = Pose3d(12.0, 1.0, -1.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at1) at2 = AprilTag() at2.ID = 2 at2.pose = Pose3d(11.0, 0.0, 2.0, Rotation3d(0.0, 0.0, math.pi)) tagList.append(at2) fieldLength: meters = 54.0 fieldWidth: meters = 27.0 layout = AprilTagFieldLayout(tagList, fieldLength, fieldWidth) robotPose = Pose2d(Translation2d(5.0, 1.0), Rotation2d.fromDegrees(-5.0)) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[0].pose, TargetModel.AprilTag36h11(), 0)] ) visionSysSim.update(robotPose) camEigen = cameraSim.prop.getIntrinsics() distEigen = cameraSim.prop.getDistCoeffs() camResults = camera.getLatestResult() targets = camResults.getTargets() results = VisionEstimation.estimateCamPosePNP( camEigen, distEigen, targets, layout, TargetModel.AprilTag36h11() ) assert results is not None pose: Pose3d = Pose3d() + results.best pose = pose.transformBy(robotToCamera.inverse()) assert pose.X() == pytest.approx(5.0, abs=0.01) assert pose.Y() == pytest.approx(1.0, abs=0.01) assert pose.Z() == pytest.approx(0.0, abs=0.01) assert pose.rotation().Z() == pytest.approx(math.radians(-5.0), abs=0.01) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[1].pose, TargetModel.AprilTag36h11(), 1)] ) visionSysSim.addVisionTargets( [VisionTargetSim(tagList[2].pose, TargetModel.AprilTag36h11(), 2)] ) visionSysSim.update(robotPose) camResults2 = camera.getLatestResult() targets2 = camResults2.getTargets() results2 = VisionEstimation.estimateCamPosePNP( camEigen, distEigen, targets2, layout, TargetModel.AprilTag36h11() ) assert results2 is not None pose2 = Pose3d() + results2.best pose2 = pose2.transformBy(robotToCamera.inverse()) assert pose2.X() == pytest.approx(robotPose.X(), abs=0.01) assert pose2.Y() == pytest.approx(robotPose.Y(), abs=0.01) assert pose2.Z() == pytest.approx(0.0, abs=0.01) assert pose2.rotation().Z() == pytest.approx(math.radians(-5.0), abs=0.01)