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.
- c4dynamics/__init__.py +240 -0
- c4dynamics/datasets/__init__.py +95 -0
- c4dynamics/datasets/_manager.py +596 -0
- c4dynamics/datasets/_registry.py +80 -0
- c4dynamics/detectors/__init__.py +37 -0
- c4dynamics/detectors/yolo3_opencv.py +686 -0
- c4dynamics/detectors/yolo3_tf.py +124 -0
- c4dynamics/eqm/__init__.py +324 -0
- c4dynamics/eqm/derivs.py +212 -0
- c4dynamics/eqm/integrate.py +359 -0
- c4dynamics/filters/__init__.py +1373 -0
- c4dynamics/filters/a.py +48 -0
- c4dynamics/filters/ekf.py +320 -0
- c4dynamics/filters/kalman.py +725 -0
- c4dynamics/filters/kalman_v0.py +1071 -0
- c4dynamics/filters/kalman_v1.py +821 -0
- c4dynamics/filters/lowpass.py +123 -0
- c4dynamics/filters/luenberger.py +97 -0
- c4dynamics/rotmat/__init__.py +141 -0
- c4dynamics/rotmat/animate.py +465 -0
- c4dynamics/rotmat/rotmat.py +351 -0
- c4dynamics/sensors/__init__.py +72 -0
- c4dynamics/sensors/lineofsight.py +78 -0
- c4dynamics/sensors/radar.py +740 -0
- c4dynamics/sensors/seeker.py +1030 -0
- c4dynamics/states/__init__.py +327 -0
- c4dynamics/states/lib/__init__.py +320 -0
- c4dynamics/states/lib/datapoint.py +660 -0
- c4dynamics/states/lib/pixelpoint.py +776 -0
- c4dynamics/states/lib/rigidbody.py +677 -0
- c4dynamics/states/state.py +1486 -0
- c4dynamics/utils/__init__.py +44 -0
- c4dynamics/utils/_struct.py +6 -0
- c4dynamics/utils/const.py +130 -0
- c4dynamics/utils/cprint.py +80 -0
- c4dynamics/utils/gen_gif.py +142 -0
- c4dynamics/utils/idx2keys.py +4 -0
- c4dynamics/utils/images_loader.py +63 -0
- c4dynamics/utils/math.py +136 -0
- c4dynamics/utils/plottools.py +140 -0
- c4dynamics/utils/plottracks.py +304 -0
- c4dynamics/utils/printpts.py +36 -0
- c4dynamics/utils/slides_gen.py +64 -0
- c4dynamics/utils/tictoc.py +167 -0
- c4dynamics/utils/video_gen.py +300 -0
- c4dynamics/utils/vidgen.py +182 -0
- c4dynamics-2.0.3.dist-info/METADATA +242 -0
- c4dynamics-2.0.3.dist-info/RECORD +50 -0
- c4dynamics-2.0.3.dist-info/WHEEL +5 -0
- 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
|
+
|