diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 17d47cc80c..09c27756be 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -17,18 +17,22 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: 3.8 # CHose earliest that robotpy supports - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel - pip install robotpy + pip install setuptools wheel pytest + + - name: Run Unit Tests + working-directory: ./photon-lib/py + run: | + pytest - name: Build wheel working-directory: ./photon-lib/py @@ -37,7 +41,7 @@ jobs: python setup.py sdist bdist_wheel - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@master with: name: dist path: ./photon-lib/py/dist/ diff --git a/photon-lib/py/.gitignore b/photon-lib/py/.gitignore index 329986996d..63889afb81 100644 --- a/photon-lib/py/.gitignore +++ b/photon-lib/py/.gitignore @@ -1,3 +1,4 @@ photonlibpy.egg-info/ dist/ -build/ \ No newline at end of file +build/ +.eggs/ \ No newline at end of file diff --git a/photon-lib/py/photonlibpy/multiTargetPNPResult.py b/photon-lib/py/photonlibpy/multiTargetPNPResult.py index c8a259c4b6..2b6b0e90fe 100644 --- a/photon-lib/py/photonlibpy/multiTargetPNPResult.py +++ b/photon-lib/py/photonlibpy/multiTargetPNPResult.py @@ -1,18 +1,19 @@ +from dataclasses import dataclass 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) - def __init__(self): - self.isPresent = False - self.best = Transform3d() - self.alt = Transform3d() - self.ambiguity = 0.0 - self.bestReprojError = 0.0 - self.altReprojError = 0.0 + isPresent:bool = False + best:Transform3d = Transform3d() + alt:Transform3d = Transform3d() + ambiguity:float = 0.0 + bestReprojError:float = 0.0 + altReprojError:float = 0.0 def createFromPacket(self, packet:Packet) -> Packet: self.isPresent = packet.decodeBoolean() @@ -23,29 +24,23 @@ def createFromPacket(self, packet:Packet) -> Packet: self.ambiguity = packet.decodeDouble() return packet - def __str__(self) -> str: - return f"PNPResult {{isPresent={self.isPresent}, best={self.best}, bestReprojError={self.bestReprojError}, alt={self.alt}, altReprojError={self.altReprojError}, ambiguity={self.ambiguity}}}" - - +@dataclass class MultiTargetPNPResult: - MAX_IDS = 32 + _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) + _PACK_SIZE_BYTES = PNPResult.PACK_SIZE_BYTES + (1 * _MAX_IDS) - def __init__(self): - self.estimatedPose = PNPResult() - self.fiducialIDsUsed = [] + estimatedPose:PNPResult = PNPResult() + fiducialIDsUsed:list[int] = [] def createFromPacket(self, packet:Packet) -> Packet: self.estimatedPose = PNPResult() self.estimatedPose.createFromPacket(packet) self.fiducialIDsUsed = [] - for _ in range(MultiTargetPNPResult.MAX_IDS): + for _ in range(MultiTargetPNPResult._MAX_IDS): fidId = packet.decode16() if(fidId >= 0): self.fiducialIDsUsed.append(fidId) return packet - def __str__(self) -> str: - return f"MultiTargetPNPResult {{estimatedPose={self.estimatedPose},fiducialIDsUsed={self.fiducialIDsUsed}}}" diff --git a/photon-lib/py/photonlibpy/packet.py b/photon-lib/py/photonlibpy/packet.py index f5d893fccf..2321a94252 100644 --- a/photon-lib/py/photonlibpy/packet.py +++ b/photon-lib/py/photonlibpy/packet.py @@ -4,20 +4,21 @@ class Packet: - """ - * Constructs an empty packet. - * - * @param self.size The self.size of the packet buffer. - """ + def __init__(self, data:list[int]): + """ + * Constructs an empty packet. + * + * @param self.size The self.size of the packet buffer. + """ self.packetData = data self.size = len(data) self.readPos = 0 self.outOfBytes = False - """ Clears the packet and resets the read and write positions.""" def clear(self): + """ Clears the packet and resets the read and write positions.""" self.packetData = [0]*self.size self.readPos = 0 self.outOfBytes = False @@ -45,21 +46,20 @@ def _getNextByte(self) -> int: return retVal - """ - * Returns the packet data. - * - * @return The packet data. - """ - def getData(self) -> list[int]: + def getData(self) -> list[int]: + """ + * Returns the packet data. + * + * @return The packet data. + """ return self.packetData - - """ - * Sets the packet data. - * - * @param data The packet data. - """ def setData(self, data:list[int]): + """ + * Sets the packet data. + * + * @param data The packet data. + """ self.clear() self.packetData = data self.size = len(self.packetData) @@ -76,55 +76,64 @@ def _decodeGeneric(self, unpackFormat, numBytes): return value - - """ - * Returns a single decoded byte from the packet. - * - * @return A decoded byte from the packet. - """ def decode8(self) -> int: + """ + * Returns a single decoded byte from the packet. + * + * @return A decoded byte from the packet. + """ return self._decodeGeneric(">b", 1) - """ - * Returns a single decoded byte from the packet. - * - * @return A decoded byte from the packet. - """ def decode16(self) -> int: + """ + * Returns a single decoded byte from the packet. + * + * @return A decoded byte from the packet. + """ return self._decodeGeneric(">h", 2) - - """ - * Returns a decoded int (32 bytes) from the packet. - * - * @return A decoded int from the packet. - """ def decode32(self) -> int: + """ + * Returns a decoded int (32 bytes) from the packet. + * + * @return A decoded int from the packet. + """ return self._decodeGeneric(">l", 4) - """ - * Returns a decoded double from the packet. - * - * @return A decoded double from the packet. - """ def decodeDouble(self) -> float: + """ + * Returns a decoded double from the packet. + * + * @return A decoded double from the packet. + """ return self._decodeGeneric(">d", 8) - """ - * Returns a decoded boolean from the packet. - * - * @return A decoded boolean from the packet. - """ + def decodeBoolean(self) -> bool: + """ + * Returns a decoded boolean from the packet. + * + * @return A decoded boolean from the packet. + """ return (self.decode8() == 1) 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 decodeTransform(self) -> Transform3d: + """ + * Returns a decoded Transform3d + * + * @return A decoded Tansform3d from the packet. + """ x = self.decodeDouble() y = self.decodeDouble() z = self.decodeDouble() diff --git a/photon-lib/py/photonlibpy/photonPipelineResult.py b/photon-lib/py/photonlibpy/photonPipelineResult.py index 6931bf1dc6..d9bdabace3 100644 --- a/photon-lib/py/photonlibpy/photonPipelineResult.py +++ b/photon-lib/py/photonlibpy/photonPipelineResult.py @@ -1,13 +1,15 @@ +from dataclasses import dataclass + from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult from photonlibpy.packet import Packet from photonlibpy.photonTrackedTarget import PhotonTrackedTarget +@dataclass class PhotonPipelineResult: - def __init__(self): - self.latencyMillis = -0.0 - self.timestampSec = -1.0 - self.targets:list[PhotonTrackedTarget] = [] - self.multiTagResult = MultiTargetPNPResult() + latencyMillis:float = -1.0 + timestampSec:float = -1.0 + targets:list[PhotonTrackedTarget] = [] + multiTagResult:MultiTargetPNPResult = MultiTargetPNPResult() def populateFromPacket(self, packet:Packet) -> Packet: self.targets = [] diff --git a/photon-lib/py/photonlibpy/photonTrackedTarget.py b/photon-lib/py/photonlibpy/photonTrackedTarget.py index 54c4edce44..d98359147a 100644 --- a/photon-lib/py/photonlibpy/photonTrackedTarget.py +++ b/photon-lib/py/photonlibpy/photonTrackedTarget.py @@ -1,33 +1,31 @@ +from dataclasses import dataclass from wpimath.geometry import Transform3d from photonlibpy.packet import Packet +@dataclass class TargetCorner: - def __init__(self, x:float, y:float): - self.x = x - self.y = y + x:float + y:float +@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) - def __init__(self, yaw:float=0, pitch:float=0, area:float=0, skew:float=0, - id:int=0, pose:Transform3d=Transform3d(), altPose: Transform3d=Transform3d(), - ambiguity:float=0, - minAreaRectCorners: list[TargetCorner]|None = None, - detectedCorners: list[TargetCorner]|None = None): - self.yaw = yaw - self.pitch = pitch - self.area = area - self.skew = skew - self.fiducialId = id - self.bestCameraToTarget = pose - self.altCameraToTarget = altPose - self.minAreaRectCorners = minAreaRectCorners - self.detectedCorners = detectedCorners - self.poseAmbiguity = ambiguity + + yaw:float = 0.0 + pitch:float = 0.0 + area:float = 0.0 + skew:float = 0.0 + fiducialId:int = -1 + bestCameraToTarget:Transform3d = Transform3d() + altCameraToTarget:Transform3d = Transform3d() + minAreaRectCorners:list[TargetCorner]|None = None + detectedCorners:list[TargetCorner]|None = None + poseAmbiguity:float = 0.0 def getYaw(self) -> float: return self.yaw @@ -83,6 +81,3 @@ def createFromPacket(self, packet:Packet) -> Packet: numCorners = packet.decode8() self.detectedCorners = self._decodeTargetList(packet, numCorners) return packet - - def __str__(self) -> str: - return f"PhotonTrackedTarget{{yaw={self.yaw},pitch={self.pitch},area={self.area},skew={self.skew},fiducialId={self.fiducialId},bestCameraToTarget={self.bestCameraToTarget}}}" diff --git a/photon-server/src/main/resources/web/index.html b/photon-server/src/main/resources/web/index.html index 988f55e6a3..e01924c78e 100644 --- a/photon-server/src/main/resources/web/index.html +++ b/photon-server/src/main/resources/web/index.html @@ -1 +1 @@ -

UI has not been copied!

+PhotonVision Client
\ No newline at end of file