c4dynamics 2.0.3__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 (50) hide show
  1. c4dynamics/__init__.py +240 -0
  2. c4dynamics/datasets/__init__.py +95 -0
  3. c4dynamics/datasets/_manager.py +596 -0
  4. c4dynamics/datasets/_registry.py +80 -0
  5. c4dynamics/detectors/__init__.py +37 -0
  6. c4dynamics/detectors/yolo3_opencv.py +686 -0
  7. c4dynamics/detectors/yolo3_tf.py +124 -0
  8. c4dynamics/eqm/__init__.py +324 -0
  9. c4dynamics/eqm/derivs.py +212 -0
  10. c4dynamics/eqm/integrate.py +359 -0
  11. c4dynamics/filters/__init__.py +1373 -0
  12. c4dynamics/filters/a.py +48 -0
  13. c4dynamics/filters/ekf.py +320 -0
  14. c4dynamics/filters/kalman.py +725 -0
  15. c4dynamics/filters/kalman_v0.py +1071 -0
  16. c4dynamics/filters/kalman_v1.py +821 -0
  17. c4dynamics/filters/lowpass.py +123 -0
  18. c4dynamics/filters/luenberger.py +97 -0
  19. c4dynamics/rotmat/__init__.py +141 -0
  20. c4dynamics/rotmat/animate.py +465 -0
  21. c4dynamics/rotmat/rotmat.py +351 -0
  22. c4dynamics/sensors/__init__.py +72 -0
  23. c4dynamics/sensors/lineofsight.py +78 -0
  24. c4dynamics/sensors/radar.py +740 -0
  25. c4dynamics/sensors/seeker.py +1030 -0
  26. c4dynamics/states/__init__.py +327 -0
  27. c4dynamics/states/lib/__init__.py +320 -0
  28. c4dynamics/states/lib/datapoint.py +660 -0
  29. c4dynamics/states/lib/pixelpoint.py +776 -0
  30. c4dynamics/states/lib/rigidbody.py +677 -0
  31. c4dynamics/states/state.py +1486 -0
  32. c4dynamics/utils/__init__.py +44 -0
  33. c4dynamics/utils/_struct.py +6 -0
  34. c4dynamics/utils/const.py +130 -0
  35. c4dynamics/utils/cprint.py +80 -0
  36. c4dynamics/utils/gen_gif.py +142 -0
  37. c4dynamics/utils/idx2keys.py +4 -0
  38. c4dynamics/utils/images_loader.py +63 -0
  39. c4dynamics/utils/math.py +136 -0
  40. c4dynamics/utils/plottools.py +140 -0
  41. c4dynamics/utils/plottracks.py +304 -0
  42. c4dynamics/utils/printpts.py +36 -0
  43. c4dynamics/utils/slides_gen.py +64 -0
  44. c4dynamics/utils/tictoc.py +167 -0
  45. c4dynamics/utils/video_gen.py +300 -0
  46. c4dynamics/utils/vidgen.py +182 -0
  47. c4dynamics-2.0.3.dist-info/METADATA +242 -0
  48. c4dynamics-2.0.3.dist-info/RECORD +50 -0
  49. c4dynamics-2.0.3.dist-info/WHEEL +5 -0
  50. c4dynamics-2.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,776 @@
