mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-24 01:31:44 +00:00
Auto-generate packet dataclasses with Jinja (#1374)
This commit is contained in:
@@ -1 +1,6 @@
|
||||
# No one here but us chickens
|
||||
|
||||
from .packet import Packet # noqa
|
||||
from .estimatedRobotPose import EstimatedRobotPose # noqa
|
||||
from .photonPoseEstimator import PhotonPoseEstimator, PoseStrategy # noqa
|
||||
from .photonCamera import PhotonCamera # noqa
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from wpimath.geometry import Pose3d
|
||||
|
||||
from .photonTrackedTarget import PhotonTrackedTarget
|
||||
from .targeting.photonTrackedTarget import PhotonTrackedTarget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .photonPoseEstimator import PoseStrategy
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class MultiTargetPNPResultSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "ffc1cb847deb6e796a583a5b1885496b"
|
||||
MESSAGE_FORMAT = "PnpResult estimatedPose;int16[?] fiducialIDsUsed;"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "MultiTargetPNPResult":
|
||||
ret = MultiTargetPNPResult()
|
||||
|
||||
# estimatedPose is of non-intrinsic type PnpResult
|
||||
ret.estimatedPose = PnpResult.photonStruct.unpack(packet)
|
||||
|
||||
# fiducialIDsUsed is a custom VLA!
|
||||
ret.fiducialIDsUsed = packet.decodeShortList()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
MultiTargetPNPResult.photonStruct = MultiTargetPNPResultSerde()
|
||||
@@ -0,0 +1,51 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class PhotonPipelineMetadataSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "2a7039527bda14d13028a1b9282d40a2"
|
||||
MESSAGE_FORMAT = (
|
||||
"int64 sequenceID;int64 captureTimestampMicros;int64 publishTimestampMicros;"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "PhotonPipelineMetadata":
|
||||
ret = PhotonPipelineMetadata()
|
||||
|
||||
# sequenceID is of intrinsic type int64
|
||||
ret.sequenceID = packet.decodeLong()
|
||||
|
||||
# captureTimestampMicros is of intrinsic type int64
|
||||
ret.captureTimestampMicros = packet.decodeLong()
|
||||
|
||||
# publishTimestampMicros is of intrinsic type int64
|
||||
ret.publishTimestampMicros = packet.decodeLong()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
PhotonPipelineMetadata.photonStruct = PhotonPipelineMetadataSerde()
|
||||
@@ -0,0 +1,49 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class PhotonPipelineResultSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "cb3e1605048ba49325888eb797399fe2"
|
||||
MESSAGE_FORMAT = "PhotonPipelineMetadata metadata;PhotonTrackedTarget[?] targets;MultiTargetPNPResult? multiTagResult;"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "PhotonPipelineResult":
|
||||
ret = PhotonPipelineResult()
|
||||
|
||||
# metadata is of non-intrinsic type PhotonPipelineMetadata
|
||||
ret.metadata = PhotonPipelineMetadata.photonStruct.unpack(packet)
|
||||
|
||||
# targets is a custom VLA!
|
||||
ret.targets = packet.decodeList(PhotonTrackedTarget.photonStruct)
|
||||
|
||||
# multiTagResult is optional! it better not be a VLA too
|
||||
ret.multiTagResult = packet.decodeOptional(MultiTargetPNPResult.photonStruct)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
PhotonPipelineResult.photonStruct = PhotonPipelineResultSerde()
|
||||
@@ -0,0 +1,76 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class PhotonTrackedTargetSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "8fdada56b9162f2e32bd24f0055d7b60"
|
||||
MESSAGE_FORMAT = "float64 yaw;float64 pitch;float64 area;float64 skew;int32 fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d bestCameraToTarget;Transform3d altCameraToTarget;float64 poseAmbiguity;TargetCorner[?] minAreaRectCorners;TargetCorner[?] detectedCorners;"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "PhotonTrackedTarget":
|
||||
ret = PhotonTrackedTarget()
|
||||
|
||||
# yaw is of intrinsic type float64
|
||||
ret.yaw = packet.decodeDouble()
|
||||
|
||||
# pitch is of intrinsic type float64
|
||||
ret.pitch = packet.decodeDouble()
|
||||
|
||||
# area is of intrinsic type float64
|
||||
ret.area = packet.decodeDouble()
|
||||
|
||||
# skew is of intrinsic type float64
|
||||
ret.skew = packet.decodeDouble()
|
||||
|
||||
# fiducialId is of intrinsic type int32
|
||||
ret.fiducialId = packet.decodeInt()
|
||||
|
||||
# objDetectId is of intrinsic type int32
|
||||
ret.objDetectId = packet.decodeInt()
|
||||
|
||||
# objDetectConf is of intrinsic type float32
|
||||
ret.objDetectConf = packet.decodeFloat()
|
||||
|
||||
# field is shimmed!
|
||||
ret.bestCameraToTarget = packet.decodeTransform()
|
||||
|
||||
# field is shimmed!
|
||||
ret.altCameraToTarget = packet.decodeTransform()
|
||||
|
||||
# poseAmbiguity is of intrinsic type float64
|
||||
ret.poseAmbiguity = packet.decodeDouble()
|
||||
|
||||
# minAreaRectCorners is a custom VLA!
|
||||
ret.minAreaRectCorners = packet.decodeList(TargetCorner.photonStruct)
|
||||
|
||||
# detectedCorners is a custom VLA!
|
||||
ret.detectedCorners = packet.decodeList(TargetCorner.photonStruct)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
PhotonTrackedTarget.photonStruct = PhotonTrackedTargetSerde()
|
||||
55
photon-lib/py/photonlibpy/generated/PnpResultSerde.py
Normal file
55
photon-lib/py/photonlibpy/generated/PnpResultSerde.py
Normal file
@@ -0,0 +1,55 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class PnpResultSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "0d1f2546b00f24718e30f38d206d4491"
|
||||
MESSAGE_FORMAT = "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 altReprojErr;float64 ambiguity;"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "PnpResult":
|
||||
ret = PnpResult()
|
||||
|
||||
# field is shimmed!
|
||||
ret.best = packet.decodeTransform()
|
||||
|
||||
# field is shimmed!
|
||||
ret.alt = packet.decodeTransform()
|
||||
|
||||
# bestReprojErr is of intrinsic type float64
|
||||
ret.bestReprojErr = packet.decodeDouble()
|
||||
|
||||
# altReprojErr is of intrinsic type float64
|
||||
ret.altReprojErr = packet.decodeDouble()
|
||||
|
||||
# ambiguity is of intrinsic type float64
|
||||
ret.ambiguity = packet.decodeDouble()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
PnpResult.photonStruct = PnpResultSerde()
|
||||
46
photon-lib/py/photonlibpy/generated/TargetCornerSerde.py
Normal file
46
photon-lib/py/photonlibpy/generated/TargetCornerSerde.py
Normal file
@@ -0,0 +1,46 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from ..targeting import *
|
||||
|
||||
|
||||
class TargetCornerSerde:
|
||||
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "22b1ff7551d10215af6fb3672fe4eda8"
|
||||
MESSAGE_FORMAT = "float64 x;float64 y;"
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "TargetCorner":
|
||||
ret = TargetCorner()
|
||||
|
||||
# x is of intrinsic type float64
|
||||
ret.x = packet.decodeDouble()
|
||||
|
||||
# y is of intrinsic type float64
|
||||
ret.y = packet.decodeDouble()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
TargetCorner.photonStruct = TargetCornerSerde()
|
||||
9
photon-lib/py/photonlibpy/generated/__init__.py
Normal file
9
photon-lib/py/photonlibpy/generated/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# no one but us chickens
|
||||
|
||||
from .MultiTargetPNPResultSerde import MultiTargetPNPResultSerde # noqa
|
||||
from .PhotonPipelineMetadataSerde import PhotonPipelineMetadataSerde # noqa
|
||||
from .PhotonPipelineMetadataSerde import PhotonPipelineMetadataSerde # noqa
|
||||
from .PhotonPipelineResultSerde import PhotonPipelineResultSerde # noqa
|
||||
from .PhotonTrackedTargetSerde import PhotonTrackedTargetSerde # noqa
|
||||
from .PnpResultSerde import PnpResultSerde # noqa
|
||||
from .TargetCornerSerde import TargetCornerSerde # noqa
|
||||
@@ -1,49 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
from wpimath.geometry import Transform3d
|
||||
from photonlibpy.packet import Packet
|
||||
|
||||
|
||||
@dataclass
|
||||
class PNPResult:
|
||||
_NUM_BYTES_IN_FLOAT = 8
|
||||
PACK_SIZE_BYTES = 1 + (_NUM_BYTES_IN_FLOAT * 7 * 2) + (_NUM_BYTES_IN_FLOAT * 3)
|
||||
|
||||
isPresent: bool = False
|
||||
best: Transform3d = field(default_factory=Transform3d)
|
||||
alt: Transform3d = field(default_factory=Transform3d)
|
||||
ambiguity: float = 0.0
|
||||
bestReprojError: float = 0.0
|
||||
altReprojError: float = 0.0
|
||||
|
||||
def createFromPacket(self, packet: Packet) -> Packet:
|
||||
self.isPresent = packet.decodeBoolean()
|
||||
|
||||
if not self.isPresent:
|
||||
return packet
|
||||
|
||||
self.best = packet.decodeTransform()
|
||||
self.alt = packet.decodeTransform()
|
||||
self.bestReprojError = packet.decodeDouble()
|
||||
self.altReprojError = packet.decodeDouble()
|
||||
self.ambiguity = packet.decodeDouble()
|
||||
return packet
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiTargetPNPResult:
|
||||
_MAX_IDS = 32
|
||||
# pnpresult + MAX_IDS possible targets (arbitrary upper limit that should never be hit, ideally)
|
||||
_PACK_SIZE_BYTES = PNPResult.PACK_SIZE_BYTES + (1 * _MAX_IDS)
|
||||
|
||||
estimatedPose: PNPResult = field(default_factory=PNPResult)
|
||||
fiducialIDsUsed: list[int] = field(default_factory=list)
|
||||
|
||||
def createFromPacket(self, packet: Packet) -> Packet:
|
||||
self.estimatedPose = PNPResult()
|
||||
self.estimatedPose.createFromPacket(packet)
|
||||
self.fiducialIDsUsed = []
|
||||
for _ in range(MultiTargetPNPResult._MAX_IDS):
|
||||
fidId = packet.decode16()
|
||||
if fidId >= 0:
|
||||
self.fiducialIDsUsed.append(fidId)
|
||||
return packet
|
||||
@@ -1,4 +1,5 @@
|
||||
import struct
|
||||
from typing import Any, Optional, Type
|
||||
from wpimath.geometry import Transform3d, Translation3d, Rotation3d, Quaternion
|
||||
import wpilib
|
||||
|
||||
@@ -82,13 +83,13 @@ class Packet:
|
||||
|
||||
def decode16(self) -> int:
|
||||
"""
|
||||
* Returns a single decoded byte from the packet.
|
||||
* Returns a single decoded short from the packet.
|
||||
*
|
||||
* @return A decoded byte from the packet.
|
||||
* @return A decoded short from the packet.
|
||||
"""
|
||||
return self._decodeGeneric(">h", 2)
|
||||
|
||||
def decode32(self) -> int:
|
||||
def decodeInt(self) -> int:
|
||||
"""
|
||||
* Returns a decoded int (32 bytes) from the packet.
|
||||
*
|
||||
@@ -104,7 +105,7 @@ class Packet:
|
||||
"""
|
||||
return self._decodeGeneric(">f", 4)
|
||||
|
||||
def decodei64(self) -> int:
|
||||
def decodeLong(self) -> int:
|
||||
"""
|
||||
* Returns a decoded int64 from the packet.
|
||||
*
|
||||
@@ -131,14 +132,22 @@ class Packet:
|
||||
def decodeDoubleArray(self, length: int) -> list[float]:
|
||||
"""
|
||||
* Returns a decoded array of floats from the packet.
|
||||
*
|
||||
* @return A decoded array of floats from the packet.
|
||||
"""
|
||||
ret = []
|
||||
for _ in range(length):
|
||||
ret.append(self.decodeDouble())
|
||||
return ret
|
||||
|
||||
def decodeShortList(self) -> list[float]:
|
||||
"""
|
||||
* Returns a decoded array of shorts from the packet.
|
||||
"""
|
||||
length = self.decode8()
|
||||
ret = []
|
||||
for _ in range(length):
|
||||
ret.append(self.decode16())
|
||||
return ret
|
||||
|
||||
def decodeTransform(self) -> Transform3d:
|
||||
"""
|
||||
* Returns a decoded Transform3d
|
||||
@@ -157,3 +166,16 @@ class Packet:
|
||||
rotation = Rotation3d(Quaternion(w, x, y, z))
|
||||
|
||||
return Transform3d(translation, rotation)
|
||||
|
||||
def decodeList(self, serde: Type):
|
||||
retList = []
|
||||
arr_len = self.decode8()
|
||||
for _ in range(arr_len):
|
||||
retList.append(serde.unpack(self))
|
||||
return retList
|
||||
|
||||
def decodeOptional(self, serde: Type) -> Optional[Any]:
|
||||
if self.decodeBoolean():
|
||||
return serde.unpack(self)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -3,9 +3,12 @@ from typing import List
|
||||
import ntcore
|
||||
from wpilib import RobotController, Timer
|
||||
import wpilib
|
||||
from photonlibpy.packet import Packet
|
||||
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
||||
from photonlibpy.version import PHOTONVISION_VERSION, PHOTONLIB_VERSION # type: ignore[import-untyped]
|
||||
from .packet import Packet
|
||||
from .targeting.photonPipelineResult import PhotonPipelineResult
|
||||
from .version import PHOTONVISION_VERSION, PHOTONLIB_VERSION # type: ignore[import-untyped]
|
||||
|
||||
# magical import to make serde stuff work
|
||||
import photonlibpy.generated # noqa
|
||||
|
||||
|
||||
class VisionLEDMode(Enum):
|
||||
@@ -100,11 +103,9 @@ class PhotonCamera:
|
||||
else:
|
||||
newResult = PhotonPipelineResult()
|
||||
pkt = Packet(byteList)
|
||||
newResult.populateFromPacket(pkt)
|
||||
newResult = PhotonPipelineResult.photonStruct.unpack(pkt)
|
||||
# NT4 allows us to correct the timestamp based on when the message was sent
|
||||
newResult.setTimestampSeconds(
|
||||
timestamp / 1e6 - newResult.getLatencyMillis() / 1e3
|
||||
)
|
||||
newResult.ntReceiveTimestampMicros = timestamp / 1e6
|
||||
ret.append(newResult)
|
||||
|
||||
return ret
|
||||
@@ -113,18 +114,17 @@ class PhotonCamera:
|
||||
self._versionCheck()
|
||||
|
||||
now = RobotController.getFPGATime()
|
||||
retVal = PhotonPipelineResult()
|
||||
packetWithTimestamp = self._rawBytesEntry.getAtomic()
|
||||
byteList = packetWithTimestamp.value
|
||||
timestamp = packetWithTimestamp.time
|
||||
packetWithTimestamp.time
|
||||
|
||||
if len(byteList) < 1:
|
||||
return retVal
|
||||
return PhotonPipelineResult()
|
||||
else:
|
||||
pkt = Packet(byteList)
|
||||
retVal.populateFromPacket(pkt)
|
||||
retVal = PhotonPipelineResult.photonStruct.unpack(pkt)
|
||||
# We don't trust NT4 time, hack around
|
||||
retVal.ntRecieveTimestampMicros = now
|
||||
retVal.ntReceiveTimestampMicros = now
|
||||
return retVal
|
||||
|
||||
def getDriverMode(self) -> bool:
|
||||
@@ -233,6 +233,6 @@ class PhotonCamera:
|
||||
|
||||
wpilib.reportWarning(bfw)
|
||||
|
||||
errText = f"Photon version {PHOTONLIB_VERSION} does not match coprocessor version {versionString}. Please install photonlibpy version {PHOTONLIB_VERSION}."
|
||||
errText = f"Photon version {PHOTONLIB_VERSION} does not match coprocessor version {versionString}. Please install photonlibpy version {versionString}, or update your coprocessor to {PHOTONLIB_VERSION}."
|
||||
wpilib.reportError(errText, True)
|
||||
raise Exception(errText)
|
||||
|
||||
@@ -5,7 +5,7 @@ import wpilib
|
||||
from robotpy_apriltag import AprilTagFieldLayout
|
||||
from wpimath.geometry import Transform3d, Pose3d, Pose2d
|
||||
|
||||
from .photonPipelineResult import PhotonPipelineResult
|
||||
from .targeting.photonPipelineResult import PhotonPipelineResult
|
||||
from .photonCamera import PhotonCamera
|
||||
from .estimatedRobotPose import EstimatedRobotPose
|
||||
|
||||
|
||||
9
photon-lib/py/photonlibpy/targeting/TargetCorner.py
Normal file
9
photon-lib/py/photonlibpy/targeting/TargetCorner.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class TargetCorner:
|
||||
x: float = 0
|
||||
y: float = 9
|
||||
|
||||
photonStruct: "TargetCornerSerde" = None
|
||||
6
photon-lib/py/photonlibpy/targeting/__init__.py
Normal file
6
photon-lib/py/photonlibpy/targeting/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# no one but us chickens
|
||||
|
||||
from .TargetCorner import TargetCorner # noqa
|
||||
from .multiTargetPNPResult import MultiTargetPNPResult, PnpResult # noqa
|
||||
from .photonPipelineResult import PhotonPipelineMetadata, PhotonPipelineResult # noqa
|
||||
from .photonTrackedTarget import PhotonTrackedTarget # noqa
|
||||
34
photon-lib/py/photonlibpy/targeting/multiTargetPNPResult.py
Normal file
34
photon-lib/py/photonlibpy/targeting/multiTargetPNPResult.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from dataclasses import dataclass, field
|
||||
from wpimath.geometry import Transform3d
|
||||
from ..packet import Packet
|
||||
|
||||
|
||||
@dataclass
|
||||
class PnpResult:
|
||||
best: Transform3d = field(default_factory=Transform3d)
|
||||
alt: Transform3d = field(default_factory=Transform3d)
|
||||
ambiguity: float = 0.0
|
||||
bestReprojError: float = 0.0
|
||||
altReprojError: float = 0.0
|
||||
|
||||
photonStruct: "PNPResultSerde" = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiTargetPNPResult:
|
||||
_MAX_IDS = 32
|
||||
|
||||
estimatedPose: PnpResult = field(default_factory=PnpResult)
|
||||
fiducialIDsUsed: list[int] = field(default_factory=list)
|
||||
|
||||
def createFromPacket(self, packet: Packet) -> Packet:
|
||||
self.estimatedPose = PnpResult()
|
||||
self.estimatedPose.createFromPacket(packet)
|
||||
self.fiducialIDsUsed = []
|
||||
for _ in range(MultiTargetPNPResult._MAX_IDS):
|
||||
fidId = packet.decode16()
|
||||
if fidId >= 0:
|
||||
self.fiducialIDsUsed.append(fidId)
|
||||
return packet
|
||||
|
||||
photonStruct: "MultiTargetPNPResultSerde" = None
|
||||
@@ -1,12 +1,12 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult
|
||||
from photonlibpy.packet import Packet
|
||||
from photonlibpy.photonTrackedTarget import PhotonTrackedTarget
|
||||
from .multiTargetPNPResult import MultiTargetPNPResult
|
||||
from .photonTrackedTarget import PhotonTrackedTarget
|
||||
|
||||
|
||||
@dataclass
|
||||
class PhotonPipelineResult:
|
||||
class PhotonPipelineMetadata:
|
||||
# Image capture and NT publish timestamp, in microseconds and in the coprocessor timebase. As
|
||||
# reported by WPIUtilJNI::now.
|
||||
captureTimestampMicros: int = -1
|
||||
@@ -15,49 +15,44 @@ class PhotonPipelineResult:
|
||||
# Mirror of the heartbeat entry -- monotonically increasing
|
||||
sequenceID: int = -1
|
||||
|
||||
photonStruct: "PhotonPipelineMetadataSerde" = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PhotonPipelineResult:
|
||||
# Since we don't trust NT time sync, keep track of when we got this packet into robot code
|
||||
ntRecieveTimestampMicros: int = -1
|
||||
ntReceiveTimestampMicros: int = -1
|
||||
|
||||
targets: list[PhotonTrackedTarget] = field(default_factory=list)
|
||||
multiTagResult: MultiTargetPNPResult = field(default_factory=MultiTargetPNPResult)
|
||||
|
||||
def populateFromPacket(self, packet: Packet) -> Packet:
|
||||
self.targets = []
|
||||
|
||||
self.sequenceID = packet.decodei64()
|
||||
self.captureTimestampMicros = packet.decodei64()
|
||||
self.publishTimestampMicros = packet.decodei64()
|
||||
|
||||
targetCount = packet.decode8()
|
||||
|
||||
for _ in range(targetCount):
|
||||
target = PhotonTrackedTarget()
|
||||
target.createFromPacket(packet)
|
||||
self.targets.append(target)
|
||||
|
||||
self.multiTagResult = MultiTargetPNPResult()
|
||||
self.multiTagResult.createFromPacket(packet)
|
||||
|
||||
return packet
|
||||
metadata: PhotonPipelineMetadata = field(default_factory=PhotonPipelineMetadata)
|
||||
multiTagResult: Optional[MultiTargetPNPResult] = None
|
||||
|
||||
def getLatencyMillis(self) -> float:
|
||||
return (self.publishTimestampMicros - self.captureTimestampMicros) / 1e3
|
||||
return (
|
||||
self.metadata.publishTimestampMicros - self.metadata.captureTimestampMicros
|
||||
) / 1e3
|
||||
|
||||
def getTimestampSeconds(self) -> float:
|
||||
"""
|
||||
Returns the estimated time the frame was taken, in the recieved system's time base. This is
|
||||
calculated as (NT recieve time (robot base) - (publish timestamp, coproc timebase - capture
|
||||
Returns the estimated time the frame was taken, in the Received system's time base. This is
|
||||
calculated as (NT Receive time (robot base) - (publish timestamp, coproc timebase - capture
|
||||
timestamp, coproc timebase))
|
||||
"""
|
||||
# TODO - we don't trust NT4 to correctly latency-compensate ntRecieveTimestampMicros
|
||||
# TODO - we don't trust NT4 to correctly latency-compensate ntReceiveTimestampMicros
|
||||
return (
|
||||
self.ntRecieveTimestampMicros
|
||||
- (self.publishTimestampMicros - self.captureTimestampMicros)
|
||||
self.ntReceiveTimestampMicros
|
||||
- (
|
||||
self.metadata.publishTimestampMicros
|
||||
- self.metadata.captureTimestampMicros
|
||||
)
|
||||
) / 1e6
|
||||
|
||||
def getTargets(self) -> list[PhotonTrackedTarget]:
|
||||
return self.targets
|
||||
|
||||
def hasTargets(self) -> bool:
|
||||
return len(self.targets) > 0
|
||||
|
||||
def getBestTarget(self) -> PhotonTrackedTarget:
|
||||
"""
|
||||
Returns the best target in this pipeline result. If there are no targets, this method will
|
||||
@@ -67,5 +62,4 @@ class PhotonPipelineResult:
|
||||
return None
|
||||
return self.getTargets()[0]
|
||||
|
||||
def hasTargets(self) -> bool:
|
||||
return len(self.targets) > 0
|
||||
photonStruct: "PhotonPipelineResultSerde" = None
|
||||
@@ -1,20 +1,11 @@
|
||||
from dataclasses import dataclass, field
|
||||
from wpimath.geometry import Transform3d
|
||||
from photonlibpy.packet import Packet
|
||||
|
||||
|
||||
@dataclass
|
||||
class TargetCorner:
|
||||
x: float
|
||||
y: float
|
||||
from ..packet import Packet
|
||||
from .TargetCorner import TargetCorner
|
||||
|
||||
|
||||
@dataclass
|
||||
class PhotonTrackedTarget:
|
||||
_MAX_CORNERS = 8
|
||||
_NUM_BYTES_IN_FLOAT = 8
|
||||
_PACK_SIZE_BYTES = _NUM_BYTES_IN_FLOAT * (5 + 7 + 2 * 4 + 1 + 7 + 2 * _MAX_CORNERS)
|
||||
|
||||
yaw: float = 0.0
|
||||
pitch: float = 0.0
|
||||
area: float = 0.0
|
||||
@@ -64,22 +55,4 @@ class PhotonTrackedTarget:
|
||||
retList.append(TargetCorner(cx, cy))
|
||||
return retList
|
||||
|
||||
def createFromPacket(self, packet: Packet) -> Packet:
|
||||
self.yaw = packet.decodeDouble()
|
||||
self.pitch = packet.decodeDouble()
|
||||
self.area = packet.decodeDouble()
|
||||
self.skew = packet.decodeDouble()
|
||||
self.fiducialId = packet.decode32()
|
||||
|
||||
self.classId = packet.decode32()
|
||||
self.objDetectConf = packet.decodeFloat()
|
||||
|
||||
self.bestCameraToTarget = packet.decodeTransform()
|
||||
self.altCameraToTarget = packet.decodeTransform()
|
||||
|
||||
self.poseAmbiguity = packet.decodeDouble()
|
||||
|
||||
self.minAreaRectCorners = self._decodeTargetList(packet, 4) # always four
|
||||
numCorners = packet.decode8()
|
||||
self.detectedCorners = self._decodeTargetList(packet, numCorners)
|
||||
return packet
|
||||
photonStruct: "PhotonTrackedTargetSerde" = None
|
||||
Reference in New Issue
Block a user