nrl-tracker 1.11.1__py3-none-any.whl → 1.12.0__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.
- {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/METADATA +4 -4
- {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/RECORD +30 -30
- pytcl/__init__.py +2 -2
- pytcl/assignment_algorithms/network_flow.py +172 -60
- pytcl/astronomical/time_systems.py +21 -0
- pytcl/containers/cluster_set.py +36 -0
- pytcl/coordinate_systems/conversions/geodetic.py +58 -0
- pytcl/core/array_utils.py +52 -0
- pytcl/gpu/ekf.py +46 -0
- pytcl/gpu/kalman.py +16 -0
- pytcl/gpu/matrix_utils.py +44 -1
- pytcl/gpu/particle_filter.py +33 -0
- pytcl/gpu/ukf.py +31 -0
- pytcl/gpu/utils.py +15 -0
- pytcl/magnetism/igrf.py +72 -0
- pytcl/magnetism/wmm.py +52 -0
- pytcl/mathematical_functions/basic_matrix/decompositions.py +7 -0
- pytcl/mathematical_functions/basic_matrix/special_matrices.py +31 -0
- pytcl/mathematical_functions/geometry/geometry.py +33 -0
- pytcl/mathematical_functions/interpolation/interpolation.py +83 -0
- pytcl/mathematical_functions/signal_processing/detection.py +31 -0
- pytcl/mathematical_functions/signal_processing/filters.py +56 -0
- pytcl/mathematical_functions/signal_processing/matched_filter.py +32 -1
- pytcl/mathematical_functions/special_functions/hypergeometric.py +17 -0
- pytcl/mathematical_functions/statistics/estimators.py +71 -0
- pytcl/mathematical_functions/transforms/wavelets.py +25 -0
- pytcl/navigation/great_circle.py +33 -0
- {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Python port of the U.S. Naval Research Laboratory's Tracker Component Library for target tracking algorithms
|
|
5
5
|
Author: Original: David F. Crouse, Naval Research Laboratory
|
|
6
6
|
Maintainer: Python Port Contributors
|
|
@@ -71,17 +71,17 @@ Requires-Dist: plotly>=5.15.0; extra == "visualization"
|
|
|
71
71
|
|
|
72
72
|
# Tracker Component Library (Python)
|
|
73
73
|
|
|
74
|
-
[](https://pypi.org/project/nrl-tracker/)
|
|
75
75
|
[](https://www.python.org/downloads/)
|
|
76
76
|
[](https://en.wikipedia.org/wiki/Public_domain)
|
|
77
77
|
[](https://github.com/psf/black)
|
|
78
|
-
[](https://github.com/nedonatelli/TCL)
|
|
79
79
|
[](docs/gap_analysis.rst)
|
|
80
80
|
[](mypy.ini)
|
|
81
81
|
|
|
82
82
|
A Python port of the [U.S. Naval Research Laboratory's Tracker Component Library](https://github.com/USNavalResearchLaboratory/TrackerComponentLibrary), a comprehensive collection of algorithms for target tracking, estimation, coordinate systems, and related mathematical functions.
|
|
83
83
|
|
|
84
|
-
**1,070+ functions** | **153 modules** | **
|
|
84
|
+
**1,070+ functions** | **153 modules** | **3,280 tests** | **100% MATLAB parity**
|
|
85
85
|
|
|
86
86
|
## Overview
|
|
87
87
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pytcl/__init__.py,sha256=
|
|
1
|
+
pytcl/__init__.py,sha256=d_xdJJ89h0N88qtQ41ZNqKLNj4tEoOjYoA7HQu55syk,2032
|
|
2
2
|
pytcl/logging_config.py,sha256=UJaYufQgNuIjpsOMTPo3ewz1XCHPk8a08jTHyP7uoI4,8956
|
|
3
3
|
pytcl/assignment_algorithms/__init__.py,sha256=kUWhmyLhZcs5GiUQA5_v7KA3qETGsvqV6wU8r7paO-k,2976
|
|
4
4
|
pytcl/assignment_algorithms/data_association.py,sha256=tsRxWJZk9aAPmE99BKXGouEpFfZrjPjb4HXvgxFUHhU,11405
|
|
@@ -6,7 +6,7 @@ pytcl/assignment_algorithms/dijkstra_min_cost.py,sha256=z-Wk1HXRNKieBsRFqR8_UB8Q
|
|
|
6
6
|
pytcl/assignment_algorithms/gating.py,sha256=JaRaFcFqjfdsTbbTP6k_GY2zemDSR02l5yInWHpb05Y,11439
|
|
7
7
|
pytcl/assignment_algorithms/jpda.py,sha256=rOY_v1vesL6EJySwD0kRDTfe7wHoDFLITg_lJLM-bX4,21731
|
|
8
8
|
pytcl/assignment_algorithms/nd_assignment.py,sha256=bcSNm3xSEjAg8gFb_TLQovpsLjNwvI5OOlh2y8XG4M0,24571
|
|
9
|
-
pytcl/assignment_algorithms/network_flow.py,sha256
|
|
9
|
+
pytcl/assignment_algorithms/network_flow.py,sha256=-9tQY-kBrjsTHqv23rBD0-iebVvtAsK_a605Qn0_9SM,18619
|
|
10
10
|
pytcl/assignment_algorithms/network_simplex.py,sha256=Qi10PsIYcTc6MZ-9GPl6ivaLaGA9F5-B7ltBbmasRNM,5566
|
|
11
11
|
pytcl/assignment_algorithms/three_dimensional/__init__.py,sha256=1Q40OUlUQoo7YKEucwdrSNo3D4A0Zibvkr8z4TpueBg,526
|
|
12
12
|
pytcl/assignment_algorithms/three_dimensional/assignment.py,sha256=OGcjg3Yr1tYriWYBJ5k6jiRMpOHDISK8FJDY0nTQxxw,19244
|
|
@@ -21,7 +21,7 @@ pytcl/astronomical/reference_frames.py,sha256=MBqprzBpEvdq3ngRL-_pp-Vnj7AqbuXhjU
|
|
|
21
21
|
pytcl/astronomical/relativity.py,sha256=vSay4am_ElkiGRwuu_4rwo5C10eN8hm86jy43xmaEt4,15933
|
|
22
22
|
pytcl/astronomical/sgp4.py,sha256=iNZrqMRUzR-LFeZiluzlNmkwxeYbIyF2F1cygyeEZVE,21546
|
|
23
23
|
pytcl/astronomical/special_orbits.py,sha256=N54c_wAD7XKk_diDOw2QjUSkmYECMyWQDq2P6EeEBEI,12745
|
|
24
|
-
pytcl/astronomical/time_systems.py,sha256=
|
|
24
|
+
pytcl/astronomical/time_systems.py,sha256=1r1DDRrMlX4eAe1d3quffIaTqIvaozmynza76aSfmrE,15615
|
|
25
25
|
pytcl/astronomical/tle.py,sha256=t3e2-0f3Wiz77q-pC2jfpohkrDfoYOEHacpNgWMNLAk,14638
|
|
26
26
|
pytcl/atmosphere/__init__.py,sha256=Joa6PBEfKun0Moii6BOzyVYG8AOFyvucKdVuY62ArQc,1685
|
|
27
27
|
pytcl/atmosphere/ionosphere.py,sha256=tt_Qu1IvqZxah2P_QaWxpwroSgtDNU-6RrqtxMRZScY,16092
|
|
@@ -34,7 +34,7 @@ pytcl/clustering/hierarchical.py,sha256=MDBIcJtZHYruSsM_seAR9bNWKB63EDh1nIf6qAn4
|
|
|
34
34
|
pytcl/clustering/kmeans.py,sha256=_r08KVvqHL1UnYE4rHBAJ1OKA97q-PWl2OOsTi6kKqk,10963
|
|
35
35
|
pytcl/containers/__init__.py,sha256=i-o_KDjhbPWc9yAlgN1R5igmyWK3CVKIB-V7Qnr-fLg,2746
|
|
36
36
|
pytcl/containers/base.py,sha256=oKD8vAH09qAEOG3uhtoTgJ9UU_hQhl6keSIlGEVucrs,7762
|
|
37
|
-
pytcl/containers/cluster_set.py,sha256=
|
|
37
|
+
pytcl/containers/cluster_set.py,sha256=_VYQa3uCf66LJWtF3LA2lCjyVsuJtfUFD3wKH9vHknk,23995
|
|
38
38
|
pytcl/containers/covertree.py,sha256=oA09TAZYdxJbfkOUxAmXxqDn-l2XoQeUsnupjTF0Vo0,13245
|
|
39
39
|
pytcl/containers/kd_tree.py,sha256=01CsFsSmiKb0PGjdQ3z1apmvqjT2T8t-1bLwXzyJu3c,18016
|
|
40
40
|
pytcl/containers/measurement_set.py,sha256=87AbdoZIUspn1yJsiMpyQ5LoEVcerUnXefXGGPtFTJg,12654
|
|
@@ -43,7 +43,7 @@ pytcl/containers/track_list.py,sha256=6q9Qgcwm-8H_JqtOCsMssF27av4XaSkhfDl-MWb1AB
|
|
|
43
43
|
pytcl/containers/vptree.py,sha256=-7znAilGCNpN7SN8TxVhNFIOyP-s9oJa9Vp4FJWehcg,8720
|
|
44
44
|
pytcl/coordinate_systems/__init__.py,sha256=jwYhu_-9AvOeP9WLG9PYtyDwfe0GjxNZ9-xCqiLymW4,3909
|
|
45
45
|
pytcl/coordinate_systems/conversions/__init__.py,sha256=PkNevB78vBw0BkalydJBbQO91AyiMJxKRrgJNt4HsYc,1100
|
|
46
|
-
pytcl/coordinate_systems/conversions/geodetic.py,sha256=
|
|
46
|
+
pytcl/coordinate_systems/conversions/geodetic.py,sha256=v8ymPT5wUt8zGDPlttbbTkNHJMxKbBUfvgNserugwlU,27373
|
|
47
47
|
pytcl/coordinate_systems/conversions/spherical.py,sha256=GwuS1k0aUQ3AG1zZJouioMjxSIuEPRZMk-arvUCTh2k,11563
|
|
48
48
|
pytcl/coordinate_systems/jacobians/__init__.py,sha256=CRGB8GzvGT_sr4Ynm51S7gSX8grqt1pO1Pq1MWmHPTs,890
|
|
49
49
|
pytcl/coordinate_systems/jacobians/jacobians.py,sha256=IkEwyseGM1LeI2-cQEqzGD-lCplK-PVCHup7Bh3QPl4,12947
|
|
@@ -52,7 +52,7 @@ pytcl/coordinate_systems/projections/projections.py,sha256=y_kwcu_zp0HHiKR-wp3v3
|
|
|
52
52
|
pytcl/coordinate_systems/rotations/__init__.py,sha256=nqAz4iJd2hEOX_r7Tz4cE524sShyxdbtcQ5m56RrDLg,1047
|
|
53
53
|
pytcl/coordinate_systems/rotations/rotations.py,sha256=-VheczqsqF-qHF46HVKYTe0oS76MTmqyXQV3EO2JAJs,22258
|
|
54
54
|
pytcl/core/__init__.py,sha256=Rm02KyEP5jCgcaS6N6F-Bs2nLgW-EO1cjCD0NqAg664,3236
|
|
55
|
-
pytcl/core/array_utils.py,sha256=
|
|
55
|
+
pytcl/core/array_utils.py,sha256=XkOzJD_65clohicbXV_uvDXtxo9_oupVMQfk_D8T4ds,15288
|
|
56
56
|
pytcl/core/constants.py,sha256=cwkCjzCU7zG2ZsFcbqwslN632v7Lw50L85s-5q892mo,9988
|
|
57
57
|
pytcl/core/exceptions.py,sha256=6ImMiwL86BdmTt-Rc8fXLXxKUGQ-PcQQyxIvKKzw-n0,24324
|
|
58
58
|
pytcl/core/maturity.py,sha256=Sut19NfH1-6f3Qd2QSC6OAqvDcVHJDwf5-F_-oEAMJA,11596
|
|
@@ -91,12 +91,12 @@ pytcl/dynamic_models/process_noise/coordinated_turn.py,sha256=0PciDXtXHjgQdaYf7q
|
|
|
91
91
|
pytcl/dynamic_models/process_noise/polynomial.py,sha256=w5ZW5Ouw6QpVtev_mnuCmZoj6_O6ovb2L_ENKDhHYIc,7742
|
|
92
92
|
pytcl/dynamic_models/process_noise/singer.py,sha256=ozAdzH4s0wGlBaxajdyZvSnK8_CumgsUZDKeMW-TxDs,5735
|
|
93
93
|
pytcl/gpu/__init__.py,sha256=aESvpn4Sa48xrQ4SIPb0j8uBt9bgiVHK_BgCXRLNY3o,4278
|
|
94
|
-
pytcl/gpu/ekf.py,sha256=
|
|
95
|
-
pytcl/gpu/kalman.py,sha256=
|
|
96
|
-
pytcl/gpu/matrix_utils.py,sha256=
|
|
97
|
-
pytcl/gpu/particle_filter.py,sha256=
|
|
98
|
-
pytcl/gpu/ukf.py,sha256=
|
|
99
|
-
pytcl/gpu/utils.py,sha256=
|
|
94
|
+
pytcl/gpu/ekf.py,sha256=Eveyy_pJ5AXMRPUIFL5p-kmJUGMPAmwuN9r4k5x_rlM,14212
|
|
95
|
+
pytcl/gpu/kalman.py,sha256=7DsEjSnMksymE4IT4m1H27-7gaB5zhHzAHdbfp_ZpCk,16939
|
|
96
|
+
pytcl/gpu/matrix_utils.py,sha256=HwUGj6TKgt7jFUI5vR95y4evW4ZgMdHCt8G9Dlxx0RY,13878
|
|
97
|
+
pytcl/gpu/particle_filter.py,sha256=frLpeDQY81gNKTvxTYqRH0w7VKEmw42IaazYQE5T-yk,18149
|
|
98
|
+
pytcl/gpu/ukf.py,sha256=SZ-JdA2bDzqSQwqhoYwQJKwGuaZbEWxa6sbSHPYkmJo,14268
|
|
99
|
+
pytcl/gpu/utils.py,sha256=jeOZW_L1hzPFgUlwtWSCYQnl0sUzMCJiH5ekyHV4P4A,15242
|
|
100
100
|
pytcl/gravity/__init__.py,sha256=5xNdQSrrkt7-1-JPOYqR38CqvNJ7qKlPyMK36DGm6-I,3693
|
|
101
101
|
pytcl/gravity/clenshaw.py,sha256=zhEtIxUY6Uj8EMv7ucO3JMBqauA5shFKbUW0HO2hUfI,17240
|
|
102
102
|
pytcl/gravity/egm.py,sha256=LAeNbaQ7eZakk0ciwLec0_8q41MrBFouVLpDsETis6o,19683
|
|
@@ -105,46 +105,46 @@ pytcl/gravity/spherical_harmonics.py,sha256=SbCIlfNuJBwQ1nIJKo0DzgeEfW7RD_QnyKI0
|
|
|
105
105
|
pytcl/gravity/tides.py,sha256=NjsiXSiI7f-0qGr7G7YJVpIOVGzDxagz2S2vf_aRq68,28681
|
|
106
106
|
pytcl/magnetism/__init__.py,sha256=pBASOzCPHNnYqUH_XDEblhGtjz50vY9uW2KS25A0zQQ,2701
|
|
107
107
|
pytcl/magnetism/emm.py,sha256=iIdxSL0uGGIf8nfA-c_SmHvg9_J7HwRA2-qbQIUW6IE,22380
|
|
108
|
-
pytcl/magnetism/igrf.py,sha256=
|
|
109
|
-
pytcl/magnetism/wmm.py,sha256=
|
|
108
|
+
pytcl/magnetism/igrf.py,sha256=XsbhEwthNSb4i9keqqrSO9h_m62WD5AMAwg2eGpgaPc,15585
|
|
109
|
+
pytcl/magnetism/wmm.py,sha256=DYUUNrigWNOR6BETHO7FmD69amDpzP4s4aV9jg0fkiQ,25160
|
|
110
110
|
pytcl/mathematical_functions/__init__.py,sha256=zeJ1ffRRl83k2NHn3HTn-fgtFoWNPq6LCALc3xRo4Do,3767
|
|
111
111
|
pytcl/mathematical_functions/basic_matrix/__init__.py,sha256=kZv3kMAEHBdVxhbyMxTyM0s-4XJP1tK6po82UsIE4tc,1318
|
|
112
|
-
pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=
|
|
113
|
-
pytcl/mathematical_functions/basic_matrix/special_matrices.py,sha256=
|
|
112
|
+
pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=yvmQt_1UAvG1O2fOg0lzXSDcJiYYFu2yDyloz8Gz8uM,12425
|
|
113
|
+
pytcl/mathematical_functions/basic_matrix/special_matrices.py,sha256=jD20KOJHF7P8xT5mtID1aniLUY84Dz701GwDbRrXKug,14545
|
|
114
114
|
pytcl/mathematical_functions/combinatorics/__init__.py,sha256=byuHI0WkxOkQF8egrfjEr-awB2visWDXlGMnDux5IBg,1043
|
|
115
115
|
pytcl/mathematical_functions/combinatorics/combinatorics.py,sha256=LzVxY3E5_pnpVl9XlcRdQjWhSSY8Fa1JUVYA6J25fro,12354
|
|
116
116
|
pytcl/mathematical_functions/continuous_optimization/__init__.py,sha256=lck60eeCUOsRpEzPHBY3kiLKwNz_fhmYoUGP3lTmTwk,55
|
|
117
117
|
pytcl/mathematical_functions/geometry/__init__.py,sha256=DhCmux9-6zxYRzlhQ9du18kvUL-leiiZwdd3Cmb5WX0,1092
|
|
118
|
-
pytcl/mathematical_functions/geometry/geometry.py,sha256=
|
|
118
|
+
pytcl/mathematical_functions/geometry/geometry.py,sha256=mjbGAIp3F-YsVoUdVhoPQOD4bTnZrdniwVZJ2-Kja8U,17710
|
|
119
119
|
pytcl/mathematical_functions/interpolation/__init__.py,sha256=lK4Rs0Ds_fzf9q0n6id5epdN0U8V7yD87dS-w1hvN8I,741
|
|
120
|
-
pytcl/mathematical_functions/interpolation/interpolation.py,sha256=
|
|
120
|
+
pytcl/mathematical_functions/interpolation/interpolation.py,sha256=E_CAexA4m-xOincCvslvPVqaw8FSRk55IAqd2CBbdak,15649
|
|
121
121
|
pytcl/mathematical_functions/numerical_integration/__init__.py,sha256=iXiHzyV_KIhCv7tXErXlN1_fUEACN6yN3CYDHRA7esw,974
|
|
122
122
|
pytcl/mathematical_functions/numerical_integration/quadrature.py,sha256=F5y8UQltTiAyIj_lGuuRYnSf465Rm_DNHMeq-E8bj-8,17732
|
|
123
123
|
pytcl/mathematical_functions/polynomials/__init__.py,sha256=WJWZcoQhnvy5f59-kncMTgD9mCtgwfDgULvDYYHS5ys,43
|
|
124
124
|
pytcl/mathematical_functions/signal_processing/__init__.py,sha256=_SzzBVtxmSvP8FKeogRdNmFo8FOVDDoexVOqd-lE7do,2325
|
|
125
|
-
pytcl/mathematical_functions/signal_processing/detection.py,sha256=
|
|
126
|
-
pytcl/mathematical_functions/signal_processing/filters.py,sha256=
|
|
127
|
-
pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=
|
|
125
|
+
pytcl/mathematical_functions/signal_processing/detection.py,sha256=Gphym92pFx3NSradLcFiBZwGSEAedBh5bOncrq0huGA,32252
|
|
126
|
+
pytcl/mathematical_functions/signal_processing/filters.py,sha256=UhNnILWzpbJeiSait0xcnl48HEmNC5j47LWEogXMgr0,25233
|
|
127
|
+
pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=63htJUvSVFnVOwwFBtHtWEMp5puEXD7HWnsKlZrLQ4E,24511
|
|
128
128
|
pytcl/mathematical_functions/special_functions/__init__.py,sha256=AJBCKj32daQxdahUQckW0bWowzOoapxni2eZnVXERdg,3859
|
|
129
129
|
pytcl/mathematical_functions/special_functions/bessel.py,sha256=kRRPafQMXmooBglEteccjb6Hct1LLq3Oze4JfLQ-AmY,15459
|
|
130
130
|
pytcl/mathematical_functions/special_functions/debye.py,sha256=eH7Y5qq5j-AMKKx7y8uMS_l_pb6z9_3SG6Igvnc1Fdg,9626
|
|
131
131
|
pytcl/mathematical_functions/special_functions/elliptic.py,sha256=mF3hVrErlK376pw-QZDoq_R6y8gnJAZAK5Kmuq8_0n4,8131
|
|
132
132
|
pytcl/mathematical_functions/special_functions/error_functions.py,sha256=6i5HqQfYdcz0UNhLnHpoMCnWzzZypjckSHTC5ISrcHA,6990
|
|
133
133
|
pytcl/mathematical_functions/special_functions/gamma_functions.py,sha256=riTiy1cqYFtJrzscw0vgTkCaPBCPDGN8Ge6qWAD1zBg,11086
|
|
134
|
-
pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=
|
|
134
|
+
pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=_iyWm0yLVDTzNxgWogru5-LtVMUQ2FXJRZAcmj-o7eQ,12061
|
|
135
135
|
pytcl/mathematical_functions/special_functions/lambert_w.py,sha256=flLSf_7IY5sRXzd2aGgyXfJTRCjekgtvguBV3MhB9GE,6943
|
|
136
136
|
pytcl/mathematical_functions/special_functions/marcum_q.py,sha256=ASQ2vVfhmx4iEvtZTyiQ60-Hy8Qxl_4XgJzYufayJaQ,9910
|
|
137
137
|
pytcl/mathematical_functions/statistics/__init__.py,sha256=dfypStgmnFmOrnWcm-3CEvLinONHraFgx9O66_37bqw,1278
|
|
138
138
|
pytcl/mathematical_functions/statistics/distributions.py,sha256=icfFIIKCEFzkpFHuYGWL197nm8wvS7UPJlr9kd_uEgw,19373
|
|
139
|
-
pytcl/mathematical_functions/statistics/estimators.py,sha256=
|
|
139
|
+
pytcl/mathematical_functions/statistics/estimators.py,sha256=egaHcbeI1812MuDZEimrh6efas-zXSuNjWM-pcpnyfE,12036
|
|
140
140
|
pytcl/mathematical_functions/transforms/__init__.py,sha256=SPXSKHjqR6B_8pvgtbtOnEiCpU-u0JF2s7hAlhb0BbI,2343
|
|
141
141
|
pytcl/mathematical_functions/transforms/fourier.py,sha256=yD1CcH7sdPlrOmBgL7JoMiPNgN8ee7bTwvblgRRf7l4,20823
|
|
142
142
|
pytcl/mathematical_functions/transforms/stft.py,sha256=olDzNH02Nta5GoeEdsdX1tTVKODr6OxLEYt_h3ZtMgA,18878
|
|
143
|
-
pytcl/mathematical_functions/transforms/wavelets.py,sha256=
|
|
143
|
+
pytcl/mathematical_functions/transforms/wavelets.py,sha256=uHFh0wM10wSbP9Gs3P0WR3Vwz-Pue-g6Rr6H2OhJV68,22866
|
|
144
144
|
pytcl/misc/__init__.py,sha256=SCHf_lQVfdl2gwUluHBiIloTF8HRH8EkgYfbNr7zOug,33
|
|
145
145
|
pytcl/navigation/__init__.py,sha256=k1_x_FnnPrIzGeNu7zejPtPubIhweBgCfwqlZJEMw0I,6042
|
|
146
146
|
pytcl/navigation/geodesy.py,sha256=NH4Txv_VtUdBk6LkQqnSnPfRnG4WnoGF7zGwB-mIbJA,23140
|
|
147
|
-
pytcl/navigation/great_circle.py,sha256=
|
|
147
|
+
pytcl/navigation/great_circle.py,sha256=9OJKvFP6KwiqaMrgGA6DVOTOFKTeZwiqWlCejyZpwvQ,26741
|
|
148
148
|
pytcl/navigation/ins.py,sha256=OIi8_RjrgEYl0MFpJEFMjIlpgX8DYGTEhdLEvqG-ABU,31151
|
|
149
149
|
pytcl/navigation/ins_gnss.py,sha256=euKF5JGgwmVBsw3jBf7_wa2z1BpZeVbSNmBuwzhGS6c,30157
|
|
150
150
|
pytcl/navigation/rhumb.py,sha256=lEr3LJqowT-WsdSA4E-V7BFmaqxoI7OpJa05pl_-uGg,20562
|
|
@@ -172,8 +172,8 @@ pytcl/trackers/mht.py,sha256=osEOXMaCeTt1eVn_E08dLRhEvBroVmf8b81zO5Zp1lU,20720
|
|
|
172
172
|
pytcl/trackers/multi_target.py,sha256=RDITa0xnbgtVYAMj5XXp4lljo5lZ2zAAc02KZlOjxbQ,10526
|
|
173
173
|
pytcl/trackers/single_target.py,sha256=Yy3FwaNTArMWcaod-0HVeiioNV4xLWxNDn_7ZPVqQYs,6562
|
|
174
174
|
pytcl/transponders/__init__.py,sha256=5fL4u3lKCYgPLo5uFeuZbtRZkJPABntuKYGUvVgMMEI,41
|
|
175
|
-
nrl_tracker-1.
|
|
176
|
-
nrl_tracker-1.
|
|
177
|
-
nrl_tracker-1.
|
|
178
|
-
nrl_tracker-1.
|
|
179
|
-
nrl_tracker-1.
|
|
175
|
+
nrl_tracker-1.12.0.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
|
|
176
|
+
nrl_tracker-1.12.0.dist-info/METADATA,sha256=R5r43JY6yLPCD0lrxbyffCMBJm8g_KEHSojmTbeKR7k,14038
|
|
177
|
+
nrl_tracker-1.12.0.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
|
|
178
|
+
nrl_tracker-1.12.0.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
|
|
179
|
+
nrl_tracker-1.12.0.dist-info/RECORD,,
|
pytcl/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ systems, dynamic models, estimation algorithms, and mathematical functions.
|
|
|
6
6
|
|
|
7
7
|
This is a Python port of the U.S. Naval Research Laboratory's Tracker Component
|
|
8
8
|
Library originally written in MATLAB.
|
|
9
|
-
**Current Version:** 1.
|
|
9
|
+
**Current Version:** 1.12.0 (January 5, 2026)
|
|
10
10
|
**Status:** Production-ready, 2,894 tests passing, 76% line coverage
|
|
11
11
|
Examples
|
|
12
12
|
--------
|
|
@@ -21,7 +21,7 @@ References
|
|
|
21
21
|
no. 5, pp. 18-27, May 2017.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
__version__ = "1.
|
|
24
|
+
__version__ = "1.12.0"
|
|
25
25
|
__author__ = "Python Port Contributors"
|
|
26
26
|
__original_author__ = "David F. Crouse, Naval Research Laboratory"
|
|
27
27
|
|
|
@@ -179,13 +179,19 @@ def min_cost_flow_successive_shortest_paths(
|
|
|
179
179
|
max_iterations: int = 1000,
|
|
180
180
|
) -> MinCostFlowResult:
|
|
181
181
|
"""
|
|
182
|
-
Solve min-cost flow using successive shortest paths.
|
|
182
|
+
Solve min-cost flow using successive shortest paths with cost scaling.
|
|
183
183
|
|
|
184
184
|
Algorithm:
|
|
185
|
-
1.
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
185
|
+
1. Initialize potentials using Bellman-Ford
|
|
186
|
+
2. While there is excess supply:
|
|
187
|
+
- Find shortest path using reduced costs (Dijkstra with potentials)
|
|
188
|
+
- Push unit flow along path
|
|
189
|
+
- Update node potentials
|
|
190
|
+
- Recompute shortest paths to maintain optimality
|
|
191
|
+
|
|
192
|
+
This is the standard min-cost flow algorithm that guarantees optimality
|
|
193
|
+
and convergence. It uses Dijkstra's algorithm with potentials, which
|
|
194
|
+
maintains the dual feasibility (reduced cost property).
|
|
189
195
|
|
|
190
196
|
Parameters
|
|
191
197
|
----------
|
|
@@ -217,100 +223,163 @@ def min_cost_flow_successive_shortest_paths(
|
|
|
217
223
|
|
|
218
224
|
Notes
|
|
219
225
|
-----
|
|
220
|
-
This
|
|
221
|
-
|
|
226
|
+
This implementation uses successive shortest paths with potentials.
|
|
227
|
+
The algorithm is guaranteed to find the optimal solution for any
|
|
228
|
+
feasible min-cost flow problem.
|
|
229
|
+
|
|
230
|
+
For rectangular assignment problems (m < n or m > n), all m units
|
|
231
|
+
of flow must be satisfied. The algorithm ensures this by finding
|
|
232
|
+
augmenting paths until all supply is routed.
|
|
222
233
|
"""
|
|
223
234
|
n_nodes = len(supplies)
|
|
224
235
|
n_edges = len(edges)
|
|
225
236
|
|
|
226
|
-
#
|
|
227
|
-
graph: list[list[tuple[int, int, float]]] = [[] for _ in range(n_nodes)]
|
|
237
|
+
# Initialize flow and residual capacity
|
|
228
238
|
flow = np.zeros(n_edges)
|
|
229
239
|
residual_capacity = np.array([e.capacity for e in edges])
|
|
230
240
|
|
|
241
|
+
# Initialize node potentials using Bellman-Ford from a dummy source
|
|
242
|
+
# This ensures all potentials are finite and maintains dual feasibility
|
|
243
|
+
potential = np.zeros(n_nodes)
|
|
244
|
+
|
|
245
|
+
# Build adjacency list representation
|
|
246
|
+
# Each entry: (to_node, edge_idx, is_reverse, cost)
|
|
247
|
+
graph: list[list[tuple[int, int, int, float]]] = [[] for _ in range(n_nodes)]
|
|
248
|
+
|
|
231
249
|
for edge_idx, edge in enumerate(edges):
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
# Forward edge
|
|
251
|
+
graph[edge.from_node].append((edge.to_node, edge_idx, 0, edge.cost))
|
|
252
|
+
# Reverse edge (for flow cancellation)
|
|
253
|
+
graph[edge.to_node].append((edge.from_node, edge_idx, 1, -edge.cost))
|
|
235
254
|
|
|
236
255
|
current_supplies = supplies.copy()
|
|
237
256
|
iteration = 0
|
|
238
257
|
|
|
258
|
+
# Main algorithm loop
|
|
239
259
|
while iteration < max_iterations:
|
|
240
|
-
# Find
|
|
241
|
-
excess_node =
|
|
260
|
+
# Find excess and deficit nodes
|
|
261
|
+
excess_node = -1
|
|
262
|
+
deficit_node = -1
|
|
263
|
+
|
|
242
264
|
for node in range(n_nodes):
|
|
243
265
|
if current_supplies[node] > 1e-10:
|
|
244
266
|
excess_node = node
|
|
245
267
|
break
|
|
246
268
|
|
|
247
|
-
if excess_node
|
|
248
|
-
break
|
|
269
|
+
if excess_node < 0:
|
|
270
|
+
break # No more excess nodes
|
|
249
271
|
|
|
250
|
-
# Find a node with deficit
|
|
251
|
-
deficit_node = None
|
|
252
272
|
for node in range(n_nodes):
|
|
253
273
|
if current_supplies[node] < -1e-10:
|
|
254
274
|
deficit_node = node
|
|
255
275
|
break
|
|
256
276
|
|
|
257
|
-
if deficit_node
|
|
258
|
-
break
|
|
277
|
+
if deficit_node < 0:
|
|
278
|
+
break # No deficit nodes
|
|
259
279
|
|
|
260
|
-
# Find shortest path using
|
|
280
|
+
# Find shortest path using Dijkstra with potentials
|
|
281
|
+
# Reduced cost: c_reduced(u,v) = c(u,v) + π(u) - π(v)
|
|
261
282
|
dist = np.full(n_nodes, np.inf)
|
|
262
283
|
dist[excess_node] = 0.0
|
|
263
284
|
parent = np.full(n_nodes, -1, dtype=int)
|
|
264
285
|
parent_edge = np.full(n_nodes, -1, dtype=int)
|
|
286
|
+
parent_reverse = np.full(n_nodes, 0, dtype=int)
|
|
287
|
+
visited = np.zeros(n_nodes, dtype=bool)
|
|
288
|
+
|
|
289
|
+
# Dijkstra's algorithm
|
|
290
|
+
for _ in range(n_nodes):
|
|
291
|
+
# Find unvisited node with minimum distance
|
|
292
|
+
u = -1
|
|
293
|
+
min_dist = np.inf
|
|
294
|
+
for node in range(n_nodes):
|
|
295
|
+
if not visited[node] and dist[node] < min_dist:
|
|
296
|
+
u = node
|
|
297
|
+
min_dist = dist[node]
|
|
298
|
+
|
|
299
|
+
if u < 0 or dist[u] == np.inf:
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
visited[u] = True
|
|
265
303
|
|
|
266
|
-
|
|
267
|
-
for
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
304
|
+
# Relax edges from u
|
|
305
|
+
for v, eidx, is_rev, cost in graph[u]:
|
|
306
|
+
if residual_capacity[eidx] > 1e-10:
|
|
307
|
+
# Compute reduced cost
|
|
308
|
+
reduced_cost = cost + potential[u] - potential[v]
|
|
309
|
+
new_dist = dist[u] + reduced_cost
|
|
310
|
+
|
|
311
|
+
if new_dist < dist[v] - 1e-10:
|
|
312
|
+
dist[v] = new_dist
|
|
313
|
+
parent[v] = u
|
|
314
|
+
parent_edge[v] = eidx
|
|
315
|
+
parent_reverse[v] = is_rev
|
|
316
|
+
|
|
317
|
+
if dist[deficit_node] >= np.inf:
|
|
279
318
|
# No path found
|
|
280
319
|
break
|
|
281
320
|
|
|
282
|
-
#
|
|
321
|
+
# Update potentials to maintain dual feasibility
|
|
322
|
+
for node in range(n_nodes):
|
|
323
|
+
if dist[node] < np.inf:
|
|
324
|
+
potential[node] += dist[node]
|
|
325
|
+
|
|
326
|
+
# Extract path by backtracking
|
|
283
327
|
path_edges = []
|
|
328
|
+
path_reverse_flags = []
|
|
284
329
|
node = deficit_node
|
|
285
|
-
|
|
330
|
+
path_length = 0
|
|
331
|
+
visited_set = set()
|
|
332
|
+
|
|
333
|
+
while parent[node] >= 0:
|
|
334
|
+
if path_length >= n_nodes:
|
|
335
|
+
break # Safety check
|
|
336
|
+
if node in visited_set:
|
|
337
|
+
break # Cycle detected
|
|
338
|
+
|
|
339
|
+
visited_set.add(node)
|
|
286
340
|
path_edges.append(parent_edge[node])
|
|
341
|
+
path_reverse_flags.append(parent_reverse[node])
|
|
287
342
|
node = parent[node]
|
|
343
|
+
path_length += 1
|
|
344
|
+
|
|
345
|
+
if not path_edges:
|
|
346
|
+
iteration += 1
|
|
347
|
+
continue
|
|
288
348
|
|
|
289
349
|
path_edges.reverse()
|
|
350
|
+
path_reverse_flags.reverse()
|
|
290
351
|
|
|
291
|
-
# Find
|
|
352
|
+
# Find bottleneck capacity
|
|
292
353
|
min_flow = min(residual_capacity[e] for e in path_edges)
|
|
293
354
|
min_flow = min(
|
|
294
|
-
min_flow,
|
|
355
|
+
min_flow,
|
|
356
|
+
current_supplies[excess_node],
|
|
357
|
+
-current_supplies[deficit_node],
|
|
295
358
|
)
|
|
296
359
|
|
|
297
360
|
# Push flow along path
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
361
|
+
for edge_idx, is_reverse in zip(path_edges, path_reverse_flags):
|
|
362
|
+
if is_reverse == 0:
|
|
363
|
+
# Forward edge: increase flow
|
|
364
|
+
flow[edge_idx] += min_flow
|
|
365
|
+
residual_capacity[edge_idx] -= min_flow
|
|
366
|
+
else:
|
|
367
|
+
# Reverse edge: decrease flow (cancel)
|
|
368
|
+
flow[edge_idx] -= min_flow
|
|
369
|
+
residual_capacity[edge_idx] += min_flow
|
|
303
370
|
|
|
304
371
|
current_supplies[excess_node] -= min_flow
|
|
305
372
|
current_supplies[deficit_node] += min_flow
|
|
306
373
|
|
|
307
374
|
iteration += 1
|
|
308
375
|
|
|
309
|
-
# Compute total cost
|
|
310
|
-
total_cost =
|
|
376
|
+
# Compute total cost: include all flows (including negative which cancel)
|
|
377
|
+
total_cost = 0.0
|
|
378
|
+
for i, edge in enumerate(edges):
|
|
379
|
+
total_cost += flow[i] * edge.cost
|
|
311
380
|
|
|
312
381
|
# Determine status
|
|
313
|
-
if np.allclose(current_supplies, 0):
|
|
382
|
+
if np.allclose(current_supplies, 0, atol=1e-6):
|
|
314
383
|
status = FlowStatus.OPTIMAL
|
|
315
384
|
elif iteration >= max_iterations:
|
|
316
385
|
status = FlowStatus.TIMEOUT
|
|
@@ -409,6 +478,14 @@ def assignment_from_flow_solution(
|
|
|
409
478
|
"""
|
|
410
479
|
Extract assignment from flow network solution.
|
|
411
480
|
|
|
481
|
+
A valid flow solution for assignment should have:
|
|
482
|
+
- Exactly 1 unit of flow from each worker to some task
|
|
483
|
+
- Exactly 1 unit of flow to each task from some worker
|
|
484
|
+
- No negative flows on worker->task edges (those are cancellations)
|
|
485
|
+
|
|
486
|
+
This function extracts the actual assignment by identifying which
|
|
487
|
+
worker->task edges carry the net positive flow.
|
|
488
|
+
|
|
412
489
|
Parameters
|
|
413
490
|
----------
|
|
414
491
|
flow : ndarray
|
|
@@ -427,24 +504,59 @@ def assignment_from_flow_solution(
|
|
|
427
504
|
"""
|
|
428
505
|
m, n = cost_matrix_shape
|
|
429
506
|
assignment = []
|
|
507
|
+
cost = 0.0
|
|
508
|
+
|
|
509
|
+
# Build source node (node 0) and sink node (node m+n+1) indices
|
|
510
|
+
source = 0
|
|
511
|
+
sink = m + n + 1
|
|
512
|
+
|
|
513
|
+
# For a valid assignment solution:
|
|
514
|
+
# - Count flow out of source to each worker
|
|
515
|
+
# - Count flow into sink from each task
|
|
516
|
+
worker_outflow = np.zeros(m)
|
|
517
|
+
task_inflow = np.zeros(n)
|
|
518
|
+
|
|
519
|
+
# Collect all worker->task edges and their flows
|
|
520
|
+
worker_task_edges = []
|
|
430
521
|
|
|
431
522
|
for edge_idx, edge in enumerate(edges):
|
|
432
|
-
# Worker
|
|
433
|
-
if
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
task_idx = edge.to_node - m - 1
|
|
437
|
-
assignment.append([worker_idx, task_idx])
|
|
523
|
+
# Worker edges: from source (0) to worker nodes (1..m)
|
|
524
|
+
if edge.from_node == source and 1 <= edge.to_node <= m:
|
|
525
|
+
worker_id = edge.to_node - 1
|
|
526
|
+
worker_outflow[worker_id] += flow[edge_idx]
|
|
438
527
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
np.sum(
|
|
444
|
-
flow[edge_idx] * edges[edge_idx].cost for edge_idx in range(len(edges))
|
|
445
|
-
)
|
|
446
|
-
)
|
|
528
|
+
# Task edges: from task nodes (m+1..m+n) to sink
|
|
529
|
+
if m + 1 <= edge.from_node <= m + n and edge.to_node == sink:
|
|
530
|
+
task_id = edge.from_node - (m + 1)
|
|
531
|
+
task_inflow[task_id] += flow[edge_idx]
|
|
447
532
|
|
|
533
|
+
# Worker-to-task edges
|
|
534
|
+
if 1 <= edge.from_node <= m and m + 1 <= edge.to_node <= m + n:
|
|
535
|
+
worker_id = edge.from_node - 1
|
|
536
|
+
task_id = edge.to_node - (m + 1)
|
|
537
|
+
if flow[edge_idx] > 0.5: # Positive flow means this edge is used
|
|
538
|
+
worker_task_edges.append(
|
|
539
|
+
{
|
|
540
|
+
"worker": worker_id,
|
|
541
|
+
"task": task_id,
|
|
542
|
+
"flow": flow[edge_idx],
|
|
543
|
+
"cost": edge.cost,
|
|
544
|
+
"edge_idx": edge_idx,
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# For assignment problems, each worker should have exactly 1 outgoing flow
|
|
549
|
+
# and each task should have exactly 1 incoming flow
|
|
550
|
+
# Extract the assignment from worker->task edges with positive flow
|
|
551
|
+
for edge_info in worker_task_edges:
|
|
552
|
+
assignment.append([edge_info["worker"], edge_info["task"]])
|
|
553
|
+
cost += edge_info["flow"] * edge_info["cost"]
|
|
554
|
+
|
|
555
|
+
assignment = (
|
|
556
|
+
np.array(assignment, dtype=np.intp)
|
|
557
|
+
if assignment
|
|
558
|
+
else np.empty((0, 2), dtype=np.intp)
|
|
559
|
+
)
|
|
448
560
|
return assignment, cost
|
|
449
561
|
|
|
450
562
|
|
|
@@ -269,6 +269,13 @@ def mjd_to_jd(mjd: float) -> float:
|
|
|
269
269
|
float
|
|
270
270
|
Julian Date.
|
|
271
271
|
|
|
272
|
+
Examples
|
|
273
|
+
--------
|
|
274
|
+
>>> mjd = 44239.0 # 1980-01-01
|
|
275
|
+
>>> jd = mjd_to_jd(mjd)
|
|
276
|
+
>>> jd
|
|
277
|
+
2444239.5
|
|
278
|
+
|
|
272
279
|
Notes
|
|
273
280
|
-----
|
|
274
281
|
MJD = JD - 2400000.5
|
|
@@ -289,6 +296,13 @@ def jd_to_mjd(jd: float) -> float:
|
|
|
289
296
|
-------
|
|
290
297
|
float
|
|
291
298
|
Modified Julian Date.
|
|
299
|
+
|
|
300
|
+
Examples
|
|
301
|
+
--------
|
|
302
|
+
>>> jd = 2444239.5 # 1980-01-01
|
|
303
|
+
>>> mjd = jd_to_mjd(jd)
|
|
304
|
+
>>> mjd
|
|
305
|
+
44239.0
|
|
292
306
|
"""
|
|
293
307
|
return jd - MJD_OFFSET
|
|
294
308
|
|
|
@@ -328,6 +342,13 @@ def jd_to_unix(jd: float) -> float:
|
|
|
328
342
|
-------
|
|
329
343
|
float
|
|
330
344
|
Unix timestamp.
|
|
345
|
+
|
|
346
|
+
Examples
|
|
347
|
+
--------
|
|
348
|
+
>>> jd = 2440587.5 # 1970-01-01 00:00:00 UTC
|
|
349
|
+
>>> unix_to_jd = jd_to_unix(jd)
|
|
350
|
+
>>> unix_to_jd
|
|
351
|
+
0.0
|
|
331
352
|
"""
|
|
332
353
|
return (jd - JD_UNIX_EPOCH) * 86400.0
|
|
333
354
|
|
pytcl/containers/cluster_set.py
CHANGED
|
@@ -94,6 +94,23 @@ def compute_cluster_centroid(
|
|
|
94
94
|
-------
|
|
95
95
|
centroid : ndarray
|
|
96
96
|
Centroid position [x, y].
|
|
97
|
+
|
|
98
|
+
Examples
|
|
99
|
+
--------
|
|
100
|
+
>>> from pytcl.trackers.multi_target import Track
|
|
101
|
+
>>> import numpy as np
|
|
102
|
+
>>> # Create sample tracks with [x, vx, y, vy] state vectors
|
|
103
|
+
>>> track1 = Track(state=np.array([0.0, 1.0, 0.0, 1.0]))
|
|
104
|
+
>>> track2 = Track(state=np.array([2.0, 1.0, 2.0, 1.0]))
|
|
105
|
+
>>> track3 = Track(state=np.array([4.0, 1.0, 4.0, 1.0]))
|
|
106
|
+
>>> tracks = [track1, track2, track3]
|
|
107
|
+
>>> centroid = compute_cluster_centroid(tracks)
|
|
108
|
+
>>> centroid
|
|
109
|
+
array([2., 2.])
|
|
110
|
+
|
|
111
|
+
See Also
|
|
112
|
+
--------
|
|
113
|
+
compute_cluster_covariance : Compute covariance of track positions.
|
|
97
114
|
"""
|
|
98
115
|
track_list = list(tracks)
|
|
99
116
|
if len(track_list) == 0:
|
|
@@ -122,6 +139,25 @@ def compute_cluster_covariance(
|
|
|
122
139
|
-------
|
|
123
140
|
covariance : ndarray
|
|
124
141
|
Position covariance matrix (2x2).
|
|
142
|
+
|
|
143
|
+
Examples
|
|
144
|
+
--------
|
|
145
|
+
>>> from pytcl.trackers.multi_target import Track
|
|
146
|
+
>>> import numpy as np
|
|
147
|
+
>>> # Create collinear tracks (high variance along x-axis)
|
|
148
|
+
>>> track1 = Track(state=np.array([0.0, 1.0, 0.0, 0.0]))
|
|
149
|
+
>>> track2 = Track(state=np.array([1.0, 1.0, 0.0, 0.0]))
|
|
150
|
+
>>> track3 = Track(state=np.array([2.0, 1.0, 0.0, 0.0]))
|
|
151
|
+
>>> tracks = [track1, track2, track3]
|
|
152
|
+
>>> cov = compute_cluster_covariance(tracks)
|
|
153
|
+
>>> cov.shape
|
|
154
|
+
(2, 2)
|
|
155
|
+
>>> cov[0, 0] > cov[1, 1] # More spread in x than y
|
|
156
|
+
True
|
|
157
|
+
|
|
158
|
+
See Also
|
|
159
|
+
--------
|
|
160
|
+
compute_cluster_centroid : Compute centroid of track positions.
|
|
125
161
|
"""
|
|
126
162
|
track_list = list(tracks)
|
|
127
163
|
if len(track_list) < 2:
|