1
+ import numpy as np
2
+ import sys
3
+ sys.path.append('.')
4
+ from c4dynamics.states.state import state
5
+
6
+
7
+ # from enum import Enum
8
+
9
+
10
+ class pixelpoint(state):
11
+ '''
12
+ A data point in a video frame with a bounding box.
13
+
14
+ :class:`pixelpoint` is a data structure for managing
15
+ object detections in tracking missions.
16
+ It provides properties and methods to
17
+ conveniently interact with computer vision modules.
18
+
19
+ The `pixelpoint` state variables are
20
+ the object center and the bounding box:
21
+
22
+ .. math::
23
+
24
+ X = [x, y, w, h]^T
25
+
26
+ - Center pixel, bounding box size.
27
+
28
+
29
+
30
+
31
+ **Arguments**
32
+
33
+ x : float or int
34
+ The x-coordinate of the center of the object.
35
+
36
+ y : float or int
37
+ The y-coordinate of the center of the object.
38
+
39
+ w : float or int
40
+ The width of the bounding box.
41
+
42
+ h : float or int
43
+ The height of the bounding box.
44
+
45
+
46
+ These variables use for streamlined operations through
47
+ the state vector :attr:`X <c4dynamics.states.state.state.X>`.
48
+
49
+ Parameters define the object and are not part of the state.
50
+ For the `pixelpoint` class these are the frame size and the object classification.
51
+
52
+
53
+ Parameters
54
+ ==========
55
+
56
+ fsize : (int, int)
57
+ Frame size in pixels.
58
+
59
+ class_id : str
60
+ Class label associated with the object.
61
+
62
+
63
+ **Construction**
64
+
65
+
66
+ A `pixelpoint` instance is typically created
67
+ when a new object is detected.
68
+
69
+ For a given detection `d` with the following indices:
70
+
71
+ - [0] : x-center, pixels
72
+ - [1] : y-center, pixels
73
+ - [2] : width, pixels
74
+ - [3] : height, pixels
75
+ - [4:end] : probabilities for each class of the list `class_names`
76
+
77
+ and for frames with dimensions `(f_width, f_height)`, the following snippet
78
+ constructs a `pixelpoint` and updates its properties.
79
+
80
+ Import packages:
81
+
82
+ .. code::
83
+
84
+ >>> from c4dynamics import pixelpoint
85
+ >>> import numpy as np
86
+
87
+
88
+ Given a detection with arbitrary values:
89
+
90
+ .. code::
91
+
92
+ >>> d = [50, 50, 15, 25, 0.8, 0.1, 0.0, 0.05, 0.89]
93
+ >>> f_width, f_height = 100, 100
94
+ >>> class_names = ['dog', 'cat', 'horse', 'fox']
95
+
96
+ Initialize a `pixelpoint` instance with the input detection:
97
+
98
+ .. code::
99
+
100
+ >>> pp = pixelpoint(x = d[0], y = d[1], w = d[2], h = d[3])
101
+ >>> pp.fsize = (f_width, f_height)
102
+ >>> pp.class_id = class_names[np.argmax(d[5:])]
103
+
104
+
105
+ See Also
106
+ ========
107
+ .lib
108
+ .state
109
+
110
+ Examples
111
+ ========
112
+
113
+ **Setup and Preliminaries**
114
+
115
+
116
+ Import required packages:
117
+
118
+ .. code::
119
+
120
+ >>> import cv2 # opencv-python
121
+ >>> import numpy as np
122
+ >>> import c4dynamics as c4d
123
+ >>> from matplotlib import pyplot as plt
124
+
125
+
126
+
127
+ Fetch 'planes.png' and 'triangle.png' using the c4dynamics'
128
+ datasets module (see :mod:`c4dynamics.datasets`):
129
+
130
+ .. code::
131
+
132
+ >>> tripath = c4d.datasets.image('triangle')
133
+ Fetched successfully
134
+ >>> planspath = c4d.datasets.image('planes')
135
+ Fetched successfully
136
+
137
+
138
+
139
+ Define two auxiliary functions.
140
+ The first, `tridetect()`, returns bounding boxes of the detected triangles:
141
+
142
+ .. code::
143
+
144
+ >>> def tridetect(img):
145
+ ... _, thresh = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 50, 255, 0)
146
+ ... contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
147
+ ... bbox = []
148
+ ... for contour in contours:
149
+ ... approx = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)
150
+ ... if len(approx) == 3:
151
+ ... bbox.append(cv2.boundingRect(contour))
152
+ ... return bbox
153
+
154
+
155
+ The second function, `ptup`, converts a tuple of two numbers into a formatted string:
156
+
157
+ .. code::
158
+
159
+ >>> def ptup(n):
160
+ ... return '(' + str(n[0]) + ', ' + str(n[1]) + ')'
161
+
162
+
163
+
164
+
165
+
166
+ **Construction from image**
167
+
168
+ .. code::
169
+
170
+ >>> img = cv2.imread(tripath)
171
+ >>> pp = c4d.pixelpoint(x = int(img.shape[1] / 2), y = int(img.shape[0] / 2), w = 100, h = 100)
172
+ >>> pp.fsize = img.shape[:2]
173
+ >>> pp.class_id = 'triangle'
174
+
175
+
176
+
177
+ **Construction from detection**
178
+
179
+ Given a frame with dimensions `(f_width, f_height)` and a detection `d` from an object detector:
180
+
181
+ .. code::
182
+
183
+ >>> pp = c4d.pixelpoint(x = d[0], y = d[1], w = d[2], h = d[3])
184
+ >>> pp.fsize = (f_width, f_height)
185
+ >>> pp.class_id = class_names[np.argmax(d[5:])]
186
+
187
+
188
+
189
+
190
+ **Triangles detection**
191
+
192
+ Run a triangles detector and create a `pixelpoint` object
193
+ per each detected triangle.
194
+ Use :attr:`box <c4dynamics.states.lib.pixelpoint.pixelpoint.box>`
195
+ to draw bounding boxes:
196
+
197
+ .. code::
198
+
199
+ >>> img = cv2.imread(tripath)
200
+ >>> triangles = tridetect(img)
201
+
202
+
203
+ .. code::
204
+
205
+ >>> print('{:^10} | {:^10} | {:^16} | {:^16} | {:^10} | {:^14}'.format('center x', 'center y', 'box top-left', 'box bottom-right', 'class', 'frame size')) # doctest: +IGNORE_OUTPUT
206
+ >>> # iterate over the detected triangles:
207
+ >>> for tri in triangles: # doctest: +IGNORE_OUTPUT
208
+ ... pp = c4d.pixelpoint(x = int(tri[0] + tri[2] / 2), y = int(tri[1] + tri[3] / 2), w = tri[2], h = tri[3])
209
+ ... pp.fsize = img.shape[:2]
210
+ ... pp.class_id = 'triangle'
211
+ ... print('{:^10d} | {:^10d} | {:^16} | {:^16} | {:^10} | {:^14}'.format(pp.x, pp.y, ptup(pp.box[0]), ptup(pp.box[1]), pp.class_id, ptup(pp.fsize)))
212
+ ... cv2.rectangle(img, pp.box[0], pp.box[1], [0, 255, 0], 2)
213
+ center x | center y | box top-left | box bottom-right | class | frame size
214
+ 399 | 274 | (184, 117) | (614, 431) | triangle | (600, 800)
215
+
216
+ .. code::
217
+
218
+ >>> plt.figure() # doctest: +IGNORE_OUTPUT
219
+ >>> plt.axis(False) # doctest: +IGNORE_OUTPUT
220
+ >>> plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
221
+
222
+
223
+ .. figure:: /_examples/pixelpoint/triangle.png
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+ **C4dynamics' YOLOv3 detector**
232
+
233
+ The method :meth:`detect <c4dynamics.detectors.yolo3_opencv.yolov3.detect>`
234
+ of the class :class:`yolov3 <c4dynamics.detectors.yolo3_opencv.yolov3>`
235
+ returns a list of `pixelpoint` for the detected objects in an image.
236
+ Print the output per a detected object and view the final image:
237
+
238
+
239
+ .. code::
240
+
241
+ >>> img = cv2.imread(planspath)
242
+ >>> yolo3 = c4d.detectors.yolov3()
243
+ Fetched successfully
244
+ >>> pts = yolo3.detect(img)
245
+
246
+
247
+ .. code::
248
+
249
+ >>> # prepare for printing properties:
250
+ >>> print('{:^10} | {:^10} | {:^16} | {:^16} | {:^10} | {:^14}'.format('center x', 'center y', 'box top-left', 'box bottom-right', 'class', 'frame size')) # doctest: +IGNORE_OUTPUT
251
+ >>> for p in pts: # doctest: +IGNORE_OUTPUT
252
+ ... print('{:^10d} | {:^10d} | {:^16} | {:^16} | {:^10} | {:^14}'.format(p.x, p.y, ptup(p.box[0]), ptup(p.box[1]), p.class_id, ptup(p.fsize)))
253
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 255, 0], 2)
254
+ ... point = (int((p.box[0][0] + p.box[1][0]) / 2 - 75), p.box[1][1] + 22)
255
+ ... cv2.putText(img, p.class_id, point, cv2.FONT_HERSHEY_SIMPLEX, 1, [0, 255, 0], 2)
256
+ center x | center y | box top-left | box bottom-right | class | frame size
257
+ 615 | 295 | (562, 259) | (668, 331) | aeroplane | (1280, 720)
258
+ 779 | 233 | (720, 199) | (838, 267) | aeroplane | (1280, 720)
259
+ 635 | 189 | (578, 153) | (692, 225) | aeroplane | (1280, 720)
260
+ 793 | 575 | (742, 540) | (844, 610) | aeroplane | (1280, 720)
261
+
262
+
263
+ .. code::
264
+
265
+ >>> plt.figure() # doctest: +IGNORE_OUTPUT
266
+ >>> plt.axis(False) # doctest: +IGNORE_OUTPUT
267
+ >>> plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
268
+
269
+
270
+ .. figure:: /_examples/pixelpoint/yolov3.png
271
+
272
+ '''
273
+ x: float
274
+ y: float
275
+ w: float
276
+ h: float
277
+
278
+ def __init__(self, x = 0, y = 0, w = 0, h = 0):
279
+
280
+
281
+ ppargs = {}
282
+ ppargs.setdefault('x', x)
283
+ ppargs.setdefault('y', y)
284
+ ppargs.setdefault('w', w)
285
+ ppargs.setdefault('h', h)
286
+
287
+
288
+ # super().__init__(x = bbox[0], y = bbox[1], w = bbox[2], h = bbox[3])
289
+ super().__init__(**ppargs)
290
+ # NOTE what's the ultimate state? with velocities or not?
291
+ # they can be added later.
292
+ # but if in anyway they are added, why not to introduce them here?
293
+ #
294
+
295
+ # self._units = 'pixels'
296
+
297
+
298
+ # NOTE still not sure whats the best idea to init of these properties:
299
+ # on the one hand they are not part of he state and shoudlnt be provdied as variables
300
+ # on the other they are necessary for methods like box.
301
+ # also the filters inherit from the state class i.e. they are states
302
+ # nontheless they are initialized not only with the state vairables but also with parameters.
303
+ self._frameheight = None
304
+ self._framewidth = None
305
+ self._class = None
306
+
307
+ # - x (float): X-coordinate of the center of the bounding box in relative coordinates.
308
+ # - y (float): Y-coordinate of the center of the bounding box in relative coordinates.
309
+
310
+
311
+ # self._boxwidth = bbox[2]
312
+ # ''' float; Width of the bounding box in a normalized format
313
+ # (0 = left image edge, 1 = right image edge. '''
314
+
315
+ # self._boxheight = bbox[3]
316
+ # ''' float; Height of the bounding box in a normalized format
317
+ # (0 = upper image edge, 1 = bottom image edge. '''
318
+
319
+
320
+ # self._framewidth = framesize[0]
321
+ # ''' int; Width of the frame in pixels. '''
322
+
323
+ # self._frameheight = framesize[1]
324
+ # ''' int; Height of the frame in pixels. '''
325
+
326
+
327
+ # self.class_id = class_id
328
+ # ''' string; Class label associated with the data point. '''
329
+
330
+
331
+
332
+ # @property
333
+ # def X(self):
334
+ # return super().X.astype(np.int64)
335
+
336
+
337
+ # parameters
338
+
339
+ @property
340
+ def fsize(self):
341
+ '''
342
+
343
+ Gets and sets the frame size.
344
+
345
+ Parameters
346
+ ----------
347
+ fsize : tuple
348
+ Size of the frame in pixels (width, height).
349
+ - (width) int : Frame width in pixels.
350
+ - (height) int : Frame height in pixels.
351
+
352
+
353
+ Returns
354
+ -------
355
+ out : tuple
356
+ A tuple of the frame size in pixels (width, height).
357
+
358
+
359
+ Raises
360
+ ------
361
+ ValueError
362
+ If `fsize` doesn't have exactly two elements, a ValueError is raised.
363
+
364
+ Examples
365
+ --------
366
+ For detailed usage,
367
+ see the examples in the introduction to the :class:`pixelpoint` class.
368
+ '''
369
+
370
+ # TODO any way add even line example to show how to use given the objects defined.
371
+ # for all the other methods too.
372
+ return (self._framewidth, self._frameheight)
373
+
374
+ @fsize.setter
375
+ def fsize(self, fsize):
376
+ if len(fsize) != 2:
377
+ raise ValueError('fsize must have exactly two elements.')
378
+
379
+ self._framewidth = fsize[0]
380
+ self._frameheight = fsize[1]
381
+
382
+
383
+ @property
384
+ def class_id(self):
385
+ '''
386
+ Gets and sets the object classification.
387
+
388
+
389
+ Parameters
390
+ ----------
391
+ class_id : str
392
+ Class label associated with the data point.
393
+
394
+
395
+ Returns
396
+ -------
397
+ out : str
398
+ The current class label associated with the data point.
399
+
400
+
401
+ Raises
402
+ ------
403
+ ValueError
404
+ If `class_id` is not str, a ValueError is raised.
405
+
406
+
407
+ Examples
408
+ --------
409
+ For detailed usage,
410
+ see the examples in the introduction to the :class:`pixelpoint` class.
411
+
412
+ '''
413
+ return self._class
414
+
415
+ @class_id.setter
416
+ def class_id(self, class_id):
417
+ if not isinstance(class_id, str):
418
+ raise TypeError('class_id must be an str object')
419
+
420
+ self._class = class_id
421
+
422
+
423
+
424
+ # properties
425
+
426
+ @property
427
+ def box(self):
428
+ '''
429
+ Returns the box coordinates.
430
+
431
+ The box coordinates are given by:
432
+ `[(x top left, y top left), (x bottom right, y bottom right)]`
433
+
434
+ Returns
435
+ -------
436
+ out : list[tuple]
437
+ List containing two tuples representing
438
+ top-left and bottom-right coordinates (in pixels).
439
+
440
+ Examples
441
+ --------
442
+ For detailed usage,
443
+ see the examples in the introduction to the :class:`pixelpoint` class.
444
+
445
+ '''
446
+ # .. include:: ../../states.lib.pixelpoint.examples.rst
447
+
448
+ # if self._units == 'normalized':
449
+ # if self._frameheight is None or self._framewidth is None:
450
+ # raise ValueError('When ''pixelpoint'' units are ''normalized'', the property ''fsize'' '
451
+ # 'must be set first with the frame width and height.')
452
+
453
+ # x = int(self.x * self._framewidth)
454
+ # y = int(self.y * self._frameheight)
455
+
456
+ # w = self.w * self._framewidth
457
+ # h = self.h * self._frameheight
458
+
459
+ # else: # units = pixels.
460
+
461
+ x = self.x
462
+ y = self.y
463
+
464
+ w = self.w
465
+ h = self.h
466
+
467
+
468
+ # top left
469
+ xtl = int(x - w / 2)
470
+ ytl = int(y - h / 2)
471
+
472
+ # bottom right
473
+ xbr = int(x + w / 2)
474
+ ybr = int(y + h / 2)
475
+
476
+ return [(xtl, ytl), (xbr, ybr)]
477
+
478
+
479
+
480
+ @property
481
+ def Xpixels(self):
482
+ '''
483
+ Returns the state vector in pixel coordinates.
484
+
485
+ When pixelpoint.units are set to `normalized`, the method `Xpixels`
486
+ is used to return the state vector in pixels.
487
+
488
+ Returns
489
+ -------
490
+ out : numpy.int32
491
+ A numpy array of the normalized coordinates :math:`[x, y, v_x, v_y]` transformed
492
+ to pixel coordinates considering the specific dimensions of the image.
493
+
494
+
495
+ Examples
496
+ --------
497
+
498
+ Import required packages:
499
+
500
+ .. code::
501
+
502
+ >>> import c4dynamics as c4d
503
+ >>> import cv2
504
+
505
+
506
+ Settings and initialization:
507
+
508
+ .. code::
509
+
510
+ >>> imgpath = c4d.datasets.image('planes')
511
+ Fetched successfully
512
+ >>> img = cv2.imread(imgpath)
513
+ >>> yolo3 = c4d.detectors.yolov3()
514
+ Fetched successfully
515
+ >>> pts = yolo3.detect(img)
516
+
517
+ Main loop:
518
+
519
+ .. code::
520
+
521
+ >>> print('{:^10} | {:^12} | {:^12} | {:^12} | {:^12}'.format('# object', 'X normalized', 'Y normalized', 'X pixels', 'Y pixels')) # doctest: +IGNORE_OUTPUT
522
+ >>> for i, p in enumerate(pts): # doctest: +IGNORE_OUTPUT
523
+ ... X = p.Xpixels
524
+ ... print('{:^10d} | {:^12.3f} | {:^12.3f} | {:^12d} | {:^12d}'.format(i, p.x, p.y, X[0], X[1]))
525
+ # object | X normalized | Y normalized | X pixels | Y pixels
526
+ 0 | 0.427 | 0.339 | 503 | 232
527
+ 1 | 0.411 | 0.491 | 484 | 336
528
+ 2 | 0.550 | 0.397 | 648 | 272
529
+ 3 | 0.507 | 0.916 | 598 | 627
530
+
531
+ '''
532
+ # TODO complete with full state vector.
533
+
534
+ # superx = super().X
535
+ return np.array([self.x * self._framewidth # x # type: ignore
536
+ , self.y * self._frameheight # y # type: ignore
537
+ , self.w * self._framewidth # w # type: ignore
538
+ , self.h * self._frameheight] # h # type: ignore
539
+ , dtype = np.int32)
540
+
541
+
542
+
543
+ @staticmethod
544
+ def boxcenter(box):
545
+ # XXX seems like useless function and indeed is not in use anywhere.
546
+ '''
547
+
548
+ Calculates the center coordinates of bounding boxes.
549
+
550
+ Given a list of bounding boxes, this static method computes the center
551
+ coordinates for each box.
552
+
553
+
554
+
555
+ Parameters
556
+ ----------
557
+ out : list[box]
558
+ List containing one pixelpoint.box or more. where
559
+ every pixelpoint.box has two tuples
560
+ representing top-left and bottom-right coordinates.
561
+
562
+ Returns
563
+ -------
564
+ out : numpy.ndarray
565
+ An array containing center coordinates for each bounding box in the
566
+ format [[center_x1, center_y1], [center_x2, center_y2], ...].
567
+
568
+ '''
569
+
570
+ return np.array([[(b[0] + b[2]) / 2, (b[1] + b[3]) / 2] for b in box])
571
+
572
+
573
+ @staticmethod
574
+ def video_detections(vidpath, tf = None, storepath = False):
575
+ import c4dynamics as c4d
576
+ import pickle
577
+ import zlib
578
+ import cv2
579
+ import os
580
+
581
+ cap = cv2.VideoCapture(vidpath)
582
+ fps = cap.get(cv2.CAP_PROP_FPS)
583
+ dt = 1 / fps # 1 / frame per second = the length of a single frame
584
+ if tf is None:
585
+ N = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # total frames count
586
+ tf = N * dt
587
+ f_frames = int(tf / dt)
588
+
589
+ yolo3 = c4d.detectors.yolov3()
590
+ yolo3.nms_th = .45
591
+
592
+ detections = {}
593
+
594
+ for _ in range(f_frames + 1):
595
+ print(_)
596
+ ret, frame = cap.read()
597
+ if not ret: break
598
+ crc32 = zlib.crc32(frame.tobytes())
599
+ detections[crc32] = yolo3.detect(frame)
600
+
601
+
602
+ if storepath:
603
+ pklname = os.path.join(storepath, os.path.basename(vidpath)[:-4] + '.pkl') # type: ignore
604
+ with open(pklname, 'wb') as file:
605
+ pickle.dump(detections, file)
606
+ print(f'detections stored at {pklname}')
607
+
608
+
609
+ cap.release()
610
+
611
+ return detections
612
+
613
+
614
+
615
+
616
+ # def set_box_size(self, width, height):
617
+ # # TODO document!
618
+ # '''
619
+ # Sets the box size (box width, box height)
620
+ # without changing the center.
621
+
622
+
623
+ # Parameters
624
+ # ----------
625
+ # b : tuple(width, height)
626
+ # A tuple containing two integers representing width and height (in pixels).
627
+
628
+
629
+ # Note
630
+ # ----
631
+ # This function sets the box width and height without
632
+ # chaning the box center.
633
+ # The center of the box is modified only by
634
+ # direct substitution to the state variables
635
+ # or by setting the state vector (:attr:`X <datapoint.X>`).
636
+
637
+
638
+
639
+ # Examples
640
+ # --------
641
+
642
+ # .. code::
643
+
644
+ # >>> width = 800
645
+ # >>> height = 600
646
+ # >>> radius = 50
647
+ # >>> img = np.zeros((height, width, 3), dtype = np.uint8)
648
+ # >>> cv2.circle(img, (width // 2, height // 2), radius, (255, 0, 0), -1)
649
+ # >>> fdp = c4d.pixelpoint(bbox = (0, 0, 0, 0), class_id = 'ball', framesize = (width, height))
650
+ # >>> fdp.x = 0.5
651
+ # >>> fdp.y = 0.5
652
+ # >>> fdp.set_box_size(2 * radius + 2, 2 * radius + 2)
653
+ # >>> cv2.rectangle(img, fdp.box[0], fdp.box[1], [255, 255, 255], 2)
654
+ # >>> _, ax3 = plt.subplots()
655
+ # >>> ax3.axis('off')
656
+ # >>> ax3.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
657
+
658
+ # .. figure:: /_architecture/images/fdp_setboxsize.png
659
+
660
+
661
+
662
+ # '''
663
+
664
+ # # self._boxwidth = width / self._framewidth
665
+ # # self._boxheight = height / self._frameheight
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
676
+ # class ppunits(Enum):
677
+ # pixels = 'pixels'
678
+ # normalized = 'normalized'
679
+
680
+
681
+ # @property
682
+ # def units(self):
683
+ # '''
684
+ # Gets and sets the image coordinates units.
685
+
686
+ # Select between two modes of units:
687
+ # - pixels (default): the image coordinates range between `[0 : width - 1]` horizontally and `[0 : height - 1]` vertically.
688
+ # - normalized: the image coordinates range between `[0 : 1]` in both axes where `0` represents the top and the left edges and `1` the opposite.
689
+
690
+ # Note
691
+ # ----
692
+ # Setting `units` as 'normalized' must be preceded by setting the frame size by the `fsize` property.
693
+
694
+
695
+ # Parameters
696
+ # ----------
697
+ # units : str
698
+ # Required units.
699
+ # - 'pixels' (default) : image coordinates are `[0 : width - 1]` in the horizontal plane and `[0 : height - 1]` vertical plane.
700
+ # - 'normalized' : coordinates are `[0 : 1]` in the horizontal plane and `[0 : 1]` vertical plane. `0` represents the top and the left edges.
701
+
702
+ # Returns
703
+ # -------
704
+ # out : str
705
+ # Current coordinates units.
706
+
707
+
708
+ # Raises
709
+ # ------
710
+ # ValueError
711
+ # - If `units` is not in 'pixels' or 'normalized', a ValueError is raised.
712
+ # - If `units` is 'normalized' and the `fsize` property is not set, a ValueError is raised.
713
+
714
+ # Example
715
+ # -------
716
+
717
+ # '''
718
+ # return self._units
719
+
720
+ # @units.setter
721
+ # def units(self, units):
722
+ # if not units in pixelpoint.ppunits:
723
+ # raise ValueError(f'Invalid units. Choose from {[value for option in pixelpoint.ppunits]}')
724
+ # if units == 'normalized' and self.fsize is None:
725
+ # raise ValueError(f'`fsize` property must be set before `units = ''normalized''` is selected')
726
+
727
+ # # if self._units == 'normalized' and (self._frameheight is None or self._framewidth is None):
728
+ # # raise ValueError('When pixelpoint units are ''normalized'', the property ''fsize'' '
729
+ # # 'must be set first with the frame width and height.')
730
+
731
+ # #
732
+ # # currently leaving it because it's too complicated to track after the updates
733
+ # # of the state and verify it's normalized. it probably invloves overriding X which
734
+ # # i dont want to do right now.
735
+ # ##
736
+
737
+ # self._units = units
738
+
739
+
740
+ # Note
741
+ # ----
742
+ # The pixelpoint has two modes to represent the state coordinates
743
+ # (:attr:`X <c4dynamics.states.state.state.X>`); pixels (default) and normalized,
744
+ # controlled by the property
745
+ # (:attr:`units <c4dynamics.states.lib.pixelpoint.pixelpoint.units>`).
746
+ # In the `pixels` mode, the coordinates are directly represented by the pixel dimensions.
747
+ # The `normalized` mode represents the image by normalized coordinates,
748
+ # ranging from `0` to `1`, where `0` represents
749
+ # the left or the upper edge, and `1` represents the right or the bottom edge.
750
+
751
+
752
+ if __name__ == "__main__":
753
+
754
+ import doctest, contextlib, os
755
+ from c4dynamics import IgnoreOutputChecker, cprint
756
+
757
+ # Register the custom OutputChecker
758
+ doctest.OutputChecker = IgnoreOutputChecker
759
+
760
+ tofile = False
761
+ optionflags = doctest.FAIL_FAST
762
+
763
+ if tofile:
764
+ with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
765
+ with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
766
+ result = doctest.testmod(optionflags = optionflags)
767
+ else:
768
+ result = doctest.testmod(optionflags = optionflags)
769
+
770
+ if result.failed == 0:
771
+ cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
772
+ else:
773
+ print(f"{result.failed}")
774
+
775
+
776
+