photonlibpy 2025.0.0b8__py3-none-any.whl → 2026.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- photonlibpy/__init__.py +34 -20
- photonlibpy/estimatedRobotPose.py +0 -7
- photonlibpy/generated/MultiTargetPNPResultSerde.py +23 -16
- photonlibpy/generated/PhotonPipelineMetadataSerde.py +23 -16
- photonlibpy/generated/PhotonPipelineResultSerde.py +23 -16
- photonlibpy/generated/PhotonTrackedTargetSerde.py +23 -16
- photonlibpy/generated/PnpResultSerde.py +23 -16
- photonlibpy/generated/TargetCornerSerde.py +23 -16
- photonlibpy/networktables/NTTopicSet.py +7 -0
- photonlibpy/photonCamera.py +36 -0
- photonlibpy/photonPoseEstimator.py +108 -176
- photonlibpy/py.typed +0 -1
- photonlibpy/simulation/__init__.py +8 -0
- photonlibpy/simulation/photonCameraSim.py +17 -10
- photonlibpy/simulation/simCameraProperties.py +1 -1
- photonlibpy/targeting/__init__.py +12 -5
- photonlibpy/targeting/photonPipelineResult.py +4 -7
- photonlibpy/timesync/__init__.py +1 -0
- photonlibpy/timesync/timeSyncServer.py +94 -0
- photonlibpy/version.py +2 -2
- photonlibpy-2026.1.1.dist-info/METADATA +24 -0
- photonlibpy-2026.1.1.dist-info/RECORD +39 -0
- {photonlibpy-2025.0.0b8.dist-info → photonlibpy-2026.1.1.dist-info}/WHEEL +1 -1
- photonlibpy-2025.0.0b8.dist-info/METADATA +0 -16
- photonlibpy-2025.0.0b8.dist-info/RECORD +0 -37
- {photonlibpy-2025.0.0b8.dist-info → photonlibpy-2026.1.1.dist-info}/top_level.txt +0 -0
photonlibpy/__init__.py
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
|
-
from .estimatedRobotPose import EstimatedRobotPose
|
|
19
|
-
from .packet import Packet
|
|
20
|
-
from .photonCamera import PhotonCamera
|
|
21
|
-
from .photonPoseEstimator import PhotonPoseEstimator
|
|
25
|
+
from .estimatedRobotPose import EstimatedRobotPose
|
|
26
|
+
from .packet import Packet
|
|
27
|
+
from .photonCamera import PhotonCamera
|
|
28
|
+
from .photonPoseEstimator import PhotonPoseEstimator
|
|
29
|
+
|
|
30
|
+
__all__ = (
|
|
31
|
+
"EstimatedRobotPose",
|
|
32
|
+
"Packet",
|
|
33
|
+
"PhotonCamera",
|
|
34
|
+
"PhotonPoseEstimator",
|
|
35
|
+
)
|
|
@@ -16,15 +16,11 @@
|
|
|
16
16
|
###############################################################################
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
|
-
from typing import TYPE_CHECKING
|
|
20
19
|
|
|
21
20
|
from wpimath.geometry import Pose3d
|
|
22
21
|
|
|
23
22
|
from .targeting.photonTrackedTarget import PhotonTrackedTarget
|
|
24
23
|
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from .photonPoseEstimator import PoseStrategy
|
|
27
|
-
|
|
28
24
|
|
|
29
25
|
@dataclass
|
|
30
26
|
class EstimatedRobotPose:
|
|
@@ -38,6 +34,3 @@ class EstimatedRobotPose:
|
|
|
38
34
|
|
|
39
35
|
targetsUsed: list[PhotonTrackedTarget]
|
|
40
36
|
"""A list of the targets used to compute this pose"""
|
|
41
|
-
|
|
42
|
-
strategy: "PoseStrategy"
|
|
43
|
-
"""The strategy actually used to produce this pose"""
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
#
|
|
2
|
+
# MIT License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) PhotonVision
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
# copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
17
24
|
|
|
18
25
|
###############################################################################
|
|
19
26
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
|
@@ -47,6 +47,13 @@ class NTTopicSet:
|
|
|
47
47
|
|
|
48
48
|
self.driverModeSubscriber.getTopic().publish().setDefault(False)
|
|
49
49
|
|
|
50
|
+
self.fpsLimitPublisher = self.subTable.getIntegerTopic("fpsLimit").publish()
|
|
51
|
+
self.fpsLimitSubscriber = self.subTable.getIntegerTopic(
|
|
52
|
+
"fpsLimitRequest"
|
|
53
|
+
).subscribe(-1)
|
|
54
|
+
|
|
55
|
+
self.fpsLimitSubscriber.getTopic().publish().setDefault(-1)
|
|
56
|
+
|
|
50
57
|
self.latencyMillisEntry = self.subTable.getDoubleTopic(
|
|
51
58
|
"latencyMillis"
|
|
52
59
|
).publish()
|
photonlibpy/photonCamera.py
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
from enum import Enum
|
|
19
19
|
from typing import List
|
|
20
20
|
|
|
21
|
+
import hal
|
|
21
22
|
import ntcore
|
|
22
23
|
|
|
23
24
|
# magical import to make serde stuff work
|
|
@@ -27,6 +28,7 @@ from wpilib import RobotController, Timer
|
|
|
27
28
|
|
|
28
29
|
from .packet import Packet
|
|
29
30
|
from .targeting.photonPipelineResult import PhotonPipelineResult
|
|
31
|
+
from .timesync.timeSyncServer import inst
|
|
30
32
|
from .version import PHOTONLIB_VERSION # type: ignore[import-untyped]
|
|
31
33
|
|
|
32
34
|
|
|
@@ -47,6 +49,8 @@ def setVersionCheckEnabled(enabled: bool):
|
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
class PhotonCamera:
|
|
52
|
+
instance_count = 1
|
|
53
|
+
|
|
50
54
|
def __init__(self, cameraName: str):
|
|
51
55
|
"""Constructs a PhotonCamera from the name of the camera.
|
|
52
56
|
|
|
@@ -70,6 +74,12 @@ class PhotonCamera:
|
|
|
70
74
|
self._driverModeSubscriber = self._cameraTable.getBooleanTopic(
|
|
71
75
|
"driverMode"
|
|
72
76
|
).subscribe(False)
|
|
77
|
+
self._fpsLimitPublisher = self._cameraTable.getIntegerTopic(
|
|
78
|
+
"fpsLimitRequest"
|
|
79
|
+
).publish()
|
|
80
|
+
self._fpsLimitSubscriber = self._cameraTable.getIntegerTopic(
|
|
81
|
+
"fpsLimit"
|
|
82
|
+
).subscribe(-1)
|
|
73
83
|
self._inputSaveImgEntry = self._cameraTable.getIntegerTopic(
|
|
74
84
|
"inputSaveImgCmd"
|
|
75
85
|
).getEntry(0)
|
|
@@ -104,6 +114,16 @@ class PhotonCamera:
|
|
|
104
114
|
self._prevHeartbeat = 0
|
|
105
115
|
self._prevHeartbeatChangeTime = Timer.getFPGATimestamp()
|
|
106
116
|
|
|
117
|
+
# Start the time sync server
|
|
118
|
+
inst.start()
|
|
119
|
+
|
|
120
|
+
# Usage reporting
|
|
121
|
+
hal.report(
|
|
122
|
+
hal.tResourceType.kResourceType_PhotonCamera.value,
|
|
123
|
+
PhotonCamera.instance_count,
|
|
124
|
+
)
|
|
125
|
+
PhotonCamera.instance_count += 1
|
|
126
|
+
|
|
107
127
|
def getAllUnreadResults(self) -> List[PhotonPipelineResult]:
|
|
108
128
|
"""
|
|
109
129
|
The list of pipeline results sent by PhotonVision since the last call to getAllUnreadResults().
|
|
@@ -176,6 +196,22 @@ class PhotonCamera:
|
|
|
176
196
|
|
|
177
197
|
self._driverModePublisher.set(driverMode)
|
|
178
198
|
|
|
199
|
+
def getFPSLimit(self) -> int:
|
|
200
|
+
"""Returns the current FPS limit set on the camera.
|
|
201
|
+
|
|
202
|
+
:returns: The current FPS limit.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
return self._fpsLimitSubscriber.get()
|
|
206
|
+
|
|
207
|
+
def setFPSLimit(self, fpsLimit: int) -> None:
|
|
208
|
+
"""Sets the FPS limit on the camera.
|
|
209
|
+
|
|
210
|
+
:param fpsLimit: The FPS limit to set. Set to -1 for unlimited FPS.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
self._fpsLimitPublisher.set(fpsLimit)
|
|
214
|
+
|
|
179
215
|
def takeInputSnapshot(self) -> None:
|
|
180
216
|
"""Request the camera to save a new image file from the input camera stream with overlays. Images
|
|
181
217
|
take up space in the filesystem of the PhotonCamera. Calling it frequently will fill up disk
|
|
@@ -15,52 +15,30 @@
|
|
|
15
15
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
###############################################################################
|
|
17
17
|
|
|
18
|
-
import enum
|
|
19
18
|
from typing import Optional
|
|
20
19
|
|
|
20
|
+
import hal
|
|
21
21
|
import wpilib
|
|
22
|
+
import wpimath.units
|
|
22
23
|
from robotpy_apriltag import AprilTagFieldLayout
|
|
23
|
-
from wpimath.geometry import
|
|
24
|
+
from wpimath.geometry import (
|
|
25
|
+
Pose2d,
|
|
26
|
+
Pose3d,
|
|
27
|
+
Rotation2d,
|
|
28
|
+
Rotation3d,
|
|
29
|
+
Transform3d,
|
|
30
|
+
Translation2d,
|
|
31
|
+
Translation3d,
|
|
32
|
+
)
|
|
33
|
+
from wpimath.interpolation import TimeInterpolatableRotation2dBuffer
|
|
24
34
|
|
|
25
35
|
from .estimatedRobotPose import EstimatedRobotPose
|
|
26
|
-
from .photonCamera import PhotonCamera
|
|
27
36
|
from .targeting.photonPipelineResult import PhotonPipelineResult
|
|
28
37
|
|
|
29
38
|
|
|
30
|
-
class PoseStrategy(enum.Enum):
|
|
31
|
-
"""
|
|
32
|
-
Position estimation strategies that can be used by the PhotonPoseEstimator class.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
LOWEST_AMBIGUITY = enum.auto()
|
|
36
|
-
"""Choose the Pose with the lowest ambiguity."""
|
|
37
|
-
|
|
38
|
-
CLOSEST_TO_CAMERA_HEIGHT = enum.auto()
|
|
39
|
-
"""Choose the Pose which is closest to the camera height."""
|
|
40
|
-
|
|
41
|
-
CLOSEST_TO_REFERENCE_POSE = enum.auto()
|
|
42
|
-
"""Choose the Pose which is closest to a set Reference position."""
|
|
43
|
-
|
|
44
|
-
CLOSEST_TO_LAST_POSE = enum.auto()
|
|
45
|
-
"""Choose the Pose which is closest to the last pose calculated."""
|
|
46
|
-
|
|
47
|
-
AVERAGE_BEST_TARGETS = enum.auto()
|
|
48
|
-
"""Return the average of the best target poses using ambiguity as weight."""
|
|
49
|
-
|
|
50
|
-
MULTI_TAG_PNP_ON_COPROCESSOR = enum.auto()
|
|
51
|
-
"""
|
|
52
|
-
Use all visible tags to compute a single pose estimate on coprocessor.
|
|
53
|
-
This option needs to be enabled on the PhotonVision web UI as well.
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
MULTI_TAG_PNP_ON_RIO = enum.auto()
|
|
57
|
-
"""
|
|
58
|
-
Use all visible tags to compute a single pose estimate.
|
|
59
|
-
This runs on the RoboRIO, and can take a lot of time.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
39
|
class PhotonPoseEstimator:
|
|
40
|
+
instance_count = 1
|
|
41
|
+
|
|
64
42
|
"""
|
|
65
43
|
The PhotonPoseEstimator class filters or combines readings from all the AprilTags visible at a
|
|
66
44
|
given timestamp on the field to produce a single robot in field pose, using the strategy set
|
|
@@ -70,8 +48,6 @@ class PhotonPoseEstimator:
|
|
|
70
48
|
def __init__(
|
|
71
49
|
self,
|
|
72
50
|
fieldTags: AprilTagFieldLayout,
|
|
73
|
-
strategy: PoseStrategy,
|
|
74
|
-
camera: PhotonCamera,
|
|
75
51
|
robotToCamera: Transform3d,
|
|
76
52
|
):
|
|
77
53
|
"""Create a new PhotonPoseEstimator.
|
|
@@ -80,23 +56,21 @@ class PhotonPoseEstimator:
|
|
|
80
56
|
with respect to the FIRST field using the Field Coordinate System.
|
|
81
57
|
Note that setting the origin of this layout object will affect the
|
|
82
58
|
results from this class.
|
|
83
|
-
:param strategy: The strategy it should use to determine the best pose.
|
|
84
|
-
:param camera: PhotonCamera
|
|
85
59
|
:param robotToCamera: Transform3d from the center of the robot to the camera mount position (i.e.,
|
|
86
60
|
robot ➔ camera) in the Robot Coordinate System.
|
|
87
61
|
"""
|
|
88
62
|
self._fieldTags = fieldTags
|
|
89
|
-
self._primaryStrategy = strategy
|
|
90
|
-
self._camera = camera
|
|
91
63
|
self.robotToCamera = robotToCamera
|
|
92
64
|
|
|
93
|
-
self._multiTagFallbackStrategy = PoseStrategy.LOWEST_AMBIGUITY
|
|
94
65
|
self._reportedErrors: set[int] = set()
|
|
95
|
-
self.
|
|
96
|
-
self._lastPose: Optional[Pose3d] = None
|
|
97
|
-
self._referencePose: Optional[Pose3d] = None
|
|
66
|
+
self._headingBuffer = TimeInterpolatableRotation2dBuffer(1)
|
|
98
67
|
|
|
99
|
-
#
|
|
68
|
+
# Usage reporting
|
|
69
|
+
hal.report(
|
|
70
|
+
hal.tResourceType.kResourceType_PhotonPoseEstimator.value,
|
|
71
|
+
PhotonPoseEstimator.instance_count,
|
|
72
|
+
)
|
|
73
|
+
PhotonPoseEstimator.instance_count += 1
|
|
100
74
|
|
|
101
75
|
@property
|
|
102
76
|
def fieldTags(self) -> AprilTagFieldLayout:
|
|
@@ -116,160 +90,120 @@ class PhotonPoseEstimator:
|
|
|
116
90
|
|
|
117
91
|
:param fieldTags: the AprilTagFieldLayout
|
|
118
92
|
"""
|
|
119
|
-
self._checkUpdate(self._fieldTags, fieldTags)
|
|
120
93
|
self._fieldTags = fieldTags
|
|
121
94
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
:returns: the strategy
|
|
95
|
+
def addHeadingData(
|
|
96
|
+
self, timestampSeconds: wpimath.units.seconds, heading: Rotation2d | Rotation3d
|
|
97
|
+
) -> None:
|
|
127
98
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@primaryStrategy.setter
|
|
131
|
-
def primaryStrategy(self, strategy: PoseStrategy):
|
|
132
|
-
"""Set the Position Estimation Strategy used by the Position Estimator.
|
|
99
|
+
Add robot heading data to buffer. Must be called periodically for the **PNP_DISTANCE_TRIG_SOLVE** strategy.
|
|
133
100
|
|
|
134
|
-
:param
|
|
101
|
+
:param timestampSeconds :timestamp of the robot heading data
|
|
102
|
+
:param heading: field-relative robot heading at given timestamp
|
|
135
103
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
104
|
+
if isinstance(heading, Rotation3d):
|
|
105
|
+
heading = heading.toRotation2d()
|
|
106
|
+
self._headingBuffer.addSample(timestampSeconds, heading)
|
|
138
107
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@multiTagFallbackStrategy.setter
|
|
144
|
-
def multiTagFallbackStrategy(self, strategy: PoseStrategy):
|
|
145
|
-
"""Set the Position Estimation Strategy used in multi-tag mode when only one tag can be seen. Must
|
|
146
|
-
NOT be MULTI_TAG_PNP
|
|
147
|
-
|
|
148
|
-
:param strategy: the strategy to set
|
|
108
|
+
def resetHeadingData(
|
|
109
|
+
self, timestampSeconds: wpimath.units.seconds, heading: Rotation2d | Rotation3d
|
|
110
|
+
) -> None:
|
|
149
111
|
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
strategy is PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR
|
|
153
|
-
or strategy is PoseStrategy.MULTI_TAG_PNP_ON_RIO
|
|
154
|
-
):
|
|
155
|
-
wpilib.reportWarning(
|
|
156
|
-
"Fallback cannot be set to MULTI_TAG_PNP! Setting to lowest ambiguity",
|
|
157
|
-
False,
|
|
158
|
-
)
|
|
159
|
-
strategy = PoseStrategy.LOWEST_AMBIGUITY
|
|
160
|
-
self._multiTagFallbackStrategy = strategy
|
|
112
|
+
Clears all heading data in the buffer, and adds a new seed. Useful for preventing estimates
|
|
113
|
+
from utilizing heading data provided prior to a pose or rotation reset.
|
|
161
114
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"""Return the reference position that is being used by the estimator.
|
|
165
|
-
|
|
166
|
-
:returns: the referencePose
|
|
115
|
+
:param timestampSeconds: timestamp of the robot heading data
|
|
116
|
+
:param heading: field-relative robot heading at given timestamp
|
|
167
117
|
"""
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@referencePose.setter
|
|
171
|
-
def referencePose(self, referencePose: Pose3d | Pose2d):
|
|
172
|
-
"""Update the stored reference pose for use when using the **CLOSEST_TO_REFERENCE_POSE**
|
|
173
|
-
strategy.
|
|
118
|
+
self._headingBuffer.clear()
|
|
119
|
+
self.addHeadingData(timestampSeconds, heading)
|
|
174
120
|
|
|
175
|
-
|
|
121
|
+
def _shouldEstimate(self, cameraResult: PhotonPipelineResult) -> bool:
|
|
176
122
|
"""
|
|
177
|
-
|
|
178
|
-
referencePose = Pose3d(referencePose)
|
|
179
|
-
self._checkUpdate(self._referencePose, referencePose)
|
|
180
|
-
self._referencePose = referencePose
|
|
123
|
+
:param cameraResult: A pipeline result from the camera.
|
|
181
124
|
|
|
182
|
-
|
|
183
|
-
def lastPose(self) -> Optional[Pose3d]:
|
|
184
|
-
return self._lastPose
|
|
185
|
-
|
|
186
|
-
@lastPose.setter
|
|
187
|
-
def lastPose(self, lastPose: Pose3d | Pose2d):
|
|
188
|
-
"""Update the stored last pose. Useful for setting the initial estimate when using the
|
|
189
|
-
**CLOSEST_TO_LAST_POSE** strategy.
|
|
190
|
-
|
|
191
|
-
:param lastPose: the lastPose to set
|
|
125
|
+
:returns: Whether or not estimation should be done.
|
|
192
126
|
"""
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
self._checkUpdate(self._lastPose, lastPose)
|
|
196
|
-
self._lastPose = lastPose
|
|
197
|
-
|
|
198
|
-
def _invalidatePoseCache(self) -> None:
|
|
199
|
-
self._poseCacheTimestampSeconds = -1.0
|
|
127
|
+
if cameraResult.getTimestampSeconds() < 0:
|
|
128
|
+
return False
|
|
200
129
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
self._invalidatePoseCache()
|
|
130
|
+
# If no targets seen, trivial case -- can't do estimation
|
|
131
|
+
return len(cameraResult.targets) > 0
|
|
204
132
|
|
|
205
|
-
def
|
|
206
|
-
self,
|
|
133
|
+
def estimatePnpDistanceTrigSolvePose(
|
|
134
|
+
self, result: PhotonPipelineResult
|
|
207
135
|
) -> Optional[EstimatedRobotPose]:
|
|
208
136
|
"""
|
|
209
|
-
Updates the estimated position of the robot. Returns empty if:
|
|
210
137
|
|
|
211
|
-
|
|
212
|
-
|
|
138
|
+
Return the estimated position of the robot by using distance data from best visible tag to
|
|
139
|
+
compute a Pose. This runs on the RoboRIO in order to access the robot's yaw heading, and MUST
|
|
140
|
+
have addHeadingData called every frame so heading data is up-to-date.
|
|
213
141
|
|
|
214
|
-
|
|
142
|
+
Yields a Pose2d in estimatedRobotPose (0 for z, roll, pitch)
|
|
215
143
|
|
|
216
|
-
|
|
144
|
+
https://www.chiefdelphi.com/t/frc-6328-mechanical-advantage-2025-build-thread/477314/98
|
|
217
145
|
|
|
218
|
-
:returns: an :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used to
|
|
219
|
-
create the estimate.
|
|
220
|
-
"""
|
|
221
|
-
if not cameraResult:
|
|
222
|
-
if not self._camera:
|
|
223
|
-
wpilib.reportError("[PhotonPoseEstimator] Missing camera!", False)
|
|
224
|
-
return None
|
|
225
|
-
cameraResult = self._camera.getLatestResult()
|
|
226
146
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# timestamp, return an
|
|
232
|
-
# empty result
|
|
147
|
+
:param result: A pipeline result from the camera.
|
|
148
|
+
:returns: An :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used
|
|
149
|
+
to create the estimate.
|
|
150
|
+
"""
|
|
233
151
|
if (
|
|
234
|
-
self.
|
|
235
|
-
|
|
236
|
-
self._poseCacheTimestampSeconds - cameraResult.getTimestampSeconds()
|
|
237
|
-
)
|
|
238
|
-
< 1e-6
|
|
152
|
+
not self._shouldEstimate(result)
|
|
153
|
+
or (bestTarget := result.getBestTarget()) is None
|
|
239
154
|
):
|
|
240
155
|
return None
|
|
241
156
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# If no targets seen, trivial case -- return empty result
|
|
246
|
-
if not cameraResult.targets:
|
|
157
|
+
if (
|
|
158
|
+
headingSample := self._headingBuffer.sample(result.getTimestampSeconds())
|
|
159
|
+
) is None:
|
|
247
160
|
return None
|
|
248
161
|
|
|
249
|
-
|
|
162
|
+
if (tagPose := self._fieldTags.getTagPose(bestTarget.fiducialId)) is None:
|
|
163
|
+
return None
|
|
250
164
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
wpilib.reportError(
|
|
260
|
-
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", False
|
|
165
|
+
camToTagTranslation = (
|
|
166
|
+
Translation3d(
|
|
167
|
+
bestTarget.getBestCameraToTarget().translation().norm(),
|
|
168
|
+
Rotation3d(
|
|
169
|
+
0,
|
|
170
|
+
-wpimath.units.degreesToRadians(bestTarget.getPitch()),
|
|
171
|
+
-wpimath.units.degreesToRadians(bestTarget.getYaw()),
|
|
172
|
+
),
|
|
261
173
|
)
|
|
262
|
-
|
|
174
|
+
.rotateBy(self.robotToCamera.rotation())
|
|
175
|
+
.toTranslation2d()
|
|
176
|
+
.rotateBy(headingSample)
|
|
177
|
+
)
|
|
263
178
|
|
|
264
|
-
|
|
265
|
-
|
|
179
|
+
fieldToCameraTranslation = (
|
|
180
|
+
tagPose.toPose2d().translation() - camToTagTranslation
|
|
181
|
+
)
|
|
182
|
+
camToRobotTranslation: Translation2d = -(
|
|
183
|
+
self.robotToCamera.translation().toTranslation2d()
|
|
184
|
+
)
|
|
185
|
+
camToRobotTranslation = camToRobotTranslation.rotateBy(headingSample)
|
|
186
|
+
robotPose = Pose2d(
|
|
187
|
+
fieldToCameraTranslation + camToRobotTranslation, headingSample
|
|
188
|
+
)
|
|
266
189
|
|
|
267
|
-
return
|
|
190
|
+
return EstimatedRobotPose(
|
|
191
|
+
Pose3d(robotPose), result.getTimestampSeconds(), result.getTargets()
|
|
192
|
+
)
|
|
268
193
|
|
|
269
|
-
def
|
|
194
|
+
def estimateCoprocMultiTagPose(
|
|
270
195
|
self, result: PhotonPipelineResult
|
|
271
196
|
) -> Optional[EstimatedRobotPose]:
|
|
272
|
-
|
|
197
|
+
"""
|
|
198
|
+
Return the estimated position of the robot by using all visible tags to compute a single
|
|
199
|
+
pose estimate on coprocessor. This option needs to be enabled on the PhotonVision web UI as
|
|
200
|
+
well.
|
|
201
|
+
|
|
202
|
+
:param result: A pipeline result from the camera.
|
|
203
|
+
:returns: An :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used
|
|
204
|
+
to create the estimate.
|
|
205
|
+
"""
|
|
206
|
+
if result.multitagResult is not None and self._shouldEstimate(result):
|
|
273
207
|
best_tf = result.multitagResult.estimatedPose.best
|
|
274
208
|
best = (
|
|
275
209
|
Pose3d()
|
|
@@ -281,23 +215,22 @@ class PhotonPoseEstimator:
|
|
|
281
215
|
best,
|
|
282
216
|
result.getTimestampSeconds(),
|
|
283
217
|
result.targets,
|
|
284
|
-
PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
|
|
285
218
|
)
|
|
286
219
|
else:
|
|
287
|
-
return
|
|
220
|
+
return None
|
|
288
221
|
|
|
289
|
-
def
|
|
222
|
+
def estimateLowestAmbiguityPose(
|
|
290
223
|
self, result: PhotonPipelineResult
|
|
291
224
|
) -> Optional[EstimatedRobotPose]:
|
|
292
225
|
"""
|
|
293
|
-
Return the estimated position of the robot with the lowest position ambiguity from a
|
|
294
|
-
pipeline results.
|
|
295
|
-
|
|
296
|
-
:param result: pipeline result
|
|
226
|
+
Return the estimated position of the robot with the lowest position ambiguity from a pipeline results.
|
|
297
227
|
|
|
298
|
-
:
|
|
299
|
-
|
|
228
|
+
:param result: A pipeline result from the camera.
|
|
229
|
+
:returns: An :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used
|
|
230
|
+
to create the estimate.
|
|
300
231
|
"""
|
|
232
|
+
if not self._shouldEstimate(result):
|
|
233
|
+
return None
|
|
301
234
|
lowestAmbiguityTarget = None
|
|
302
235
|
|
|
303
236
|
lowestAmbiguityScore = 10.0
|
|
@@ -328,7 +261,6 @@ class PhotonPoseEstimator:
|
|
|
328
261
|
).transformBy(self.robotToCamera.inverse()),
|
|
329
262
|
result.getTimestampSeconds(),
|
|
330
263
|
result.targets,
|
|
331
|
-
PoseStrategy.LOWEST_AMBIGUITY,
|
|
332
264
|
)
|
|
333
265
|
|
|
334
266
|
def _reportFiducialPoseError(self, fiducialId: int) -> None:
|
photonlibpy/py.typed
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -3,3 +3,11 @@ from .simCameraProperties import SimCameraProperties
|
|
|
3
3
|
from .videoSimUtil import VideoSimUtil
|
|
4
4
|
from .visionSystemSim import VisionSystemSim
|
|
5
5
|
from .visionTargetSim import VisionTargetSim
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"PhotonCameraSim",
|
|
9
|
+
"SimCameraProperties",
|
|
10
|
+
"VideoSimUtil",
|
|
11
|
+
"VisionSystemSim",
|
|
12
|
+
"VisionTargetSim",
|
|
13
|
+
)
|
|
@@ -36,6 +36,9 @@ class PhotonCameraSim:
|
|
|
36
36
|
self,
|
|
37
37
|
camera: PhotonCamera,
|
|
38
38
|
props: SimCameraProperties = SimCameraProperties.PERFECT_90DEG(),
|
|
39
|
+
tagLayout: AprilTagFieldLayout = AprilTagFieldLayout.loadField(
|
|
40
|
+
AprilTagField.kDefaultField
|
|
41
|
+
),
|
|
39
42
|
minTargetAreaPercent: float | None = None,
|
|
40
43
|
maxSightRange: meters | None = None,
|
|
41
44
|
):
|
|
@@ -48,7 +51,7 @@ class PhotonCameraSim:
|
|
|
48
51
|
|
|
49
52
|
:param camera: The camera to be simulated
|
|
50
53
|
:param prop: Properties of this camera such as FOV and FPS
|
|
51
|
-
:param minTargetAreaPercent: The minimum percentage(0 - 100) a detected target must take up of
|
|
54
|
+
:param minTargetAreaPercent: The minimum percentage (0 - 100) a detected target must take up of
|
|
52
55
|
the camera's image to be processed. Match this with your contour filtering settings in the
|
|
53
56
|
PhotonVision GUI.
|
|
54
57
|
:param maxSightRangeMeters: Maximum distance at which the target is illuminated to your camera.
|
|
@@ -64,7 +67,7 @@ class PhotonCameraSim:
|
|
|
64
67
|
self.videoSimProcEnabled: bool = False
|
|
65
68
|
self.heartbeatCounter: int = 0
|
|
66
69
|
self.nextNtEntryTime = wpilib.Timer.getFPGATimestamp()
|
|
67
|
-
self.tagLayout =
|
|
70
|
+
self.tagLayout = tagLayout
|
|
68
71
|
|
|
69
72
|
self.cam = camera
|
|
70
73
|
self.prop = props
|
|
@@ -203,7 +206,7 @@ class PhotonCameraSim:
|
|
|
203
206
|
return None
|
|
204
207
|
|
|
205
208
|
def setMinTargetAreaPercent(self, areaPercent: float) -> None:
|
|
206
|
-
"""The minimum percentage(0 - 100) a detected target must take up of the camera's image to be
|
|
209
|
+
"""The minimum percentage (0 - 100) a detected target must take up of the camera's image to be
|
|
207
210
|
processed.
|
|
208
211
|
"""
|
|
209
212
|
self.minTargetAreaPercent = areaPercent
|
|
@@ -268,6 +271,9 @@ class PhotonCameraSim:
|
|
|
268
271
|
camRt = RotTrlTransform3d.makeRelativeTo(cameraPose)
|
|
269
272
|
|
|
270
273
|
for tgt in targets:
|
|
274
|
+
if len(detectableTgts) >= 50:
|
|
275
|
+
break
|
|
276
|
+
|
|
271
277
|
# pose isn't visible, skip to next
|
|
272
278
|
if not self.canSeeTargetPose(cameraPose, tgt):
|
|
273
279
|
continue
|
|
@@ -417,14 +423,15 @@ class PhotonCameraSim:
|
|
|
417
423
|
|
|
418
424
|
# put this simulated data to NT
|
|
419
425
|
self.heartbeatCounter += 1
|
|
420
|
-
|
|
426
|
+
publishTimestampMicros = wpilib.Timer.getFPGATimestamp() * 1e6
|
|
421
427
|
return PhotonPipelineResult(
|
|
428
|
+
ntReceiveTimestampMicros=int(publishTimestampMicros + 10),
|
|
422
429
|
metadata=PhotonPipelineMetadata(
|
|
423
|
-
|
|
424
|
-
int(
|
|
425
|
-
|
|
430
|
+
captureTimestampMicros=int(publishTimestampMicros - latency * 1e6),
|
|
431
|
+
publishTimestampMicros=int(publishTimestampMicros),
|
|
432
|
+
sequenceID=self.heartbeatCounter,
|
|
426
433
|
# Pretend like we heard a pong recently
|
|
427
|
-
int(np.random.uniform(950, 1050)),
|
|
434
|
+
timeSinceLastPong=int(np.random.uniform(950, 1050)),
|
|
428
435
|
),
|
|
429
436
|
targets=detectableTgts,
|
|
430
437
|
multitagResult=multiTagResults,
|
|
@@ -474,11 +481,11 @@ class PhotonCameraSim:
|
|
|
474
481
|
|
|
475
482
|
intrinsics = self.prop.getIntrinsics()
|
|
476
483
|
intrinsicsView = intrinsics.flatten().tolist()
|
|
477
|
-
self.ts.cameraIntrinsicsPublisher.set(intrinsicsView, receiveTimestamp_us)
|
|
484
|
+
self.ts.cameraIntrinsicsPublisher.set(list(intrinsicsView), receiveTimestamp_us)
|
|
478
485
|
|
|
479
486
|
distortion = self.prop.getDistCoeffs()
|
|
480
487
|
distortionView = distortion.flatten().tolist()
|
|
481
|
-
self.ts.cameraDistortionPublisher.set(distortionView, receiveTimestamp_us)
|
|
488
|
+
self.ts.cameraDistortionPublisher.set(list(distortionView), receiveTimestamp_us)
|
|
482
489
|
|
|
483
490
|
self.ts.heartbeatPublisher.set(self.heartbeatCounter, receiveTimestamp_us)
|
|
484
491
|
self.heartbeatCounter += 1
|
|
@@ -195,7 +195,7 @@ class SimCameraProperties:
|
|
|
195
195
|
return self.latencyStdDev
|
|
196
196
|
|
|
197
197
|
def getContourAreaPercent(self, points: np.ndarray) -> float:
|
|
198
|
-
"""The percentage(0 - 100) of this camera's resolution the contour takes up in pixels of the
|
|
198
|
+
"""The percentage (0 - 100) of this camera's resolution the contour takes up in pixels of the
|
|
199
199
|
image.
|
|
200
200
|
|
|
201
201
|
:param points: Points of the contour
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
from .multiTargetPNPResult import MultiTargetPNPResult, PnpResult
|
|
2
|
+
from .photonPipelineResult import PhotonPipelineMetadata, PhotonPipelineResult
|
|
3
|
+
from .photonTrackedTarget import PhotonTrackedTarget
|
|
4
|
+
from .TargetCorner import TargetCorner
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
__all__ = (
|
|
7
|
+
"MultiTargetPNPResult",
|
|
8
|
+
"PnpResult",
|
|
9
|
+
"PhotonPipelineMetadata",
|
|
10
|
+
"PhotonPipelineResult",
|
|
11
|
+
"PhotonTrackedTarget",
|
|
12
|
+
"TargetCorner",
|
|
13
|
+
)
|
|
@@ -47,13 +47,10 @@ class PhotonPipelineResult:
|
|
|
47
47
|
timestamp, coproc timebase))
|
|
48
48
|
"""
|
|
49
49
|
# TODO - we don't trust NT4 to correctly latency-compensate ntReceiveTimestampMicros
|
|
50
|
-
|
|
51
|
-
self.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- self.metadata.captureTimestampMicros
|
|
55
|
-
)
|
|
56
|
-
) / 1e6
|
|
50
|
+
latency = (
|
|
51
|
+
self.metadata.publishTimestampMicros - self.metadata.captureTimestampMicros
|
|
52
|
+
)
|
|
53
|
+
return (self.ntReceiveTimestampMicros - latency) / 1e6
|
|
57
54
|
|
|
58
55
|
def getTargets(self) -> list[PhotonTrackedTarget]:
|
|
59
56
|
return self.targets
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# no one but us chickens
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import socket
|
|
3
|
+
import struct
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
from wpilib import Timer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TspPing:
|
|
11
|
+
def __init__(self, version: int, message_id: int, client_time: int):
|
|
12
|
+
self.version = version
|
|
13
|
+
self.message_id = message_id
|
|
14
|
+
self.client_time = client_time
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def unpack(data: bytes) -> "TspPing":
|
|
18
|
+
# Unpack using struct.unpack
|
|
19
|
+
version, message_id, client_time = struct.unpack("<BBQ", data)
|
|
20
|
+
return TspPing(version, message_id, client_time)
|
|
21
|
+
|
|
22
|
+
def pack(self) -> bytes:
|
|
23
|
+
# Pack using struct.pack
|
|
24
|
+
return struct.pack("<BBQ", self.version, self.message_id, self.client_time)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TspPong:
|
|
28
|
+
def __init__(self, ping: "TspPing", server_time: int):
|
|
29
|
+
self.version = ping.version
|
|
30
|
+
self.message_id = 2 # Pong message ID
|
|
31
|
+
self.client_time = ping.client_time
|
|
32
|
+
self.server_time = server_time
|
|
33
|
+
|
|
34
|
+
def pack(self) -> bytes:
|
|
35
|
+
# Pack using struct.pack
|
|
36
|
+
return struct.pack(
|
|
37
|
+
"<BBQQ", self.version, self.message_id, self.client_time, self.server_time
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def unpack(data: bytes) -> "TspPong":
|
|
42
|
+
# Unpack using struct.unpack
|
|
43
|
+
version, message_id, client_time, server_time = struct.unpack("<BBQQ", data)
|
|
44
|
+
ping = TspPing(version, message_id, client_time)
|
|
45
|
+
return TspPong(ping, server_time)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TimeSyncServer:
|
|
49
|
+
"""This class is a python re-write of the UDP time sync server protocol
|
|
50
|
+
which runs on a roboRIO to establish a timebase for all PhotonVision coprocessors.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
PORT = 5810
|
|
54
|
+
|
|
55
|
+
def __init__(self, time_provider: Optional[Callable[[], int]] = None):
|
|
56
|
+
self.time_provider = time_provider or Timer.getFPGATimestamp
|
|
57
|
+
self._process: Optional[threading.Thread] = None
|
|
58
|
+
self.logger = logging.getLogger("PhotonVision-TimeSyncServer")
|
|
59
|
+
|
|
60
|
+
def _udp_server(self):
|
|
61
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket:
|
|
62
|
+
udp_socket.bind(("0.0.0.0", self.PORT))
|
|
63
|
+
while True:
|
|
64
|
+
data, addr = udp_socket.recvfrom(1024) # Buffer size of 1024 bytes
|
|
65
|
+
|
|
66
|
+
if len(data) < 10:
|
|
67
|
+
self.logger.error("Too few bytes")
|
|
68
|
+
continue # Ignore incomplete packets
|
|
69
|
+
|
|
70
|
+
ping = TspPing.unpack(data)
|
|
71
|
+
if ping.version != 1 or ping.message_id != 1:
|
|
72
|
+
self.logger.error("Invalid Version/ID")
|
|
73
|
+
continue # Ignore invalid pings
|
|
74
|
+
|
|
75
|
+
server_time = int(self.time_provider() * 1e6) # Convert to microseconds
|
|
76
|
+
pong = TspPong(ping, server_time)
|
|
77
|
+
udp_socket.sendto(pong.pack(), addr)
|
|
78
|
+
|
|
79
|
+
def start(self):
|
|
80
|
+
if self._process is not None and self._process.is_alive():
|
|
81
|
+
return # Nothing to do
|
|
82
|
+
|
|
83
|
+
self._process = threading.Thread(target=self._udp_server, daemon=True)
|
|
84
|
+
self._process.start()
|
|
85
|
+
self.logger.info("Server Started")
|
|
86
|
+
|
|
87
|
+
def stop(self):
|
|
88
|
+
if self._process is not None:
|
|
89
|
+
self._process.join()
|
|
90
|
+
self._process = None
|
|
91
|
+
self.logger.info("Server Stopped")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
inst = TimeSyncServer()
|
photonlibpy/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
PHOTONLIB_VERSION="
|
|
2
|
-
PHOTONVISION_VERSION="
|
|
1
|
+
PHOTONLIB_VERSION="2026.1.1"
|
|
2
|
+
PHOTONVISION_VERSION="v2026.1.1"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: photonlibpy
|
|
3
|
+
Version: 2026.1.1
|
|
4
|
+
Summary: Pure-python implementation of PhotonLib for interfacing with PhotonVision on coprocessors. Implemented with PhotonVision version v2026.1.1 .
|
|
5
|
+
Home-page: https://photonvision.org
|
|
6
|
+
Author: Photonvision Development Team
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: numpy~=2.3
|
|
10
|
+
Requires-Dist: wpilib<2027,>=2026.2.1
|
|
11
|
+
Requires-Dist: robotpy-wpimath<2027,>=2026.2.1
|
|
12
|
+
Requires-Dist: robotpy-apriltag<2027,>=2026.2.1
|
|
13
|
+
Requires-Dist: robotpy-cscore<2027,>=2026.2.1
|
|
14
|
+
Requires-Dist: pyntcore<2027,>=2026.2.1
|
|
15
|
+
Requires-Dist: opencv-python; platform_machine != "roborio"
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
A Pure-python implementation of PhotonLib
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
photonlibpy/__init__.py,sha256=zPyxHwPCDSeCz7OeAAtzfn9-TnHNlG2qRyNQ8yPy0yQ,1379
|
|
2
|
+
photonlibpy/estimatedRobotPose.py,sha256=gKKALm4PcMDBzkF7WganGW9srONJQgQjZioHY588AHA,1413
|
|
3
|
+
photonlibpy/packet.py,sha256=5YomViVFwOljL2FGOetWM9FbPc_yCQ15ylzkYlgLIs8,9724
|
|
4
|
+
photonlibpy/photonCamera.py,sha256=chSwxxfCbunlam9t0BaE9deNMxJ6x9KyBPJBA9esir4,13908
|
|
5
|
+
photonlibpy/photonPoseEstimator.py,sha256=t10pyh38FLRMwvqAuz9rnf9MNaE8AEkBFhoYaODW3kI,10387
|
|
6
|
+
photonlibpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
photonlibpy/version.py,sha256=6_AEn0cUFelka8gVEzXz3CFr_yA8RdW99pRU9O8et4s,62
|
|
8
|
+
photonlibpy/estimation/__init__.py,sha256=pZ-d6fN1DJvT-lRl4FfIos5HAvlzmetIOrGIinrdv7k,223
|
|
9
|
+
photonlibpy/estimation/cameraTargetRelation.py,sha256=i7DPBXtkZve4ToXQscEIe-5F1oGQ1Qmf5QBaE__EeMQ,1158
|
|
10
|
+
photonlibpy/estimation/openCVHelp.py,sha256=O1dV7v7RHSyw7l5L0QXbal6t9K7iyvEG76tG-t4AFVg,12388
|
|
11
|
+
photonlibpy/estimation/rotTrlTransform3d.py,sha256=oRYk4V1XF7guNxefJsCPcBWPIkKZl0HYD_Gs86wwToE,2671
|
|
12
|
+
photonlibpy/estimation/targetModel.py,sha256=fIhkf0-_Sfv-KhYEBnhFWMF4Xu84FfF6B-KUEsuuGjQ,6459
|
|
13
|
+
photonlibpy/estimation/visionEstimation.py,sha256=Q4KWdLCV1H0wJUJCzG7OYNhVmk2jR1iPUmX_PFylLdA,4174
|
|
14
|
+
photonlibpy/generated/MultiTargetPNPResultSerde.py,sha256=GH31IKMyHglymJ5phA2I-xJW3QmI9jEv-hmql6r6RoU,2693
|
|
15
|
+
photonlibpy/generated/PhotonPipelineMetadataSerde.py,sha256=RjYLPVE_Od-N2LfeI0_5wvtYKCAjOhArsNSZOcQShmY,3074
|
|
16
|
+
photonlibpy/generated/PhotonPipelineResultSerde.py,sha256=4TrLO8a61eLSfT_cux3Y4AAgjhDoGOiRCfyBLTL6mBg,3302
|
|
17
|
+
photonlibpy/generated/PhotonTrackedTargetSerde.py,sha256=vTuugrX6EzegQYOBspa5i0YAWXvru4NxaBlNkjmnEz0,4624
|
|
18
|
+
photonlibpy/generated/PnpResultSerde.py,sha256=YHKEGaTHNRvd0NMIpw-cO3hOuSqXnkPxmSi3v3nr5OE,2880
|
|
19
|
+
photonlibpy/generated/TargetCornerSerde.py,sha256=FJ6rPTGlwpp6ORUYU-y09xaZxBFlV1o1pft2uVHJMKQ,2377
|
|
20
|
+
photonlibpy/generated/__init__.py,sha256=mElM8M88---wxTWO-SRqIJ4EfxN0fdIUwZBZ-UIGuRw,428
|
|
21
|
+
photonlibpy/networktables/NTTopicSet.py,sha256=sSZtW23OJVNzpfwc2abx1lFW4RWrGpGeta62pKe18ZY,3241
|
|
22
|
+
photonlibpy/networktables/__init__.py,sha256=o_LxTdyIylAszMy_zhUtTkXHyu6jqxccccj78d44OrI,35
|
|
23
|
+
photonlibpy/simulation/__init__.py,sha256=HazsBMXg1HT8TnyxYO8QI9NXwZOrtuCSytnTdquLBKw,358
|
|
24
|
+
photonlibpy/simulation/photonCameraSim.py,sha256=RojKliUvmgc5UTdSKttvtj1CW7ANZyt11_xYqixamTs,20241
|
|
25
|
+
photonlibpy/simulation/simCameraProperties.py,sha256=qU-3zDpETKcmM23Mi1UvcL1uOE0buBYlblPlZrFtvDs,27196
|
|
26
|
+
photonlibpy/simulation/videoSimUtil.py,sha256=xMuTvJ2Jx9IoQqmAJi_zUm06MdEwhVpIz9OyzYQp0k4,29
|
|
27
|
+
photonlibpy/simulation/visionSystemSim.py,sha256=GmKs0d32WE8B020YEWnj-0dQuCnVv1ScGdcFl1fOsKo,13835
|
|
28
|
+
photonlibpy/simulation/visionTargetSim.py,sha256=FH85fKE4NntowUvssfgZ1KlE-I_3Z-QuAgb2bFqvfdY,2219
|
|
29
|
+
photonlibpy/targeting/TargetCorner.py,sha256=ouKj3E5uD76OZSNHHuSDzKOY65a8HqtcOsuejH-MVsU,276
|
|
30
|
+
photonlibpy/targeting/__init__.py,sha256=OxxkBvBa6sFdjG7T1hO8CwBkRHk6GYdXbVhVgYfW7Gc,402
|
|
31
|
+
photonlibpy/targeting/multiTargetPNPResult.py,sha256=Y9rweHtMzoCZ6mv6F8CutQi2Thq5pHN0ydBWvTCsOwY,806
|
|
32
|
+
photonlibpy/targeting/photonPipelineResult.py,sha256=H_wsIQfdo41xNT32YqfttH6AYQRZhAMR7XDiysMa3lw,2700
|
|
33
|
+
photonlibpy/targeting/photonTrackedTarget.py,sha256=zCoFp32hX-3GmBYEmsYBQieBoMzXtP2F_55_q0zPOXA,1956
|
|
34
|
+
photonlibpy/timesync/__init__.py,sha256=pECFraHRZC2Dl4x4OrmhW8oWkawP68pCb-Y61dZycJs,25
|
|
35
|
+
photonlibpy/timesync/timeSyncServer.py,sha256=0d76uHBS_Jo3etvougdZHapbRrbb31iu-jlf8h9U_QQ,3194
|
|
36
|
+
photonlibpy-2026.1.1.dist-info/METADATA,sha256=KdRf2WnQteiGxvdUy1bQpG0t-__rKPyi1SMDmg-16nM,871
|
|
37
|
+
photonlibpy-2026.1.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
38
|
+
photonlibpy-2026.1.1.dist-info/top_level.txt,sha256=T8Xc6U6he2VjKUAca6zawSkHdUZuLanxYIc4nxw2ctc,12
|
|
39
|
+
photonlibpy-2026.1.1.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: photonlibpy
|
|
3
|
-
Version: 2025.0.0b8
|
|
4
|
-
Summary: Pure-python implementation of PhotonLib for interfacing with PhotonVision on coprocessors. Implemented with PhotonVision version v2025.0.0-beta-8 .
|
|
5
|
-
Home-page: https://photonvision.org
|
|
6
|
-
Author: Photonvision Development Team
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: numpy~=2.1
|
|
9
|
-
Requires-Dist: wpilib<2026,>=2025.0.0b1
|
|
10
|
-
Requires-Dist: robotpy-wpimath<2026,>=2025.0.0b1
|
|
11
|
-
Requires-Dist: robotpy-apriltag<2026,>=2025.0.0b1
|
|
12
|
-
Requires-Dist: robotpy-cscore<2026,>=2025.0.0b1
|
|
13
|
-
Requires-Dist: pyntcore<2026,>=2025.0.0b1
|
|
14
|
-
Requires-Dist: opencv-python; platform_machine != "roborio"
|
|
15
|
-
|
|
16
|
-
A Pure-python implementation of PhotonLib
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
photonlibpy/__init__.py,sha256=WW1OGrrcNXwwxaHSZlkxmhH2GYiQIHHxSxGVTJZhZbY,1136
|
|
2
|
-
photonlibpy/estimatedRobotPose.py,sha256=X7wF9xdPXGKSVy0MY0qrWZJOEbuZPd721lYp0KXKlP0,1603
|
|
3
|
-
photonlibpy/packet.py,sha256=5YomViVFwOljL2FGOetWM9FbPc_yCQ15ylzkYlgLIs8,9724
|
|
4
|
-
photonlibpy/photonCamera.py,sha256=ENBHp959it4aLnFtV66rHoAIYCvK4bTflZMrGSCwnZ8,12905
|
|
5
|
-
photonlibpy/photonPoseEstimator.py,sha256=2iMqxPFsQHTsq95yv-WCSv1a6wXNcHPqOyMc4Bu6IG0,12584
|
|
6
|
-
photonlibpy/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
-
photonlibpy/version.py,sha256=Gi-5Nspy6P3-YhTRXS3INLKV_X1zGeRsdORosEs-li4,77
|
|
8
|
-
photonlibpy/estimation/__init__.py,sha256=pZ-d6fN1DJvT-lRl4FfIos5HAvlzmetIOrGIinrdv7k,223
|
|
9
|
-
photonlibpy/estimation/cameraTargetRelation.py,sha256=i7DPBXtkZve4ToXQscEIe-5F1oGQ1Qmf5QBaE__EeMQ,1158
|
|
10
|
-
photonlibpy/estimation/openCVHelp.py,sha256=O1dV7v7RHSyw7l5L0QXbal6t9K7iyvEG76tG-t4AFVg,12388
|
|
11
|
-
photonlibpy/estimation/rotTrlTransform3d.py,sha256=oRYk4V1XF7guNxefJsCPcBWPIkKZl0HYD_Gs86wwToE,2671
|
|
12
|
-
photonlibpy/estimation/targetModel.py,sha256=fIhkf0-_Sfv-KhYEBnhFWMF4Xu84FfF6B-KUEsuuGjQ,6459
|
|
13
|
-
photonlibpy/estimation/visionEstimation.py,sha256=Q4KWdLCV1H0wJUJCzG7OYNhVmk2jR1iPUmX_PFylLdA,4174
|
|
14
|
-
photonlibpy/generated/MultiTargetPNPResultSerde.py,sha256=CzsCosHUnzxAoSC_sSAqpsXsDp54KW-BClnIXdzfMPc,2506
|
|
15
|
-
photonlibpy/generated/PhotonPipelineMetadataSerde.py,sha256=9pq5XUEDEoRowCd7sRnuGV7xtugu5HZCRrnwqZG2xXc,2887
|
|
16
|
-
photonlibpy/generated/PhotonPipelineResultSerde.py,sha256=8_YhW1icdhPOx5eAqwKadvnR29NABmhHr6ERAWK8wEo,3115
|
|
17
|
-
photonlibpy/generated/PhotonTrackedTargetSerde.py,sha256=-6vKir_ABDVBGbg8ktM48IKm_nBMFBbiyuZLiO_fP9U,4437
|
|
18
|
-
photonlibpy/generated/PnpResultSerde.py,sha256=YoTKdQ51oSdxC-7Poy6hunL0-zkMKvP5uedqaHWPudY,2693
|
|
19
|
-
photonlibpy/generated/TargetCornerSerde.py,sha256=kziD_rQIwyhzPfgOaDgn-3d87tvtXiAYbBjzu76biYU,2190
|
|
20
|
-
photonlibpy/generated/__init__.py,sha256=mElM8M88---wxTWO-SRqIJ4EfxN0fdIUwZBZ-UIGuRw,428
|
|
21
|
-
photonlibpy/networktables/NTTopicSet.py,sha256=29wPgXcuqT-u72-YXwSjRHWhECNzU8eDsexcqlA8KQ0,2967
|
|
22
|
-
photonlibpy/networktables/__init__.py,sha256=o_LxTdyIylAszMy_zhUtTkXHyu6jqxccccj78d44OrI,35
|
|
23
|
-
photonlibpy/simulation/__init__.py,sha256=HKJV02of5d8bOnuI7syLzSYtOYge7XUrHSaLvawh99M,227
|
|
24
|
-
photonlibpy/simulation/photonCameraSim.py,sha256=8ELLcOXUtDftV_OYzGG03wigANimJs2SehV-fluWy3k,19907
|
|
25
|
-
photonlibpy/simulation/simCameraProperties.py,sha256=ODVxnylF8zw9HZSbw0PzG_OEtUo9ChRo-G_iEgADOCg,27195
|
|
26
|
-
photonlibpy/simulation/videoSimUtil.py,sha256=xMuTvJ2Jx9IoQqmAJi_zUm06MdEwhVpIz9OyzYQp0k4,29
|
|
27
|
-
photonlibpy/simulation/visionSystemSim.py,sha256=GmKs0d32WE8B020YEWnj-0dQuCnVv1ScGdcFl1fOsKo,13835
|
|
28
|
-
photonlibpy/simulation/visionTargetSim.py,sha256=FH85fKE4NntowUvssfgZ1KlE-I_3Z-QuAgb2bFqvfdY,2219
|
|
29
|
-
photonlibpy/targeting/TargetCorner.py,sha256=ouKj3E5uD76OZSNHHuSDzKOY65a8HqtcOsuejH-MVsU,276
|
|
30
|
-
photonlibpy/targeting/__init__.py,sha256=YzINSpq6A0cjr-yAQcFqHoiYdLGKPFXThlVYlMjY11w,295
|
|
31
|
-
photonlibpy/targeting/multiTargetPNPResult.py,sha256=Y9rweHtMzoCZ6mv6F8CutQi2Thq5pHN0ydBWvTCsOwY,806
|
|
32
|
-
photonlibpy/targeting/photonPipelineResult.py,sha256=MbaSyHZTJpoKTtLOZztpSGSt9xWWFqhzgwj8medObVA,2732
|
|
33
|
-
photonlibpy/targeting/photonTrackedTarget.py,sha256=zCoFp32hX-3GmBYEmsYBQieBoMzXtP2F_55_q0zPOXA,1956
|
|
34
|
-
photonlibpy-2025.0.0b8.dist-info/METADATA,sha256=f0Zgv_wfJOSge_dKazRJUnuld9Vb_3oWMLC_AOTeMwI,689
|
|
35
|
-
photonlibpy-2025.0.0b8.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
36
|
-
photonlibpy-2025.0.0b8.dist-info/top_level.txt,sha256=T8Xc6U6he2VjKUAca6zawSkHdUZuLanxYIc4nxw2ctc,12
|
|
37
|
-
photonlibpy-2025.0.0b8.dist-info/RECORD,,
|
|
File without changes
|