1515## along with this program. If not, see <https://www.gnu.org/licenses/>.
1616###############################################################################
1717
18+ from test import testUtil
19+
1820import wpimath .units
1921from photonlibpy import PhotonCamera , PhotonPoseEstimator , PoseStrategy
2022from photonlibpy .estimation import TargetModel
@@ -191,7 +193,7 @@ def test_pnpDistanceTrigSolve():
191193 assert bestTarget .fiducialId == 0
192194 assert result .ntReceiveTimestampMicros > 0
193195 # Make test independent of the FPGA time.
194- result .ntReceiveTimestampMicros = fakeTimestampSecs * 1e6
196+ result .ntReceiveTimestampMicros = int ( fakeTimestampSecs * 1e6 )
195197
196198 estimator .addHeadingData (
197199 result .getTimestampSeconds (), realPose .rotation ().toRotation2d ()
@@ -217,7 +219,7 @@ def test_pnpDistanceTrigSolve():
217219 assert bestTarget .fiducialId == 0
218220 assert result .ntReceiveTimestampMicros > 0
219221 # Make test independent of the FPGA time.
220- result .ntReceiveTimestampMicros = fakeTimestampSecs * 1e6
222+ result .ntReceiveTimestampMicros = int ( fakeTimestampSecs * 1e6 )
221223
222224 estimator .addHeadingData (
223225 result .getTimestampSeconds (), realPose .rotation ().toRotation2d ()
@@ -289,8 +291,36 @@ def test_multiTagOnCoprocStrategy():
289291def test_cacheIsInvalidated ():
290292 aprilTags = fakeAprilTagFieldLayout ()
291293 cameraOne = PhotonCameraInjector ()
294+
295+ estimator = PhotonPoseEstimator (
296+ aprilTags , PoseStrategy .LOWEST_AMBIGUITY , cameraOne , Transform3d ()
297+ )
298+
299+ # Initial state, expect no timestamp.
300+ assertEquals (- 1 , estimator ._poseCacheTimestampSeconds )
301+
302+ # First result is 17s after epoch start.
303+ timestamps = testUtil .PipelineTimestamps (captureTimestampMicros = 17_000_000 )
304+ latencySecs = timestamps .pipelineLatencySecs ()
305+
306+ # No targets, expect empty result
307+ cameraOne .result = PhotonPipelineResult (
308+ timestamps .receiveTimestampMicros (),
309+ metadata = timestamps .toPhotonPipelineMetadata (),
310+ )
311+ estimatedPose = estimator .update ()
312+
313+ assert estimatedPose is None
314+ assertEquals (
315+ timestamps .receiveTimestampMicros () * 1e-6 - latencySecs ,
316+ estimator ._poseCacheTimestampSeconds ,
317+ 1e-3 ,
318+ )
319+
320+ # Set actual result
321+ timestamps .incrementTimeMicros (2_500_000 )
292322 result = PhotonPipelineResult (
293- int ( 20 * 1e6 ),
323+ timestamps . receiveTimestampMicros ( ),
294324 [
295325 PhotonTrackedTarget (
296326 3.0 ,
@@ -315,31 +345,21 @@ def test_cacheIsInvalidated():
315345 0.7 ,
316346 )
317347 ],
318- metadata = PhotonPipelineMetadata (0 , int (2 * 1e3 ), 0 ),
319- )
320-
321- estimator = PhotonPoseEstimator (
322- aprilTags , PoseStrategy .LOWEST_AMBIGUITY , cameraOne , Transform3d ()
348+ metadata = timestamps .toPhotonPipelineMetadata (),
323349 )
324-
325- # Empty result, expect empty result
326- cameraOne .result = PhotonPipelineResult (0 )
327- estimatedPose = estimator .update ()
328- assert estimatedPose is None
329-
330- # Set actual result
331350 cameraOne .result = result
332351 estimatedPose = estimator .update ()
333352 assert estimatedPose is not None
334- assertEquals (20 , estimatedPose .timestampSeconds , 0.01 )
335- assertEquals (20 - 2e-3 , estimator ._poseCacheTimestampSeconds , 1e-3 )
353+ expectedTimestamp = timestamps .receiveTimestampMicros () * 1e-6 - latencySecs
354+ assertEquals (expectedTimestamp , estimatedPose .timestampSeconds , 1e-3 )
355+ assertEquals (expectedTimestamp , estimator ._poseCacheTimestampSeconds , 1e-3 )
336356
337357 # And again -- pose cache should mean this is empty
338358 cameraOne .result = result
339359 estimatedPose = estimator .update ()
340360 assert estimatedPose is None
341361 # Expect the old timestamp to still be here
342- assertEquals (20 - 2e-3 , estimator ._poseCacheTimestampSeconds , 1e-3 )
362+ assertEquals (expectedTimestamp , estimator ._poseCacheTimestampSeconds , 1e-3 )
343363
344364 # Set new field layout -- right after, the pose cache timestamp should be -1
345365 estimator .fieldTags = AprilTagFieldLayout ([AprilTag ()], 0 , 0 )
@@ -350,8 +370,14 @@ def test_cacheIsInvalidated():
350370
351371 assert estimatedPose is not None
352372
353- assertEquals (20 , estimatedPose .timestampSeconds , 0.01 )
354- assertEquals (20 - 2e-3 , estimator ._poseCacheTimestampSeconds , 1e-3 )
373+ assertEquals (expectedTimestamp , estimatedPose .timestampSeconds , 1e-3 )
374+ assertEquals (expectedTimestamp , estimator ._poseCacheTimestampSeconds , 1e-3 )
375+
376+ # Setting a value from None to a non-None should invalidate the cache.
377+ assert estimator .referencePose is None
378+ estimator .referencePose = Pose3d (3 , 3 , 3 , Rotation3d ())
379+
380+ assertEquals (- 1 , estimator ._poseCacheTimestampSeconds )
355381
356382
357383def assertEquals (expected , actual , epsilon = 0.0 ):
0 commit comments