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,465 @@
1
+ import os, sys
2
+ import time
3
+ import numpy as np
4
+ import tkinter as tk
5
+ sys.path.append('.')
6
+ import c4dynamics as c4d
7
+ from typing import Optional, Union, List
8
+ import warnings
9
+
10
+ def animate(rb, modelpath: str, angle0: list = [0, 0, 0]
11
+ , modelcolor: Union[np.ndarray, List[float], tuple, None] = None, dt: float = 1e-3
12
+ , savedir: Optional[str] = None, cbackground: Union[np.ndarray, List[float], tuple] = [1, 1, 1]):
13
+ '''
14
+ Animate a rigidbody.
15
+
16
+ Animates the rigid body's motion using a 3D model
17
+ according to the 3-2-1 Euler angles histories.
18
+
19
+ **Important Note**
20
+
21
+ Using the `animate` function requires installation of `Open3D` which is not a prerequisite of `C4dynamics`.
22
+ For the different ways to install `Open3D` please refer to its `official website <https://www.open3d.org/>`_.
23
+ A direct installation with pip:
24
+
25
+ ::
26
+
27
+ pip install open3d
28
+
29
+
30
+ Parameters
31
+ ----------
32
+
33
+ modelpath : str
34
+ A path to a single file model or a path to a folder containing multiple
35
+ model files.
36
+ If the provided path is of a folder, only model files should exist in it.
37
+ Typically supported files for mesh models are .obj, .stl, .ply.
38
+ Supported point cloud file is .pcd.
39
+ If your point cloud file has .ply extension, convert it to a .pcd first.
40
+ You may do that by using `Open3D`, see note below.
41
+
42
+ angle0 : array_like, optional
43
+ Initial Euler angles :math:`[\\varphi, \\theta, \\psi]`, in radians, representing
44
+ the model attitude with respect to the screen frame, see note below.
45
+
46
+ modelcolor : array_like, optional
47
+ Colors array [R, G, B] of values between 0 and 1.
48
+ The shape of `modelcolor` is `mx3`, where m is either 1 or as
49
+ the number of the model files.
50
+ If `m > 1`, then the order of the colors in the array
51
+ should match the alphabetical order of the files in `modelpath`.
52
+
53
+ dt : float, optional
54
+ Time step between two frames for the animation.
55
+ Default is 1msec.
56
+
57
+ savedir : str, optional
58
+ If provided, saves each frame as an image in the specified directory.
59
+ Default is None.
60
+
61
+
62
+ Note
63
+ ----
64
+ - Currently, only 321 Euler order of rotation is supported.
65
+ Therefore if the stored angular state is produced by
66
+ using other set of Euler angles, they have to be converted to a 321 set first.
67
+ - If the provided path is of a folder, only model files should exist in it.
68
+ Typically supported files for mesh models are .obj, .stl, .ply.
69
+ Supported point cloud file is .pcd.
70
+ If your point cloud file has .ply extension, convert it to a .pcd first.
71
+ You may do that by using `Open3D`:
72
+
73
+ .. code::
74
+
75
+ >>> import open3d as o3d
76
+ >>> pcd = o3d.io.read_point_cloud('model.ply') # doctest: +IGNORE_OUTPUT
77
+ >>> o3d.io.write_point_cloud('model.pcd', pcd) # doctest: +IGNORE_OUTPUT
78
+
79
+ For more info see `Open3D documentation <https://www.open3d.org/docs/release/tutorial/geometry/file_io.html>`_
80
+ - Initial Euler angles :math:`[\\varphi, \\theta, \\psi]`, in radians, representing
81
+ the model attitude with respect to the screen frame, see note below.
82
+ The screen frame is defined as follows:
83
+
84
+ ::
85
+
86
+ x: right
87
+ y: up
88
+ z: outside
89
+
90
+ Default attitude [0, 0, 0].
91
+
92
+ Examples
93
+ --------
94
+
95
+ The 3D models used in the following examples can be downloaded and
96
+ fetched by c4dynamics' datasets:
97
+
98
+ .. code::
99
+
100
+ >>> import c4dynamics as c4d
101
+
102
+ .. code::
103
+
104
+ >>> bunnypath = c4d.datasets.d3_model('bunny') # 'bunny.pcd': point cloud data file
105
+ Fetched successfully
106
+ >>> bunnymesh_path = c4d.datasets.d3_model('bunnymesh') # 'bunny_mesh.ply': polygon file
107
+ Fetched successfully
108
+ >>> f16path = c4d.datasets.d3_model('f16') # folder of 10 stl files.
109
+ Fetched successfully
110
+
111
+ For more details, refer to :mod:`c4dynamics.datasets`.
112
+
113
+
114
+
115
+ **Animate Stanford bunny**
116
+
117
+
118
+ 1. The Stanford bunny is a computer graphics 3D test model
119
+ developed by Greg Turk and Marc Levoy in 1994 at Stanford University.
120
+ The model consists of 69,451 triangles, with the data determined by
121
+ 3D scanning a ceramic figurine of a rabbit. For more details, refer to
122
+ `The Stanford 3D Scanning Repository <https://graphics.stanford.edu/data/3Dscanrep/#bunny>`_
123
+
124
+
125
+
126
+ .. code::
127
+
128
+ >>> bunny = c4d.rigidbody()
129
+ >>> # generate an arbitrary attitude motion
130
+ >>> dt = 0.01
131
+ >>> T = 5
132
+ >>> for t in np.arange(0, T, dt):
133
+ ... bunny.psi += dt * 360 * c4d.d2r / T
134
+ ... bunny.store(t)
135
+ >>> bunny.animate(bunnypath, cbackground = [0, 0, 0])
136
+
137
+ .. figure:: /_examples/animate/bunny.gif
138
+
139
+
140
+ 2. You can change the model's color by setting the `modelcolor` parameter.
141
+ Here is an example of a mesh version of Stanford bunny with a custom color:
142
+
143
+ .. code::
144
+
145
+ >>> bunny.animate(bunnymesh_path, cbackground = [0, 0, 0], modelcolor = [1, 0, .5])
146
+
147
+ .. figure:: /_examples/animate/bunny_red.gif
148
+
149
+
150
+ **Motion of a dynamic system**
151
+
152
+
153
+ 3. An F16 has the following Euler angles:
154
+
155
+ .. code::
156
+
157
+ >>> f16 = c4d.rigidbody()
158
+ >>> dt = 0.01
159
+ >>> for t in np.arange(0, 9, dt):
160
+ ... if t < 3:
161
+ ... f16.psi += dt * 180 * c4d.d2r / 3
162
+ ... elif t < 6:
163
+ ... f16.theta += dt * 180 * c4d.d2r / 3
164
+ ... else:
165
+ ... f16.phi -= dt * 180 * c4d.d2r / 3
166
+ ... f16.store(t)
167
+
168
+ .. figure:: /_examples/animate/f16_eulers.png
169
+
170
+ The jet model is consisted of multiple files, therefore the `f16` rigidbody object
171
+ that was simulated with the above motion is provided with a path to the consisting folder.
172
+
173
+ .. code::
174
+
175
+ >>> f16.animate(f16path)
176
+
177
+ .. figure:: /_examples/animate/f16.gif
178
+
179
+
180
+ 4. It's obvious that the animated model doesn't follow the required rotation as simulated above.
181
+ This because the model initial postion isn't aligned with the screen frame.
182
+ To align the aircraft body frame which defined as:
183
+
184
+ ::
185
+
186
+ x: centerline
187
+ z: perpendicular to x, downward
188
+ y: completes the right-hand coordinate system
189
+
190
+ With the screen frame which defined as:
191
+
192
+ ::
193
+
194
+ x: rightward
195
+ y: upward
196
+ z: outside the screen
197
+
198
+ We should examine a frame of the model before any rotation.
199
+ It can be achieved by using `Open3D`:
200
+
201
+
202
+ .. code::
203
+
204
+ >>> import os
205
+ >>> import open3d as o3d
206
+
207
+
208
+ .. code::
209
+
210
+ >>> model = []
211
+ >>> for f in sorted(os.listdir(f16path)):
212
+ ... mfilepath = os.path.join(f16path, f)
213
+ ... m = o3d.io.read_triangle_mesh(mfilepath)
214
+ ... m.compute_vertex_normals() # doctest: +IGNORE_OUTPUT
215
+ ... model.append(m)
216
+ >>> o3d.visualization.draw_geometries(model)
217
+
218
+ .. figure:: /_examples/animate/f16_static.png
219
+
220
+
221
+ It turns out that for 3-2-1 order of rotation (see :mod:`rotmat <c4dynamics.rotmat>`)
222
+ the body frame with respect to the screen frame is given by:
223
+
224
+ - Rotation of `180deg` about `y` (up the screen)
225
+
226
+ - Rotation of `90deg` about `x` (right the screen)
227
+
228
+ Let's re-run with the correct initial conditions:
229
+
230
+ .. code::
231
+
232
+ >>> x0 = [90 * c4d.d2r, 0, 180 * c4d.d2r]
233
+ >>> f16.animate(f16path, angle0 = x0)
234
+
235
+ .. figure:: /_examples/animate/f16_IC.gif
236
+
237
+
238
+ 5. The attitude is correct but the the model is colorless.
239
+ Let's give it some color;
240
+ We sort the colors by the jet's parts alphabetically as it
241
+ assigns the values according to the order of an alphabetical
242
+ reading of the files in the folder.
243
+ Finally convert it to a list.
244
+
245
+ .. code::
246
+
247
+ >>> f16colors = list({'Aileron_A_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
248
+ ... , 'Aileron_B_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
249
+ ... , 'Body_F16': [0.8, 0.8, 0.8]
250
+ ... , 'Cockpit_F16': [0.1, 0.1, 0.1]
251
+ ... , 'LE_Slat_A_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
252
+ ... , 'LE_Slat_B_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
253
+ ... , 'Rudder_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
254
+ ... , 'Stabilator_A_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
255
+ ... , 'Stabilator_B_F16': [0.3 * 0.8, 0.3 * 0.8, 0.3 * 1]
256
+ ... }.values())
257
+ >>> f16.animate(f16path, angle0 = x0, modelcolor = f16colors)
258
+
259
+ .. figure:: /_examples/animate/f16_color.gif
260
+
261
+
262
+ 6. It can also be painted with a single color for all its parts and a single color for the background:
263
+
264
+ .. code::
265
+
266
+ >>> f16.animate(f16path, angle0 = x0, modelcolor = [0, 0, 0], cbackground = np.array([230, 230, 255]) / 255)
267
+
268
+ .. figure:: /_examples/animate/f16_monochrome.gif
269
+
270
+
271
+ 7. Finally, let's use the `savedir` option using the c4dynamics' gif util to generate a gif file out of the model animation
272
+
273
+ .. code::
274
+
275
+ >>> f16colors = np.vstack(([255, 215, 0], [255, 215, 0]
276
+ ... , [184, 134, 11], [0, 32, 38]
277
+ ... , [218, 165, 32], [218, 165, 32], [54, 69, 79]
278
+ ... , [205, 149, 12], [205, 149, 12])) / 255
279
+ >>> outfol = os.path.join('tests', '_out', 'f16a')
280
+ >>> f16.animate(f16path, angle0 = x0, savedir = outfol, modelcolor = f16colors)
281
+ >>> # the storage folder 'outfol' is the source of images for the gif function
282
+ >>> # the 'duration' parameter sets the required length of the animation
283
+ >>> gifname = 'f16_animation.gif'
284
+ >>> c4d.gif(outfol, gifname, duration = 1)
285
+
286
+ Viewing the gif on a Jupyter notebook is possible by using the `Image` funtion of the `IPython` module:
287
+
288
+ .. code::
289
+
290
+ >>> import os
291
+ >>> from IPython.display import Image
292
+
293
+ .. code::
294
+
295
+ >>> gifpath = os.path.join(outfol, gifname)
296
+ >>> Image(filename = gifpath) # doctest: +IGNORE_OUTPUT
297
+
298
+ .. figure:: /_examples/animate/f16_color2.gif
299
+
300
+ '''
301
+ try:
302
+ import open3d as o3d
303
+ except ImportError:
304
+ raise ImportError(
305
+ "The 'open3d' package is required for this function to work. "
306
+ "Please install it by running 'pip install open3d'."
307
+ )
308
+
309
+ if not rb._data:
310
+ warnings.warn(f"""No stored data for the given rigidbody.""" , c4d.c4warn)
311
+ return None
312
+
313
+ #
314
+ # load the model
315
+ # get all the files
316
+ # check type
317
+ # read model
318
+ # add color
319
+ ##
320
+
321
+ model = []
322
+
323
+
324
+ if os.path.isfile(modelpath):
325
+ modelpath, files = os.path.split(modelpath)
326
+ files = [files]
327
+
328
+ elif os.path.isdir(modelpath):
329
+ files = os.listdir(modelpath)
330
+ if not files:
331
+ raise FileNotFoundError(f"'{modelpath}' is empty.")
332
+
333
+ files = [file for file in files if os.path.isfile(os.path.join(modelpath, file))]
334
+
335
+ else:
336
+ raise FileNotFoundError(f"'{modelpath}' doesn't exist.")
337
+
338
+ if modelcolor is not None:
339
+ # user input
340
+
341
+ # if not 3 colums array, raise an error:
342
+ modelcolor = np.atleast_2d(modelcolor)
343
+ if modelcolor.shape[1] != 3: # Example condition: if modelcolor is empty
344
+ raise ValueError(f"modelcolor must be three columns array for RGB.")
345
+
346
+ # if one row, duplicate
347
+ if modelcolor.shape[1] != 1 and modelcolor.shape[0] == 1:
348
+ modelcolor = np.atleast_2d(np.repeat(modelcolor, len(files), axis = 0))
349
+
350
+
351
+
352
+
353
+
354
+ for i, f in enumerate(sorted(files)):
355
+
356
+ mfilepath = os.path.join(modelpath, f)
357
+ ismesh = True if any(f[-3:].lower() == s for s in ['stl', 'obj', 'ply']) else False
358
+ imodel = o3d.io.read_triangle_mesh(mfilepath) if ismesh else o3d.io.read_point_cloud(mfilepath)
359
+ if modelcolor is not None: #MAYBE WANTS TO SAY IS NOT NONE
360
+ imodel.paint_uniform_color(modelcolor[i])
361
+ if ismesh:
362
+ imodel.compute_vertex_normals()
363
+
364
+ model.append(imodel)
365
+
366
+
367
+
368
+
369
+
370
+ # Create a dummy tkinter window
371
+ root = tk.Tk()
372
+ # Get the screen width and height
373
+ screen_width = root.winfo_screenwidth()
374
+ screen_height = root.winfo_screenheight()
375
+ # Close the dummy window
376
+ root.destroy()
377
+
378
+ vis = o3d.visualization.Visualizer() # type: ignore
379
+ vis.create_window(window_name = ''
380
+ , width = int(screen_width / 2)
381
+ , height = int(screen_height / 2)
382
+ , left = int(screen_width / 2)
383
+ , top = int(screen_height / 4))
384
+ opt = vis.get_render_option()
385
+ opt.background_color = cbackground # RGB values for black
386
+
387
+ #
388
+ # prepare output folder
389
+ ##
390
+ if savedir and not os.path.exists(savedir):
391
+ os.makedirs(savedir)
392
+
393
+
394
+ #
395
+ # load the angular data
396
+ ##
397
+
398
+ psidata = rb.data('psi')[1]
399
+ thetadata = rb.data('theta')[1]
400
+ phidata = rb.data('phi')[1]
401
+
402
+ for m in model:
403
+ vis.add_geometry(m)
404
+
405
+ # here xscreen = xplane, yscreen = -zplane, and zscreen = yplane
406
+ # then dcm321(phi, theta, psi) => dcm231(phi, -theta, -psi)
407
+ # but now the conversion of intrinsic frame to extrinsic frame is translated as:
408
+ # -> dcm132(phi, -theta, -psi)
409
+
410
+ # rotate the object to its initial attitude
411
+ Robj = c4d.rotmat.rotx(angle0[0]) @ c4d.rotmat.roty(angle0[1]) @ c4d.rotmat.rotz(angle0[2])
412
+
413
+
414
+ for i in range(len(phidata)):
415
+
416
+ # match the inertial axes to the screen axes
417
+ rbi = c4d.rigidbody(phi = -phidata[i] # about x screen
418
+ , theta = psidata[i] # about y screen
419
+ , psi = -thetadata[i]) # about z screen
420
+
421
+ # 321 intrinsic => 132 extrinsic
422
+ Rin = c4d.rotmat.roty(rbi.theta) @ c4d.rotmat.rotz(rbi.psi) @ c4d.rotmat.rotx(rbi.phi)
423
+ DR = Rin @ Robj.T
424
+ Robj = Rin
425
+
426
+ for m in model:
427
+ m.rotate(DR, center = (0, 0, 0))
428
+ vis.update_geometry(m)
429
+
430
+ vis.poll_events()
431
+ vis.update_renderer()
432
+
433
+ time.sleep(dt)
434
+
435
+ if savedir:
436
+ vis.capture_screen_image(os.path.join(savedir, 'animated' + str(i) + '.png'))
437
+
438
+ vis.destroy_window()
439
+
440
+
441
+
442
+ if __name__ == "__main__":
443
+
444
+ import doctest, contextlib
445
+ from c4dynamics import IgnoreOutputChecker, cprint
446
+
447
+ # Register the custom OutputChecker
448
+ doctest.OutputChecker = IgnoreOutputChecker
449
+
450
+ tofile = False
451
+ optionflags = doctest.FAIL_FAST
452
+
453
+ if tofile:
454
+ with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
455
+ with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
456
+ result = doctest.testmod(optionflags = optionflags)
457
+ else:
458
+ result = doctest.testmod(optionflags = optionflags)
459
+
460
+ if result.failed == 0:
461
+ cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
462
+ else:
463
+ print(f"{result.failed}")
464
+
465
+