photonlibpy 2025.0.0a0__py3-none-any.whl → 2025.0.0b2__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.
Files changed (36) hide show
  1. photonlibpy/__init__.py +2 -2
  2. photonlibpy/estimation/__init__.py +5 -0
  3. photonlibpy/estimation/cameraTargetRelation.py +25 -0
  4. photonlibpy/estimation/openCVHelp.py +200 -0
  5. photonlibpy/estimation/rotTrlTransform3d.py +32 -0
  6. photonlibpy/estimation/targetModel.py +137 -0
  7. photonlibpy/estimation/visionEstimation.py +91 -0
  8. photonlibpy/generated/MultiTargetPNPResultSerde.py +12 -0
  9. photonlibpy/generated/PhotonPipelineMetadataSerde.py +23 -4
  10. photonlibpy/generated/PhotonPipelineResultSerde.py +19 -2
  11. photonlibpy/generated/PhotonTrackedTargetSerde.py +40 -0
  12. photonlibpy/generated/PnpResultSerde.py +19 -0
  13. photonlibpy/generated/TargetCornerSerde.py +12 -0
  14. photonlibpy/generated/__init__.py +0 -1
  15. photonlibpy/networktables/NTTopicSet.py +64 -0
  16. photonlibpy/networktables/__init__.py +1 -0
  17. photonlibpy/packet.py +123 -8
  18. photonlibpy/photonCamera.py +10 -7
  19. photonlibpy/photonPoseEstimator.py +5 -5
  20. photonlibpy/simulation/__init__.py +5 -0
  21. photonlibpy/simulation/photonCameraSim.py +408 -0
  22. photonlibpy/simulation/simCameraProperties.py +661 -0
  23. photonlibpy/simulation/videoSimUtil.py +2 -0
  24. photonlibpy/simulation/visionSystemSim.py +237 -0
  25. photonlibpy/simulation/visionTargetSim.py +50 -0
  26. photonlibpy/targeting/TargetCorner.py +5 -1
  27. photonlibpy/targeting/__init__.py +1 -1
  28. photonlibpy/targeting/multiTargetPNPResult.py +10 -4
  29. photonlibpy/targeting/photonPipelineResult.py +12 -5
  30. photonlibpy/targeting/photonTrackedTarget.py +13 -5
  31. photonlibpy/version.py +2 -2
  32. {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/METADATA +6 -2
  33. photonlibpy-2025.0.0b2.dist-info/RECORD +36 -0
  34. {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/WHEEL +1 -1
  35. photonlibpy-2025.0.0a0.dist-info/RECORD +0 -22
  36. {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,661 @@
1
+ import logging
2
+ import math
3
+ import typing
4
+
5
+ import cv2 as cv
6
+ import numpy as np
7
+ from wpimath.geometry import Rotation2d, Rotation3d, Translation3d
8
+ from wpimath.units import hertz, seconds
9
+
10
+ from ..estimation import RotTrlTransform3d
11
+
12
+
13
+ class SimCameraProperties:
14
+ def __init__(self, path: str | None = None, width: int = 0, height: int = 0):
15
+ self.resWidth: int = -1
16
+ self.resHeight: int = -1
17
+ self.camIntrinsics: np.ndarray = np.zeros((3, 3)) # [3,3]
18
+ self.distCoeffs: np.ndarray = np.zeros((8, 1)) # [8,1]
19
+ self.avgErrorPx: float = 0.0
20
+ self.errorStdDevPx: float = 0.0
21
+ self.frameSpeed: seconds = 0.0
22
+ self.exposureTime: seconds = 0.0
23
+ self.avgLatency: seconds = 0.0
24
+ self.latencyStdDev: seconds = 0.0
25
+ self.viewplanes: list[np.ndarray] = [] # [3,1]
26
+
27
+ if path is None:
28
+ self.setCalibration(960, 720, fovDiag=Rotation2d(math.radians(90.0)))
29
+ else:
30
+ raise Exception("not yet implemented")
31
+
32
+ def setCalibration(
33
+ self,
34
+ width: int,
35
+ height: int,
36
+ *,
37
+ fovDiag: Rotation2d | None = None,
38
+ newCamIntrinsics: np.ndarray | None = None,
39
+ newDistCoeffs: np.ndarray | None = None,
40
+ ):
41
+ # Should be an inverted XOR on the args to differentiate between the signatures
42
+
43
+ has_fov_args = fovDiag is not None
44
+ has_matrix_args = newCamIntrinsics is not None and newDistCoeffs is not None
45
+
46
+ if (has_fov_args and has_matrix_args) or (
47
+ not has_matrix_args and not has_fov_args
48
+ ):
49
+ raise Exception("not a correct function sig")
50
+
51
+ if has_fov_args:
52
+ if fovDiag.degrees() < 1.0 or fovDiag.degrees() > 179.0:
53
+ fovDiag = Rotation2d.fromDegrees(
54
+ max(min(fovDiag.degrees(), 179.0), 1.0)
55
+ )
56
+ logging.error(
57
+ "Requested invalid FOV! Clamping between (1, 179) degrees..."
58
+ )
59
+
60
+ resDiag = math.sqrt(width * width + height * height)
61
+ diagRatio = math.tan(fovDiag.radians() / 2.0)
62
+ fovWidth = Rotation2d(math.atan((diagRatio * (width / resDiag)) * 2))
63
+ fovHeight = Rotation2d(math.atan(diagRatio * (height / resDiag)) * 2)
64
+
65
+ newDistCoeffs = np.zeros((8, 1))
66
+
67
+ cx = width / 2.0 - 0.5
68
+ cy = height / 2.0 - 0.5
69
+
70
+ fx = cx / math.tan(fovWidth.radians() / 2.0)
71
+ fy = cy / math.tan(fovHeight.radians() / 2.0)
72
+
73
+ newCamIntrinsics = np.array([[fx, 0.0, cx], [0.0, fy, cy], [0.0, 0.0, 1.0]])
74
+
75
+ # really convince python we are doing the right thing
76
+ assert newCamIntrinsics is not None
77
+ assert newDistCoeffs is not None
78
+
79
+ self.resWidth = width
80
+ self.resHeight = height
81
+ self.camIntrinsics = newCamIntrinsics
82
+ self.distCoeffs = newDistCoeffs
83
+
84
+ p = [
85
+ Translation3d(
86
+ 1.0,
87
+ Rotation3d(
88
+ 0.0,
89
+ 0.0,
90
+ (self.getPixelYaw(0) + Rotation2d(math.pi / 2.0)).radians(),
91
+ ),
92
+ ),
93
+ Translation3d(
94
+ 1.0,
95
+ Rotation3d(
96
+ 0.0,
97
+ 0.0,
98
+ (self.getPixelYaw(width) + Rotation2d(math.pi / 2.0)).radians(),
99
+ ),
100
+ ),
101
+ Translation3d(
102
+ 1.0,
103
+ Rotation3d(
104
+ 0.0,
105
+ 0.0,
106
+ (self.getPixelPitch(0) + Rotation2d(math.pi / 2.0)).radians(),
107
+ ),
108
+ ),
109
+ Translation3d(
110
+ 1.0,
111
+ Rotation3d(
112
+ 0.0,
113
+ 0.0,
114
+ (self.getPixelPitch(height) + Rotation2d(math.pi / 2.0)).radians(),
115
+ ),
116
+ ),
117
+ ]
118
+
119
+ self.viewplanes = []
120
+
121
+ for i in p:
122
+ self.viewplanes.append(np.array([i.X(), i.Y(), i.Z()]))
123
+
124
+ def setCalibError(self, newAvgErrorPx: float, newErrorStdDevPx: float):
125
+ self.avgErrorPx = newAvgErrorPx
126
+ self.errorStdDevPx = newErrorStdDevPx
127
+
128
+ def setFPS(self, fps: hertz):
129
+ self.frameSpeed = max(1.0 / fps, self.exposureTime)
130
+
131
+ def setExposureTime(self, newExposureTime: seconds):
132
+ self.exposureTime = newExposureTime
133
+ self.frameSpeed = max(self.frameSpeed, self.exposureTime)
134
+
135
+ def setAvgLatency(self, newAvgLatency: seconds):
136
+ self.vgLatency = newAvgLatency
137
+
138
+ def setLatencyStdDev(self, newLatencyStdDev: seconds):
139
+ self.latencyStdDev = newLatencyStdDev
140
+
141
+ def getResWidth(self) -> int:
142
+ return self.resWidth
143
+
144
+ def getResHeight(self) -> int:
145
+ return self.resHeight
146
+
147
+ def getResArea(self) -> int:
148
+ return self.resWidth * self.resHeight
149
+
150
+ def getAspectRatio(self) -> float:
151
+ return 1.0 * self.resWidth / self.resHeight
152
+
153
+ def getIntrinsics(self) -> np.ndarray:
154
+ return self.camIntrinsics
155
+
156
+ def getDistCoeffs(self) -> np.ndarray:
157
+ return self.distCoeffs
158
+
159
+ def getFPS(self) -> hertz:
160
+ return 1.0 / self.frameSpeed
161
+
162
+ def getFrameSpeed(self) -> seconds:
163
+ return self.frameSpeed
164
+
165
+ def getExposureTime(self) -> seconds:
166
+ return self.exposureTime
167
+
168
+ def getAverageLatency(self) -> seconds:
169
+ return self.avgLatency
170
+
171
+ def getLatencyStdDev(self) -> seconds:
172
+ return self.latencyStdDev
173
+
174
+ def getContourAreaPercent(self, points: list[typing.Tuple[float, float]]) -> float:
175
+ return (
176
+ cv.contourArea(cv.convexHull(np.array(points))) / self.getResArea() * 100.0
177
+ )
178
+
179
+ def getPixelYaw(self, pixelX: float) -> Rotation2d:
180
+ fx = self.camIntrinsics[0, 0]
181
+ cx = self.camIntrinsics[0, 2]
182
+ xOffset = cx - pixelX
183
+ return Rotation2d(fx, xOffset)
184
+
185
+ def getPixelPitch(self, pixelY: float) -> Rotation2d:
186
+ fy = self.camIntrinsics[1, 1]
187
+ cy = self.camIntrinsics[1, 2]
188
+ yOffset = cy - pixelY
189
+ return Rotation2d(fy, -yOffset)
190
+
191
+ def getPixelRot(self, point: typing.Tuple[int, int]) -> Rotation3d:
192
+ return Rotation3d(
193
+ 0.0,
194
+ self.getPixelPitch(point[1]).radians(),
195
+ self.getPixelYaw(point[0]).radians(),
196
+ )
197
+
198
+ def getCorrectedPixelRot(self, point: typing.Tuple[float, float]) -> Rotation3d:
199
+ fx = self.camIntrinsics[0, 0]
200
+ cx = self.camIntrinsics[0, 2]
201
+ xOffset = cx - point[0]
202
+
203
+ fy = self.camIntrinsics[1, 1]
204
+ cy = self.camIntrinsics[1, 2]
205
+ yOffset = cy - point[1]
206
+
207
+ yaw = Rotation2d(fx, xOffset)
208
+ pitch = Rotation2d(fy / math.cos(math.atan(xOffset / fx)), -yOffset)
209
+ return Rotation3d(0.0, pitch.radians(), yaw.radians())
210
+
211
+ def getHorizFOV(self) -> Rotation2d:
212
+ left = self.getPixelYaw(0)
213
+ right = self.getPixelYaw(self.resWidth)
214
+ return left - right
215
+
216
+ def getVertFOV(self) -> Rotation2d:
217
+ above = self.getPixelPitch(0)
218
+ below = self.getPixelPitch(self.resHeight)
219
+ return below - above
220
+
221
+ def getDiagFOV(self) -> Rotation2d:
222
+ return Rotation2d(
223
+ math.hypot(self.getHorizFOV().radians(), self.getVertFOV().radians())
224
+ )
225
+
226
+ def getVisibleLine(
227
+ self, camRt: RotTrlTransform3d, a: Translation3d, b: Translation3d
228
+ ) -> typing.Tuple[float | None, float | None]:
229
+ relA = camRt.apply(a)
230
+ relB = camRt.apply(b)
231
+
232
+ if relA.X() <= 0.0 and relB.X() <= 0.0:
233
+ return (None, None)
234
+
235
+ av = np.array([relA.X(), relA.Y(), relA.Z()])
236
+ bv = np.array([relB.X(), relB.Y(), relB.Z()])
237
+ abv = bv - av
238
+
239
+ aVisible = True
240
+ bVisible = True
241
+
242
+ for normal in self.viewplanes:
243
+ aVisibility = av.dot(normal)
244
+ if aVisibility < 0:
245
+ aVisible = False
246
+
247
+ bVisibility = bv.dot(normal)
248
+ if bVisibility < 0:
249
+ bVisible = False
250
+ if aVisibility <= 0 and bVisibility <= 0:
251
+ return (None, None)
252
+
253
+ if aVisible and bVisible:
254
+ return (0.0, 1.0)
255
+
256
+ intersections = [float("nan"), float("nan"), float("nan"), float("nan")]
257
+
258
+ # Optionally 3x1 vector
259
+ ipts: typing.List[np.ndarray | None] = [None, None, None, None]
260
+
261
+ for i, normal in enumerate(self.viewplanes):
262
+ a_projn = (av.dot(normal) / normal.dot(normal)) * normal
263
+
264
+ if abs(abv.dot(normal)) < 1.0e-5:
265
+ continue
266
+ intersections[i] = a_projn.dot(a_projn) / -(abv.dot(a_projn))
267
+
268
+ apv = intersections[i] * abv
269
+ intersectpt = av + apv
270
+ ipts[i] = intersectpt
271
+
272
+ for j in range(1, len(self.viewplanes)):
273
+ if j == 0:
274
+ continue
275
+ oi = (i + j) % len(self.viewplanes)
276
+ onormal = self.viewplanes[oi]
277
+ if intersectpt.dot(onormal) < 0:
278
+ intersections[i] = float("nan")
279
+ ipts[i] = None
280
+ break
281
+
282
+ if not ipts[i]:
283
+ continue
284
+
285
+ for j in range(i - 1, 0 - 1):
286
+ oipt = ipts[j]
287
+ if not oipt:
288
+ continue
289
+
290
+ diff = oipt - intersectpt
291
+ if abs(diff).max() < 1e-4:
292
+ intersections[i] = float("nan")
293
+ ipts[i] = None
294
+ break
295
+
296
+ inter1 = float("nan")
297
+ inter2 = float("nan")
298
+ for inter in intersections:
299
+ if not math.isnan(inter):
300
+ if math.isnan(inter1):
301
+ inter1 = inter
302
+ else:
303
+ inter2 = inter
304
+
305
+ if not math.isnan(inter2):
306
+ max_ = max(inter1, inter2)
307
+ min_ = min(inter1, inter2)
308
+ if aVisible:
309
+ min_ = 0
310
+ if bVisible:
311
+ max_ = 1
312
+ return (min_, max_)
313
+ elif not math.isnan(inter1):
314
+ if aVisible:
315
+ return (0, inter1)
316
+ if bVisible:
317
+ return (inter1, 1)
318
+ return (inter1, None)
319
+ else:
320
+ return (None, None)
321
+
322
+ def estPixelNoise(self, points: np.ndarray) -> np.ndarray:
323
+ assert points.shape[1] == 1, points.shape
324
+ assert points.shape[2] == 2, points.shape
325
+ if self.avgErrorPx == 0 and self.errorStdDevPx == 0:
326
+ return points
327
+
328
+ noisyPts: list[list] = []
329
+ for p in points:
330
+ error = np.random.normal(self.avgErrorPx, self.errorStdDevPx, 1)[0]
331
+ errorAngle = np.random.uniform(-math.pi, math.pi)
332
+ noisyPts.append(
333
+ [
334
+ [
335
+ float(p[0, 0] + error * math.cos(errorAngle)),
336
+ float(p[0, 1] + error * math.sin(errorAngle)),
337
+ ]
338
+ ]
339
+ )
340
+ retval = np.array(noisyPts, dtype=np.float32)
341
+ assert points.shape == retval.shape, retval
342
+ return retval
343
+
344
+ def estLatency(self) -> seconds:
345
+ return max(
346
+ float(np.random.normal(self.avgLatency, self.latencyStdDev, 1)[0]),
347
+ 0.0,
348
+ )
349
+
350
+ def estSecUntilNextFrame(self) -> seconds:
351
+ return self.frameSpeed + max(0.0, self.estLatency() - self.frameSpeed)
352
+
353
+ @classmethod
354
+ def PERFECT_90DEG(cls) -> typing.Self:
355
+ return cls()
356
+
357
+ @classmethod
358
+ def PI4_LIFECAM_320_240(cls) -> typing.Self:
359
+ prop = cls()
360
+ prop.setCalibration(
361
+ 320,
362
+ 240,
363
+ newCamIntrinsics=np.array(
364
+ [
365
+ [328.2733242048587, 0.0, 164.8190261141906],
366
+ [0.0, 318.0609794305216, 123.8633838438093],
367
+ [0.0, 0.0, 1.0],
368
+ ]
369
+ ),
370
+ newDistCoeffs=np.array(
371
+ [
372
+ [
373
+ 0.09957946553445934,
374
+ -0.9166265114485799,
375
+ 0.0019519890627236526,
376
+ -0.0036071725380870333,
377
+ 1.5627234622420942,
378
+ 0,
379
+ 0,
380
+ 0,
381
+ ]
382
+ ]
383
+ ),
384
+ )
385
+ prop.setCalibError(0.21, 0.0124)
386
+ prop.setFPS(30.0)
387
+ prop.setAvgLatency(30.0e-3)
388
+ prop.setLatencyStdDev(10.0e-3)
389
+ return prop
390
+
391
+ @classmethod
392
+ def PI4_LIFECAM_640_480(cls) -> typing.Self:
393
+ prop = cls()
394
+ prop.setCalibration(
395
+ 640,
396
+ 480,
397
+ newCamIntrinsics=np.array(
398
+ [
399
+ [669.1428078983059, 0.0, 322.53377249329213],
400
+ [0.0, 646.9843137061716, 241.26567383784163],
401
+ [0.0, 0.0, 1.0],
402
+ ]
403
+ ),
404
+ newDistCoeffs=np.array(
405
+ [
406
+ [
407
+ 0.12788470750464645,
408
+ -1.2350335805796528,
409
+ 0.0024990767286192732,
410
+ -0.0026958287600230705,
411
+ 2.2951386729115537,
412
+ 0,
413
+ 0,
414
+ 0,
415
+ ]
416
+ ]
417
+ ),
418
+ )
419
+ prop.setCalibError(0.26, 0.046)
420
+ prop.setFPS(15.0)
421
+ prop.setAvgLatency(65.0e-3)
422
+ prop.setLatencyStdDev(15.0e-3)
423
+ return prop
424
+
425
+ @classmethod
426
+ def LL2_640_480(cls) -> typing.Self:
427
+ prop = cls()
428
+ prop.setCalibration(
429
+ 640,
430
+ 480,
431
+ newCamIntrinsics=np.array(
432
+ [
433
+ [511.22843367007755, 0.0, 323.62049380211096],
434
+ [0.0, 514.5452336723849, 261.8827920543568],
435
+ [0.0, 0.0, 1.0],
436
+ ]
437
+ ),
438
+ newDistCoeffs=np.array(
439
+ [
440
+ [
441
+ 0.1917469998873756,
442
+ -0.5142936883324216,
443
+ 0.012461562046896614,
444
+ 0.0014084973492408186,
445
+ 0.35160648971214437,
446
+ 0,
447
+ 0,
448
+ 0,
449
+ ]
450
+ ]
451
+ ),
452
+ )
453
+ prop.setCalibError(0.25, 0.05)
454
+ prop.setFPS(15.0)
455
+ prop.setAvgLatency(35.0e-3)
456
+ prop.setLatencyStdDev(8.0e-3)
457
+ return prop
458
+
459
+ @classmethod
460
+ def LL2_960_720(cls) -> typing.Self:
461
+ prop = cls()
462
+ prop.setCalibration(
463
+ 960,
464
+ 720,
465
+ newCamIntrinsics=np.array(
466
+ [
467
+ [769.6873145148892, 0.0, 486.1096609458122],
468
+ [0.0, 773.8164483705323, 384.66071662358354],
469
+ [0.0, 0.0, 1.0],
470
+ ]
471
+ ),
472
+ newDistCoeffs=np.array(
473
+ [
474
+ [
475
+ 0.189462064814501,
476
+ -0.49903003669627627,
477
+ 0.007468423590519429,
478
+ 0.002496885298683693,
479
+ 0.3443122090208624,
480
+ 0,
481
+ 0,
482
+ 0,
483
+ ]
484
+ ]
485
+ ),
486
+ )
487
+ prop.setCalibError(0.35, 0.10)
488
+ prop.setFPS(10.0)
489
+ prop.setAvgLatency(50.0e-3)
490
+ prop.setLatencyStdDev(15.0e-3)
491
+ return prop
492
+
493
+ @classmethod
494
+ def LL2_1280_720(cls) -> typing.Self:
495
+ prop = cls()
496
+ prop.setCalibration(
497
+ 1280,
498
+ 720,
499
+ newCamIntrinsics=np.array(
500
+ [
501
+ [1011.3749416937393, 0.0, 645.4955139388737],
502
+ [0.0, 1008.5391755084075, 508.32877656020196],
503
+ [0.0, 0.0, 1.0],
504
+ ]
505
+ ),
506
+ newDistCoeffs=np.array(
507
+ [
508
+ [
509
+ 0.13730101577061535,
510
+ -0.2904345656989261,
511
+ 8.32475714507539e-4,
512
+ -3.694397782014239e-4,
513
+ 0.09487962227027584,
514
+ 0,
515
+ 0,
516
+ 0,
517
+ ]
518
+ ]
519
+ ),
520
+ )
521
+ prop.setCalibError(0.37, 0.06)
522
+ prop.setFPS(7.0)
523
+ prop.setAvgLatency(60.0e-3)
524
+ prop.setLatencyStdDev(20.0e-3)
525
+ return prop
526
+
527
+ @classmethod
528
+ def OV9281_640_480(cls) -> typing.Self:
529
+ prop = cls()
530
+ prop.setCalibration(
531
+ 640,
532
+ 480,
533
+ newCamIntrinsics=np.array(
534
+ [
535
+ [627.1573807284262, 0, 307.79423851611824],
536
+ [0, 626.6621595938243, 219.02625533911998],
537
+ [0, 0, 1],
538
+ ]
539
+ ),
540
+ newDistCoeffs=np.array(
541
+ [
542
+ [
543
+ 0.054834081023049625,
544
+ -0.15994111706817074,
545
+ -0.0017587106009926158,
546
+ -0.0014671022483263552,
547
+ 0.049742166267499596,
548
+ 0,
549
+ 0,
550
+ 0,
551
+ ],
552
+ ]
553
+ ),
554
+ )
555
+ prop.setCalibError(0.25, 0.05)
556
+ prop.setFPS(30.0)
557
+ prop.setAvgLatency(60.0e-3)
558
+ prop.setLatencyStdDev(20.0e-3)
559
+ return prop
560
+
561
+ @classmethod
562
+ def OV9281_800_600(cls) -> typing.Self:
563
+ prop = cls()
564
+ prop.setCalibration(
565
+ 800,
566
+ 600,
567
+ newCamIntrinsics=np.array(
568
+ [
569
+ [783.9467259105329, 0, 384.7427981451478],
570
+ [0, 783.3276994922804, 273.7828191739],
571
+ [0, 0, 1],
572
+ ]
573
+ ),
574
+ newDistCoeffs=np.array(
575
+ [
576
+ [
577
+ 0.054834081023049625,
578
+ -0.15994111706817074,
579
+ -0.0017587106009926158,
580
+ -0.0014671022483263552,
581
+ 0.049742166267499596,
582
+ 0,
583
+ 0,
584
+ 0,
585
+ ],
586
+ ]
587
+ ),
588
+ )
589
+ prop.setCalibError(0.25, 0.05)
590
+ prop.setFPS(25.0)
591
+ prop.setAvgLatency(60.0e-3)
592
+ prop.setLatencyStdDev(20.0e-3)
593
+ return prop
594
+
595
+ @classmethod
596
+ def OV9281_1280_720(cls) -> typing.Self:
597
+ prop = cls()
598
+ prop.setCalibration(
599
+ 1280,
600
+ 720,
601
+ newCamIntrinsics=np.array(
602
+ [
603
+ [940.7360710926395, 0, 615.5884770322365],
604
+ [0, 939.9932393907364, 328.53938300868],
605
+ [0, 0, 1],
606
+ ]
607
+ ),
608
+ newDistCoeffs=np.array(
609
+ [
610
+ [
611
+ 0.054834081023049625,
612
+ -0.15994111706817074,
613
+ -0.0017587106009926158,
614
+ -0.0014671022483263552,
615
+ 0.049742166267499596,
616
+ 0,
617
+ 0,
618
+ 0,
619
+ ],
620
+ ]
621
+ ),
622
+ )
623
+ prop.setCalibError(0.25, 0.05)
624
+ prop.setFPS(15.0)
625
+ prop.setAvgLatency(60.0e-3)
626
+ prop.setLatencyStdDev(20.0e-3)
627
+ return prop
628
+
629
+ @classmethod
630
+ def OV9281_1920_1080(cls) -> typing.Self:
631
+ prop = cls()
632
+ prop.setCalibration(
633
+ 1920,
634
+ 1080,
635
+ newCamIntrinsics=np.array(
636
+ [
637
+ [1411.1041066389591, 0, 923.3827155483548],
638
+ [0, 1409.9898590861046, 492.80907451301994],
639
+ [0, 0, 1],
640
+ ]
641
+ ),
642
+ newDistCoeffs=np.array(
643
+ [
644
+ [
645
+ 0.054834081023049625,
646
+ -0.15994111706817074,
647
+ -0.0017587106009926158,
648
+ -0.0014671022483263552,
649
+ 0.049742166267499596,
650
+ 0,
651
+ 0,
652
+ 0,
653
+ ],
654
+ ]
655
+ ),
656
+ )
657
+ prop.setCalibError(0.25, 0.05)
658
+ prop.setFPS(10.0)
659
+ prop.setAvgLatency(60.0e-3)
660
+ prop.setLatencyStdDev(20.0e-3)
661
+ return prop
@@ -0,0 +1,2 @@
1
+ class VideoSimUtil:
2
+ pass