photonlibpy 2025.3.2__py3-none-any.whl → 2026.0.0a1__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.
@@ -0,0 +1,617 @@
1
+ import math
2
+
3
+ import pytest
4
+ from photonlibpy.estimation import TargetModel, VisionEstimation
5
+ from photonlibpy.photonCamera import PhotonCamera
6
+ from photonlibpy.simulation import PhotonCameraSim, VisionSystemSim, VisionTargetSim
7
+ from robotpy_apriltag import AprilTag, AprilTagFieldLayout
8
+ from wpimath.geometry import (
9
+ Pose2d,
10
+ Pose3d,
11
+ Rotation2d,
12
+ Rotation3d,
13
+ Transform3d,
14
+ Translation2d,
15
+ Translation3d,
16
+ )
17
+ from wpimath.units import feetToMeters, meters
18
+
19
+
20
+ def test_VisibilityCupidShuffle() -> None:
21
+ targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
22
+
23
+ visionSysSim = VisionSystemSim("Test")
24
+ camera = PhotonCamera("camera")
25
+ cameraSim = PhotonCameraSim(camera)
26
+ visionSysSim.addCamera(cameraSim, Transform3d())
27
+
28
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
29
+
30
+ visionSysSim.addVisionTargets(
31
+ [
32
+ VisionTargetSim(
33
+ targetPose, TargetModel.createPlanar(width=1.0, height=1.0), 4774
34
+ )
35
+ ]
36
+ )
37
+
38
+ # To the right, to the right
39
+ robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(-70.0))
40
+ visionSysSim.update(robotPose)
41
+ assert not camera.getLatestResult().hasTargets()
42
+
43
+ # To the right, to the right
44
+ robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(-95.0))
45
+ visionSysSim.update(robotPose)
46
+ assert not camera.getLatestResult().hasTargets()
47
+
48
+ # To the left, to the left
49
+ robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(90.0))
50
+ visionSysSim.update(robotPose)
51
+ assert not camera.getLatestResult().hasTargets()
52
+
53
+ # To the left, to the left
54
+ robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(65.0))
55
+ visionSysSim.update(robotPose)
56
+ assert not camera.getLatestResult().hasTargets()
57
+
58
+ # Now kick, now kick
59
+ robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(5.0))
60
+ visionSysSim.update(robotPose)
61
+ assert camera.getLatestResult().hasTargets()
62
+
63
+ # Now kick, now kick
64
+ robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(-5.0))
65
+ visionSysSim.update(robotPose)
66
+ assert camera.getLatestResult().hasTargets()
67
+
68
+ # Now walk it by yourself
69
+ robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(-179.0))
70
+ visionSysSim.update(robotPose)
71
+ assert not camera.getLatestResult().hasTargets()
72
+
73
+ # Now walk it by yourself
74
+ visionSysSim.adjustCamera(
75
+ cameraSim, Transform3d(Translation3d(), Rotation3d(0, 0, math.pi))
76
+ )
77
+ visionSysSim.update(robotPose)
78
+ assert camera.getLatestResult().hasTargets()
79
+
80
+
81
+ def test_NotVisibleVert1() -> None:
82
+ targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
83
+
84
+ visionSysSim = VisionSystemSim("Test")
85
+ camera = PhotonCamera("camera")
86
+ cameraSim = PhotonCameraSim(camera)
87
+ visionSysSim.addCamera(cameraSim, Transform3d())
88
+
89
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
90
+
91
+ visionSysSim.addVisionTargets(
92
+ [
93
+ VisionTargetSim(
94
+ targetPose, TargetModel.createPlanar(width=3.0, height=3.0), 4774
95
+ )
96
+ ]
97
+ )
98
+
99
+ robotPose = Pose2d(Translation2d(5.0, 0.0), Rotation2d.fromDegrees(5.0))
100
+
101
+ visionSysSim.update(robotPose)
102
+ assert camera.getLatestResult().hasTargets()
103
+
104
+ visionSysSim.adjustCamera(
105
+ cameraSim,
106
+ Transform3d(Translation3d(0.0, 0.0, 5000.0), Rotation3d(0.0, 0.0, math.pi)),
107
+ )
108
+ visionSysSim.update(robotPose)
109
+ assert not camera.getLatestResult().hasTargets()
110
+
111
+
112
+ def test_NotVisibleVert2() -> None:
113
+ targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
114
+
115
+ robotToCamera = Transform3d(
116
+ Translation3d(0.0, 0.0, 1.0), Rotation3d(0.0, -math.pi / 4.0, 0.0)
117
+ )
118
+
119
+ visionSysSim = VisionSystemSim("Test")
120
+ camera = PhotonCamera("camera")
121
+ cameraSim = PhotonCameraSim(camera)
122
+ visionSysSim.addCamera(cameraSim, robotToCamera)
123
+
124
+ cameraSim.prop.setCalibrationFromFOV(
125
+ 4774, 4774, fovDiag=Rotation2d.fromDegrees(80.0)
126
+ )
127
+ visionSysSim.addVisionTargets(
128
+ [
129
+ VisionTargetSim(
130
+ targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774
131
+ )
132
+ ]
133
+ )
134
+
135
+ robotPose = Pose2d(Translation2d(13.98, 0.0), Rotation2d.fromDegrees(5.0))
136
+ visionSysSim.update(robotPose)
137
+ assert camera.getLatestResult().hasTargets()
138
+
139
+ robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0))
140
+ visionSysSim.update(robotPose)
141
+ assert not camera.getLatestResult().hasTargets()
142
+
143
+
144
+ def test_NotVisibleTargetSize() -> None:
145
+ targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi))
146
+
147
+ visionSysSim = VisionSystemSim("Test")
148
+ camera = PhotonCamera("camera")
149
+ cameraSim = PhotonCameraSim(camera)
150
+ visionSysSim.addCamera(cameraSim, Transform3d())
151
+
152
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
153
+ cameraSim.setMinTargetAreaPixels(20.0)
154
+ visionSysSim.addVisionTargets(
155
+ [
156
+ VisionTargetSim(
157
+ targetPose, TargetModel.createPlanar(width=0.1, height=0.1), 4774
158
+ )
159
+ ]
160
+ )
161
+
162
+ robotPose = Pose2d(Translation2d(12.0, 0.0), Rotation2d.fromDegrees(5.0))
163
+ visionSysSim.update(robotPose)
164
+ assert camera.getLatestResult().hasTargets()
165
+
166
+ robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0))
167
+ visionSysSim.update(robotPose)
168
+ assert not camera.getLatestResult().hasTargets()
169
+
170
+
171
+ def test_NotVisibleTooFarLeds() -> None:
172
+ targetPose = Pose3d(Translation3d(15.98, 0.0, 1.0), Rotation3d(0, 0, math.pi))
173
+
174
+ visionSysSim = VisionSystemSim("Test")
175
+ camera = PhotonCamera("camera")
176
+ cameraSim = PhotonCameraSim(camera)
177
+ visionSysSim.addCamera(cameraSim, Transform3d())
178
+
179
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
180
+ cameraSim.setMinTargetAreaPixels(1.0)
181
+ cameraSim.setMaxSightRange(10.0)
182
+ visionSysSim.addVisionTargets(
183
+ [
184
+ VisionTargetSim(
185
+ targetPose, TargetModel.createPlanar(width=1.0, height=1.0), 4774
186
+ )
187
+ ]
188
+ )
189
+
190
+ robotPose = Pose2d(Translation2d(10.0, 0.0), Rotation2d.fromDegrees(5.0))
191
+ visionSysSim.update(robotPose)
192
+ assert camera.getLatestResult().hasTargets()
193
+
194
+ robotPose = Pose2d(Translation2d(0.0, 0.0), Rotation2d.fromDegrees(5.0))
195
+ visionSysSim.update(robotPose)
196
+ assert not camera.getLatestResult().hasTargets()
197
+
198
+
199
+ @pytest.mark.parametrize(
200
+ "expected_yaw", [-10.0, -5.0, -2.0, -1.0, 0.0, 5.0, 7.0, 10.23]
201
+ )
202
+ def test_YawAngles(expected_yaw) -> None:
203
+ targetPose = Pose3d(
204
+ Translation3d(15.98, 0.0, 1.0), Rotation3d(0.0, 0.0, 3.0 * math.pi / 4.0)
205
+ )
206
+
207
+ visionSysSim = VisionSystemSim("Test")
208
+ camera = PhotonCamera("camera")
209
+ cameraSim = PhotonCameraSim(camera)
210
+
211
+ visionSysSim.addCamera(cameraSim, Transform3d())
212
+
213
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
214
+ cameraSim.setMinTargetAreaPixels(0.0)
215
+ visionSysSim.addVisionTargets(
216
+ [
217
+ VisionTargetSim(
218
+ targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774
219
+ )
220
+ ]
221
+ )
222
+
223
+ robotPose = Pose2d(Translation2d(10.0, 0.0), Rotation2d.fromDegrees(expected_yaw))
224
+ visionSysSim.update(robotPose)
225
+
226
+ result = camera.getLatestResult()
227
+
228
+ bestTarget = result.getBestTarget()
229
+ assert bestTarget is not None
230
+ assert bestTarget.getYaw() == pytest.approx(expected_yaw, abs=0.25)
231
+
232
+
233
+ @pytest.mark.parametrize(
234
+ "expected_pitch", [-10.0, -5.0, -2.0, -1.0, 0.0, 5.0, 7.0, 10.23]
235
+ )
236
+ def test_PitchAngles(expected_pitch) -> None:
237
+ targetPose = Pose3d(
238
+ Translation3d(15.98, 0.0, 0.0), Rotation3d(0, 0, 3.0 * math.pi / 4.0)
239
+ )
240
+ robotPose = Pose2d(
241
+ Translation2d(10.0, 0.0), Rotation2d.fromDegrees(-expected_pitch)
242
+ )
243
+ visionSysSim = VisionSystemSim("Test")
244
+ camera = PhotonCamera("camera")
245
+ cameraSim = PhotonCameraSim(camera)
246
+ visionSysSim.addCamera(cameraSim, Transform3d())
247
+
248
+ cameraSim.prop.setCalibrationFromFOV(
249
+ 640, 480, fovDiag=Rotation2d.fromDegrees(120.0)
250
+ )
251
+ cameraSim.setMinTargetAreaPixels(0.0)
252
+ visionSysSim.addVisionTargets(
253
+ [
254
+ VisionTargetSim(
255
+ targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774
256
+ )
257
+ ]
258
+ )
259
+ visionSysSim.adjustCamera(
260
+ cameraSim,
261
+ Transform3d(
262
+ Translation3d(), Rotation3d(0.0, math.radians(expected_pitch), 0.0)
263
+ ),
264
+ )
265
+ visionSysSim.update(robotPose)
266
+
267
+ result = camera.getLatestResult()
268
+
269
+ bestTarget = result.getBestTarget()
270
+ assert bestTarget is not None
271
+ assert bestTarget.getPitch() == pytest.approx(expected_pitch, abs=0.25)
272
+
273
+
274
+ @pytest.mark.parametrize(
275
+ "distParam, pitchParam, heightParam",
276
+ [
277
+ (5, -15.98, 0),
278
+ (6, -15.98, 1),
279
+ (10, -15.98, 0),
280
+ (15, -15.98, 2),
281
+ (19.95, -15.98, 0),
282
+ (20, -15.98, 0),
283
+ (5, -42, 1),
284
+ (6, -42, 0),
285
+ (10, -42, 2),
286
+ (15, -42, 0.5),
287
+ (19.42, -15.98, 0),
288
+ (20, -42, 0),
289
+ (5, -55, 2),
290
+ (6, -55, 0),
291
+ (10, -54, 2.2),
292
+ (15, -53, 0),
293
+ (19.52, -15.98, 1.1),
294
+ ],
295
+ )
296
+ def test_distanceCalc(distParam, pitchParam, heightParam) -> None:
297
+ distParam = feetToMeters(distParam)
298
+ pitchParam = math.radians(pitchParam)
299
+ heightParam = feetToMeters(heightParam)
300
+
301
+ targetPose = Pose3d(
302
+ Translation3d(15.98, 0.0, 1.0), Rotation3d(0.0, 0.0, 0.98 * math.pi)
303
+ )
304
+ robotPose = Pose3d(Translation3d(15.98 - distParam, 0.0, 0.0), Rotation3d())
305
+ robotToCamera = Transform3d(
306
+ Translation3d(0.0, 0.0, heightParam), Rotation3d(0.0, pitchParam, 0.0)
307
+ )
308
+
309
+ visionSysSim = VisionSystemSim(
310
+ "absurdlylongnamewhichshouldneveractuallyhappenbuteehwelltestitanywaysohowsyourdaygoingihopegoodhaveagreatrestofyourlife"
311
+ )
312
+ camera = PhotonCamera("camera")
313
+ cameraSim = PhotonCameraSim(camera)
314
+ visionSysSim.addCamera(cameraSim, Transform3d())
315
+
316
+ cameraSim.prop.setCalibrationFromFOV(
317
+ 640, 480, fovDiag=Rotation2d.fromDegrees(160.0)
318
+ )
319
+ cameraSim.setMinTargetAreaPixels(0.0)
320
+ visionSysSim.adjustCamera(cameraSim, robotToCamera)
321
+ visionSysSim.addVisionTargets(
322
+ [
323
+ VisionTargetSim(
324
+ targetPose, TargetModel.createPlanar(width=0.5, height=0.5), 4774
325
+ )
326
+ ]
327
+ )
328
+ visionSysSim.update(robotPose)
329
+
330
+ result = camera.getLatestResult()
331
+
332
+ target = result.getBestTarget()
333
+ assert target is not None
334
+ assert target.getYaw() == pytest.approx(0.0, abs=0.5)
335
+
336
+ # TODO Enable when PhotonUtils is ported
337
+ # dist = PhotonUtils.calculateDistanceToTarget(
338
+ # robotToCamera.Z(), targetPose.Z(), -pitchParam, math.degrees(target.getPitch())
339
+ # )
340
+ # assert dist == pytest.approx(distParam, abs=0.25)
341
+
342
+
343
+ def test_MultipleTargets() -> None:
344
+ targetPoseL = Pose3d(Translation3d(15.98, 2.0, 0.0), Rotation3d(0.0, 0.0, math.pi))
345
+ targetPoseC = Pose3d(Translation3d(15.98, 0.0, 0.0), Rotation3d(0.0, 0.0, math.pi))
346
+ targetPoseR = Pose3d(Translation3d(15.98, -2.0, 0.0), Rotation3d(0.0, 0.0, math.pi))
347
+
348
+ visionSysSim = VisionSystemSim("Test")
349
+ camera = PhotonCamera("camera")
350
+ cameraSim = PhotonCameraSim(camera)
351
+ visionSysSim.addCamera(cameraSim, Transform3d())
352
+
353
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
354
+ cameraSim.setMinTargetAreaPixels(20.0)
355
+
356
+ visionSysSim.addVisionTargets(
357
+ [
358
+ VisionTargetSim(
359
+ targetPoseL.transformBy(
360
+ Transform3d(Translation3d(0, 0, 0), Rotation3d())
361
+ ),
362
+ TargetModel.AprilTag16h5(),
363
+ 1,
364
+ ),
365
+ VisionTargetSim(
366
+ targetPoseC.transformBy(
367
+ Transform3d(Translation3d(0, 0, 0), Rotation3d())
368
+ ),
369
+ TargetModel.AprilTag16h5(),
370
+ 2,
371
+ ),
372
+ VisionTargetSim(
373
+ targetPoseR.transformBy(
374
+ Transform3d(Translation3d(0, 0, 0), Rotation3d())
375
+ ),
376
+ TargetModel.AprilTag16h5(),
377
+ 3,
378
+ ),
379
+ VisionTargetSim(
380
+ targetPoseL.transformBy(
381
+ Transform3d(Translation3d(0, 0, 1), Rotation3d())
382
+ ),
383
+ TargetModel.AprilTag16h5(),
384
+ 4,
385
+ ),
386
+ VisionTargetSim(
387
+ targetPoseC.transformBy(
388
+ Transform3d(Translation3d(0, 0, 1), Rotation3d())
389
+ ),
390
+ TargetModel.AprilTag16h5(),
391
+ 5,
392
+ ),
393
+ VisionTargetSim(
394
+ targetPoseR.transformBy(
395
+ Transform3d(Translation3d(0, 0, 1), Rotation3d())
396
+ ),
397
+ TargetModel.AprilTag16h5(),
398
+ 6,
399
+ ),
400
+ VisionTargetSim(
401
+ targetPoseL.transformBy(
402
+ Transform3d(Translation3d(0, 0, 0.5), Rotation3d())
403
+ ),
404
+ TargetModel.AprilTag16h5(),
405
+ 7,
406
+ ),
407
+ VisionTargetSim(
408
+ targetPoseC.transformBy(
409
+ Transform3d(Translation3d(0, 0, 0.5), Rotation3d())
410
+ ),
411
+ TargetModel.AprilTag16h5(),
412
+ 8,
413
+ ),
414
+ VisionTargetSim(
415
+ targetPoseL.transformBy(
416
+ Transform3d(Translation3d(0, 0, 0.75), Rotation3d())
417
+ ),
418
+ TargetModel.AprilTag16h5(),
419
+ 9,
420
+ ),
421
+ VisionTargetSim(
422
+ targetPoseR.transformBy(
423
+ Transform3d(Translation3d(0, 0, 0.75), Rotation3d())
424
+ ),
425
+ TargetModel.AprilTag16h5(),
426
+ 10,
427
+ ),
428
+ VisionTargetSim(
429
+ targetPoseL.transformBy(
430
+ Transform3d(Translation3d(0, 0, 0.25), Rotation3d())
431
+ ),
432
+ TargetModel.AprilTag16h5(),
433
+ 11,
434
+ ),
435
+ ]
436
+ )
437
+ robotPose = Pose2d(Translation2d(6.0, 0.0), Rotation2d.fromDegrees(0.25))
438
+ visionSysSim.update(robotPose)
439
+ res = camera.getLatestResult()
440
+ assert res.hasTargets()
441
+ tgtList = res.getTargets()
442
+ assert len(tgtList) == 11
443
+
444
+
445
+ def test_PoseEstimation() -> None:
446
+ visionSysSim = VisionSystemSim("Test")
447
+ camera = PhotonCamera("camera")
448
+ cameraSim = PhotonCameraSim(camera)
449
+ visionSysSim.addCamera(cameraSim, Transform3d())
450
+
451
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0))
452
+ cameraSim.setMinTargetAreaPixels(20.0)
453
+
454
+ tagList: list[AprilTag] = []
455
+ at0 = AprilTag()
456
+ at0.ID = 0
457
+ at0.pose = Pose3d(12.0, 3.0, 1.0, Rotation3d(0.0, 0.0, math.pi))
458
+ tagList.append(at0)
459
+ at1 = AprilTag()
460
+ at1.ID = 1
461
+ at1.pose = Pose3d(12.0, 1.0, -1.0, Rotation3d(0.0, 0.0, math.pi))
462
+ tagList.append(at1)
463
+ at2 = AprilTag()
464
+ at2.ID = 2
465
+ at2.pose = Pose3d(11.0, 0.0, 2.0, Rotation3d(0.0, 0.0, math.pi))
466
+ tagList.append(at2)
467
+
468
+ fieldLength: meters = 54.0
469
+ fieldWidth: meters = 27.0
470
+ layout = AprilTagFieldLayout(tagList, fieldLength, fieldWidth)
471
+ robotPose = Pose2d(Translation2d(5.0, 1.0), Rotation2d.fromDegrees(5.0))
472
+ visionSysSim.addVisionTargets(
473
+ [VisionTargetSim(tagList[0].pose, TargetModel.AprilTag16h5(), 0)]
474
+ )
475
+
476
+ visionSysSim.update(robotPose)
477
+
478
+ camEigen = cameraSim.prop.getIntrinsics()
479
+ distEigen = cameraSim.prop.getDistCoeffs()
480
+
481
+ camResults = camera.getLatestResult()
482
+ targets = camResults.getTargets()
483
+ results = VisionEstimation.estimateCamPosePNP(
484
+ camEigen, distEigen, targets, layout, TargetModel.AprilTag16h5()
485
+ )
486
+ assert results is not None
487
+ pose: Pose3d = Pose3d() + results.best
488
+ assert pose.X() == pytest.approx(5.0, abs=0.01)
489
+ assert pose.Y() == pytest.approx(1.0, abs=0.01)
490
+ assert pose.Z() == pytest.approx(0.0, abs=0.01)
491
+ assert pose.rotation().Z() == pytest.approx(math.radians(5.0), abs=0.01)
492
+
493
+ visionSysSim.addVisionTargets(
494
+ [VisionTargetSim(tagList[1].pose, TargetModel.AprilTag16h5(), 1)]
495
+ )
496
+ visionSysSim.addVisionTargets(
497
+ [VisionTargetSim(tagList[2].pose, TargetModel.AprilTag16h5(), 2)]
498
+ )
499
+ visionSysSim.update(robotPose)
500
+
501
+ camResults2 = camera.getLatestResult()
502
+ targets2 = camResults2.getTargets()
503
+ results2 = VisionEstimation.estimateCamPosePNP(
504
+ camEigen, distEigen, targets2, layout, TargetModel.AprilTag16h5()
505
+ )
506
+ assert results2 is not None
507
+ pose2 = Pose3d() + results2.best
508
+
509
+ assert pose2.X() == pytest.approx(robotPose.X(), abs=0.01)
510
+ assert pose2.Y() == pytest.approx(robotPose.Y(), abs=0.01)
511
+ assert pose2.Z() == pytest.approx(0.0, abs=0.01)
512
+ assert pose2.rotation().Z() == pytest.approx(math.radians(5.0), abs=0.01)
513
+
514
+
515
+ def test_PoseEstimationRotated() -> None:
516
+ robotToCamera = Transform3d(
517
+ Translation3d(6.0 * 0.0254, 6.0 * 0.0254, 6.0 * 0.0254),
518
+ Rotation3d(0.0, math.radians(-30.0), math.radians(25.5)),
519
+ )
520
+
521
+ visionSysSim = VisionSystemSim("Test")
522
+ camera = PhotonCamera("camera")
523
+ cameraSim = PhotonCameraSim(camera)
524
+ visionSysSim.addCamera(cameraSim, robotToCamera)
525
+
526
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(90.0))
527
+ cameraSim.setMinTargetAreaPixels(20.0)
528
+
529
+ tagList: list[AprilTag] = []
530
+ at0 = AprilTag()
531
+ at0.ID = 0
532
+ at0.pose = Pose3d(12.0, 3.0, 1.0, Rotation3d(0.0, 0.0, math.pi))
533
+ tagList.append(at0)
534
+ at1 = AprilTag()
535
+ at1.ID = 1
536
+ at1.pose = Pose3d(12.0, 1.0, -1.0, Rotation3d(0.0, 0.0, math.pi))
537
+ tagList.append(at1)
538
+ at2 = AprilTag()
539
+ at2.ID = 2
540
+ at2.pose = Pose3d(11.0, 0.0, 2.0, Rotation3d(0.0, 0.0, math.pi))
541
+ tagList.append(at2)
542
+
543
+ fieldLength: meters = 54.0
544
+ fieldWidth: meters = 27.0
545
+ layout = AprilTagFieldLayout(tagList, fieldLength, fieldWidth)
546
+ robotPose = Pose2d(Translation2d(5.0, 1.0), Rotation2d.fromDegrees(-5.0))
547
+ visionSysSim.addVisionTargets(
548
+ [VisionTargetSim(tagList[0].pose, TargetModel.AprilTag36h11(), 0)]
549
+ )
550
+
551
+ visionSysSim.update(robotPose)
552
+
553
+ camEigen = cameraSim.prop.getIntrinsics()
554
+ distEigen = cameraSim.prop.getDistCoeffs()
555
+
556
+ camResults = camera.getLatestResult()
557
+ targets = camResults.getTargets()
558
+ results = VisionEstimation.estimateCamPosePNP(
559
+ camEigen, distEigen, targets, layout, TargetModel.AprilTag36h11()
560
+ )
561
+ assert results is not None
562
+ pose: Pose3d = Pose3d() + results.best
563
+ pose = pose.transformBy(robotToCamera.inverse())
564
+ assert pose.X() == pytest.approx(5.0, abs=0.01)
565
+ assert pose.Y() == pytest.approx(1.0, abs=0.01)
566
+ assert pose.Z() == pytest.approx(0.0, abs=0.01)
567
+ assert pose.rotation().Z() == pytest.approx(math.radians(-5.0), abs=0.01)
568
+
569
+ visionSysSim.addVisionTargets(
570
+ [VisionTargetSim(tagList[1].pose, TargetModel.AprilTag36h11(), 1)]
571
+ )
572
+ visionSysSim.addVisionTargets(
573
+ [VisionTargetSim(tagList[2].pose, TargetModel.AprilTag36h11(), 2)]
574
+ )
575
+ visionSysSim.update(robotPose)
576
+
577
+ camResults2 = camera.getLatestResult()
578
+ targets2 = camResults2.getTargets()
579
+ results2 = VisionEstimation.estimateCamPosePNP(
580
+ camEigen, distEigen, targets2, layout, TargetModel.AprilTag36h11()
581
+ )
582
+ assert results2 is not None
583
+ pose2 = Pose3d() + results2.best
584
+ pose2 = pose2.transformBy(robotToCamera.inverse())
585
+
586
+ assert pose2.X() == pytest.approx(robotPose.X(), abs=0.01)
587
+ assert pose2.Y() == pytest.approx(robotPose.Y(), abs=0.01)
588
+ assert pose2.Z() == pytest.approx(0.0, abs=0.01)
589
+ assert pose2.rotation().Z() == pytest.approx(math.radians(-5.0), abs=0.01)
590
+
591
+
592
+ def test_TagAmbiguity() -> None:
593
+ visionSysSim = VisionSystemSim("Test")
594
+ camera = PhotonCamera("camera")
595
+ cameraSim = PhotonCameraSim(camera)
596
+ visionSysSim.addCamera(cameraSim, Transform3d())
597
+ cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
598
+ cameraSim.setMinTargetAreaPixels(20.0)
599
+
600
+ targetPose = Pose3d(Translation3d(2.0, 0.0, 0.0), Rotation3d(0, 0, math.pi))
601
+ visionSysSim.addVisionTargets(
602
+ [VisionTargetSim(targetPose, TargetModel.AprilTag36h11(), 3)]
603
+ )
604
+
605
+ robotPose = Pose2d()
606
+ visionSysSim.update(robotPose)
607
+ tgt = camera.getLatestResult().getBestTarget()
608
+ assert tgt is not None
609
+ ambiguity = tgt.getPoseAmbiguity()
610
+ assert ambiguity > 0.5, "Tag ambiguity expected to be high"
611
+
612
+ robotPose = Pose2d(Translation2d(-2.0, -2.0), Rotation2d.fromDegrees(30.0))
613
+ visionSysSim.update(robotPose)
614
+ tgt = camera.getLatestResult().getBestTarget()
615
+ assert tgt is not None
616
+ ambiguity = tgt.getPoseAmbiguity()
617
+ assert 0 < ambiguity < 0.2, "Tag ambiguity expected to be high"