[photonlibpy] add mypy to ci (#1570)

Co-authored-by: James Ward <james@thedropbears.org.au>
This commit is contained in:
Lucien Morey
2024-11-14 02:39:02 +11:00
committed by GitHub
parent a7319ce1d6
commit a64491a59e
21 changed files with 116 additions and 51 deletions

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -0,0 +1,2 @@
[tool.mypy]
exclude = ["build","setup.py"]

View File

@@ -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)

View File

@@ -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 }}"

View File

@@ -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
};
}