mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-23 01:21:40 +00:00
[photonlibpy] add mypy to ci (#1570)
Co-authored-by: James Ward <james@thedropbears.org.au>
This commit is contained in:
9
.github/workflows/python.yml
vendored
9
.github/workflows/python.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel pytest
|
||||
pip install setuptools wheel pytest mypy
|
||||
|
||||
- name: Build wheel
|
||||
working-directory: ./photon-lib/py
|
||||
@@ -50,6 +50,13 @@ jobs:
|
||||
pip install --no-cache-dir dist/*.whl
|
||||
pytest
|
||||
|
||||
- name: Run mypy type checking
|
||||
uses: liskin/gh-problem-matcher-wrap@v3
|
||||
with:
|
||||
linters: mypy
|
||||
run: |
|
||||
mypy --show-column-numbers --config-file photon-lib/py/pyproject.toml photon-lib
|
||||
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
|
||||
@@ -128,7 +128,6 @@ class OpenCVHelp:
|
||||
alt: Transform3d | None = None
|
||||
reprojectionError: cv.typing.MatLike | None = None
|
||||
best: Transform3d = Transform3d()
|
||||
alt: Transform3d | None = None
|
||||
|
||||
for tries in range(2):
|
||||
retval, rvecs, tvecs, reprojectionError = cv.solvePnPGeneric(
|
||||
|
||||
@@ -18,6 +18,8 @@ class TargetModel:
|
||||
verts: List[Translation3d] | None = None
|
||||
):
|
||||
|
||||
self.vertices: List[Translation3d] = []
|
||||
|
||||
if (
|
||||
width is not None
|
||||
and height is not None
|
||||
@@ -88,7 +90,7 @@ class TargetModel:
|
||||
|
||||
self.isSpherical = False
|
||||
if len(verts) <= 2:
|
||||
self.vertices: List[Translation3d] = []
|
||||
self.vertices = []
|
||||
self.isPlanar = False
|
||||
else:
|
||||
cornersPlaner = True
|
||||
|
||||
@@ -16,10 +16,10 @@ class VisionEstimation:
|
||||
id = tag.getFiducialId()
|
||||
maybePose = layout.getTagPose(id)
|
||||
if maybePose:
|
||||
tag = AprilTag()
|
||||
tag.ID = id
|
||||
tag.pose = maybePose
|
||||
retVal.append(tag)
|
||||
aprilTag = AprilTag()
|
||||
aprilTag.ID = id
|
||||
aprilTag.pose = maybePose
|
||||
retVal.append(aprilTag)
|
||||
return retVal
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -20,8 +20,14 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import MultiTargetPNPResult # noqa
|
||||
from ..targeting import PnpResult # noqa
|
||||
|
||||
|
||||
class MultiTargetPNPResultSerde:
|
||||
|
||||
@@ -20,8 +20,13 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import PhotonPipelineMetadata # noqa
|
||||
|
||||
|
||||
class PhotonPipelineMetadataSerde:
|
||||
|
||||
@@ -20,8 +20,16 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import MultiTargetPNPResult # noqa
|
||||
from ..targeting import PhotonPipelineMetadata # noqa
|
||||
from ..targeting import PhotonPipelineResult # noqa
|
||||
from ..targeting import PhotonTrackedTarget # noqa
|
||||
|
||||
|
||||
class PhotonPipelineResultSerde:
|
||||
|
||||
@@ -20,8 +20,14 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import PhotonTrackedTarget # noqa
|
||||
from ..targeting import TargetCorner # noqa
|
||||
|
||||
|
||||
class PhotonTrackedTargetSerde:
|
||||
|
||||
@@ -20,8 +20,13 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import PnpResult # noqa
|
||||
|
||||
|
||||
class PnpResultSerde:
|
||||
|
||||
@@ -20,8 +20,13 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import *
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..targeting import TargetCorner # noqa
|
||||
|
||||
|
||||
class TargetCornerSerde:
|
||||
|
||||
@@ -41,7 +41,9 @@ class PhotonCameraSim:
|
||||
self.videoSimRawEnabled: bool = False
|
||||
self.videoSimWireframeEnabled: bool = False
|
||||
self.videoSimWireframeResolution: float = 0.1
|
||||
self.videoSimProcEnabled: bool = True
|
||||
self.videoSimProcEnabled: bool = (
|
||||
False # TODO switch this back to default True when the functionality is enabled
|
||||
)
|
||||
self.heartbeatCounter: int = 0
|
||||
self.nextNtEntryTime = int(wpilib.Timer.getFPGATimestamp() * 1e6)
|
||||
self.tagLayout = AprilTagFieldLayout.loadField(AprilTagField.k2024Crescendo)
|
||||
@@ -191,19 +193,19 @@ class PhotonCameraSim:
|
||||
self.maxSightRange = range
|
||||
|
||||
def enableRawStream(self, enabled: bool) -> None:
|
||||
self.videoSimRawEnabled = enabled
|
||||
raise Exception("Raw stream not implemented")
|
||||
# self.videoSimRawEnabled = enabled
|
||||
|
||||
def enableDrawWireframe(self, enabled: bool) -> None:
|
||||
self.videoSimWireframeEnabled = enabled
|
||||
raise Exception("Wireframe not implemented")
|
||||
# self.videoSimWireframeEnabled = enabled
|
||||
|
||||
def setWireframeResolution(self, resolution: float) -> None:
|
||||
self.videoSimWireframeResolution = resolution
|
||||
|
||||
def enableProcessedStream(self, enabled: bool) -> None:
|
||||
self.videoSimProcEnabled = enabled
|
||||
raise Exception("Processed stream not implemented")
|
||||
# self.videoSimProcEnabled = enabled
|
||||
|
||||
def process(
|
||||
self, latency: seconds, cameraPose: Pose3d, targets: list[VisionTargetSim]
|
||||
@@ -321,13 +323,13 @@ class PhotonCameraSim:
|
||||
)
|
||||
|
||||
# Video streams disabled for now
|
||||
if self.enableRawStream:
|
||||
if self.videoSimRawEnabled:
|
||||
# VideoSimUtil::UpdateVideoProp(videoSimRaw, prop);
|
||||
# cv::Size videoFrameSize{prop.GetResWidth(), prop.GetResHeight()};
|
||||
# cv::Mat blankFrame = cv::Mat::zeros(videoFrameSize, CV_8UC1);
|
||||
# blankFrame.assignTo(videoSimFrameRaw);
|
||||
pass
|
||||
if self.enableProcessedStream:
|
||||
if self.videoSimProcEnabled:
|
||||
# VideoSimUtil::UpdateVideoProp(videoSimProcessed, prop);
|
||||
pass
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ class SimCameraProperties:
|
||||
raise Exception("not a correct function sig")
|
||||
|
||||
if has_fov_args:
|
||||
# really convince python we are doing the right thing
|
||||
assert fovDiag is not None
|
||||
if fovDiag.degrees() < 1.0 or fovDiag.degrees() > 179.0:
|
||||
fovDiag = Rotation2d.fromDegrees(
|
||||
max(min(fovDiag.degrees(), 179.0), 1.0)
|
||||
|
||||
@@ -228,8 +228,8 @@ class VisionSystemSim:
|
||||
|
||||
camResult = camSim.process(latency, lateCameraPose, allTargets)
|
||||
camSim.submitProcessedFrame(camResult, timestampNt)
|
||||
for target in camResult.getTargets():
|
||||
trf = target.getBestCameraToTarget()
|
||||
for tgt in camResult.getTargets():
|
||||
trf = tgt.getBestCameraToTarget()
|
||||
if trf == Transform3d():
|
||||
continue
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import generated
|
||||
from ..generated.TargetCornerSerde import TargetCornerSerde
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -10,4 +10,4 @@ class TargetCorner:
|
||||
x: float = 0
|
||||
y: float = 9
|
||||
|
||||
photonStruct: ClassVar["generated.TargetCornerSerde"]
|
||||
photonStruct: ClassVar["TargetCornerSerde"]
|
||||
|
||||
@@ -4,7 +4,8 @@ from typing import TYPE_CHECKING, ClassVar
|
||||
from wpimath.geometry import Transform3d
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import generated
|
||||
from ..generated.MultiTargetPNPResultSerde import MultiTargetPNPResultSerde
|
||||
from ..generated.PnpResultSerde import PnpResultSerde
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -15,7 +16,7 @@ class PnpResult:
|
||||
bestReprojErr: float = 0.0
|
||||
altReprojErr: float = 0.0
|
||||
|
||||
photonStruct: ClassVar["generated.PnpResultSerde"]
|
||||
photonStruct: ClassVar["PnpResultSerde"]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -25,4 +26,4 @@ class MultiTargetPNPResult:
|
||||
estimatedPose: PnpResult = field(default_factory=PnpResult)
|
||||
fiducialIDsUsed: list[int] = field(default_factory=list)
|
||||
|
||||
photonStruct: ClassVar["generated.MultiTargetPNPResultSerde"]
|
||||
photonStruct: ClassVar["MultiTargetPNPResultSerde"]
|
||||
|
||||
@@ -5,7 +5,8 @@ from .multiTargetPNPResult import MultiTargetPNPResult
|
||||
from .photonTrackedTarget import PhotonTrackedTarget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import generated
|
||||
from ..generated.PhotonPipelineMetadataSerde import PhotonPipelineMetadataSerde
|
||||
from ..generated.PhotonPipelineResultSerde import PhotonPipelineResultSerde
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -20,7 +21,7 @@ class PhotonPipelineMetadata:
|
||||
|
||||
timeSinceLastPong: int = -1
|
||||
|
||||
photonStruct: ClassVar["generated.PhotonPipelineMetadataSerde"]
|
||||
photonStruct: ClassVar["PhotonPipelineMetadataSerde"]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -69,4 +70,4 @@ class PhotonPipelineResult:
|
||||
return None
|
||||
return self.getTargets()[0]
|
||||
|
||||
photonStruct: ClassVar["generated.PhotonPipelineResultSerde"]
|
||||
photonStruct: ClassVar["PhotonPipelineResultSerde"]
|
||||
|
||||
@@ -7,7 +7,7 @@ from ..packet import Packet
|
||||
from .TargetCorner import TargetCorner
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import generated
|
||||
from ..generated.PhotonTrackedTargetSerde import PhotonTrackedTargetSerde
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -63,4 +63,4 @@ class PhotonTrackedTarget:
|
||||
retList.append(TargetCorner(cx, cy))
|
||||
return retList
|
||||
|
||||
photonStruct: ClassVar["generated.PhotonTrackedTargetSerde"]
|
||||
photonStruct: ClassVar["PhotonTrackedTargetSerde"]
|
||||
|
||||
2
photon-lib/py/pyproject.toml
Normal file
2
photon-lib/py/pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.mypy]
|
||||
exclude = ["build","setup.py"]
|
||||
@@ -25,7 +25,7 @@ def setupCommon() -> None:
|
||||
setVersionCheckEnabled(False)
|
||||
|
||||
|
||||
def test_VisibilityCupidShuffle():
|
||||
def test_VisibilityCupidShuffle() -> None:
|
||||
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
@@ -83,7 +83,7 @@ def test_VisibilityCupidShuffle():
|
||||
assert camera.getLatestResult().hasTargets()
|
||||
|
||||
|
||||
def test_NotVisibleVert1():
|
||||
def test_NotVisibleVert1() -> None:
|
||||
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
@@ -111,7 +111,7 @@ def test_NotVisibleVert1():
|
||||
assert not camera.getLatestResult().hasTargets()
|
||||
|
||||
|
||||
def test_NotVisibleVert2():
|
||||
def test_NotVisibleVert2() -> None:
|
||||
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
@@ -138,7 +138,7 @@ def test_NotVisibleVert2():
|
||||
assert not camera.getLatestResult().hasTargets()
|
||||
|
||||
|
||||
def test_NotVisibleTargetSize():
|
||||
def test_NotVisibleTargetSize() -> None:
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
visionSysSim = VisionSystemSim("Test")
|
||||
@@ -161,7 +161,7 @@ def test_NotVisibleTargetSize():
|
||||
assert not camera.getLatestResult().hasTargets()
|
||||
|
||||
|
||||
def test_NotVisibleTooFarLeds():
|
||||
def test_NotVisibleTooFarLeds() -> None:
|
||||
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
@@ -189,7 +189,7 @@ def test_NotVisibleTooFarLeds():
|
||||
@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):
|
||||
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)
|
||||
@@ -211,14 +211,15 @@ def test_YawAngles(expected_yaw):
|
||||
|
||||
result = camera.getLatestResult()
|
||||
|
||||
assert result.hasTargets()
|
||||
assert result.getBestTarget().getYaw() == pytest.approx(expected_yaw, abs=0.25)
|
||||
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):
|
||||
def test_PitchAngles(expected_pitch) -> None:
|
||||
|
||||
targetPose = Pose3d(
|
||||
Translation3d(15.98, 0.0, 0.0), Rotation3d(0, 0, 3.0 * math.pi / 4.0)
|
||||
@@ -245,8 +246,9 @@ def test_PitchAngles(expected_pitch):
|
||||
|
||||
result = camera.getLatestResult()
|
||||
|
||||
assert result.hasTargets()
|
||||
assert result.getBestTarget().getPitch() == pytest.approx(expected_pitch, abs=0.25)
|
||||
bestTarget = result.getBestTarget()
|
||||
assert bestTarget is not None
|
||||
assert bestTarget.getPitch() == pytest.approx(expected_pitch, abs=0.25)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -271,7 +273,7 @@ def test_PitchAngles(expected_pitch):
|
||||
(19.52, -15.98, 1.1),
|
||||
],
|
||||
)
|
||||
def test_distanceCalc(distParam, pitchParam, heightParam):
|
||||
def test_distanceCalc(distParam, pitchParam, heightParam) -> None:
|
||||
distParam = feetToMeters(distParam)
|
||||
pitchParam = math.radians(pitchParam)
|
||||
heightParam = feetToMeters(heightParam)
|
||||
@@ -301,10 +303,8 @@ def test_distanceCalc(distParam, pitchParam, heightParam):
|
||||
|
||||
result = camera.getLatestResult()
|
||||
|
||||
assert result.hasTargets()
|
||||
|
||||
target = result.getBestTarget()
|
||||
|
||||
assert target is not None
|
||||
assert target.getYaw() == pytest.approx(0.0, abs=0.5)
|
||||
|
||||
# TODO Enable when PhotonUtils is ported
|
||||
@@ -314,7 +314,7 @@ def test_distanceCalc(distParam, pitchParam, heightParam):
|
||||
# assert dist == pytest.approx(distParam, abs=0.25)
|
||||
|
||||
|
||||
def test_MultipleTargets():
|
||||
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))
|
||||
@@ -415,7 +415,7 @@ def test_MultipleTargets():
|
||||
assert len(tgtList) == 11
|
||||
|
||||
|
||||
def test_PoseEstimation():
|
||||
def test_PoseEstimation() -> None:
|
||||
visionSysSim = VisionSystemSim("Test")
|
||||
camera = PhotonCamera("camera")
|
||||
cameraSim = PhotonCameraSim(camera)
|
||||
|
||||
@@ -20,11 +20,25 @@
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
{%- set ns = namespace(types=[]) -%}
|
||||
{%- for field in fields|unique(attribute="type")-%}
|
||||
{%- set _ = ns.types.append(field.type) -%}
|
||||
{%- endfor -%}
|
||||
{% set _ = ns.types.append(name) -%}
|
||||
{%- for type in ns.types|sort%}
|
||||
{%- if not type | is_shimmed and not type | is_intrinsic %}
|
||||
from ..targeting import {{ type }} # noqa
|
||||
{%- endif %}
|
||||
{%- endfor%}
|
||||
|
||||
|
||||
class {{ name }}Serde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "{{ message_hash }}"
|
||||
MESSAGE_FORMAT = "{{ message_fmt }}"
|
||||
|
||||
@@ -78,7 +78,7 @@ public class PhotonPipelineResultSerde implements PacketSerde<PhotonPipelineResu
|
||||
@Override
|
||||
public PacketSerde<?>[] getNestedPhotonMessages() {
|
||||
return new PacketSerde<?>[] {
|
||||
MultiTargetPNPResult.photonStruct,PhotonTrackedTarget.photonStruct,PhotonPipelineMetadata.photonStruct
|
||||
MultiTargetPNPResult.photonStruct,PhotonPipelineMetadata.photonStruct,PhotonTrackedTarget.photonStruct
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user