nrl-tracker 0.21.4__py3-none-any.whl → 1.7.5__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 (95) hide show
  1. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/METADATA +57 -10
  2. nrl_tracker-1.7.5.dist-info/RECORD +165 -0
  3. pytcl/__init__.py +4 -3
  4. pytcl/assignment_algorithms/__init__.py +28 -0
  5. pytcl/assignment_algorithms/data_association.py +2 -7
  6. pytcl/assignment_algorithms/gating.py +10 -10
  7. pytcl/assignment_algorithms/jpda.py +40 -40
  8. pytcl/assignment_algorithms/nd_assignment.py +379 -0
  9. pytcl/assignment_algorithms/network_flow.py +371 -0
  10. pytcl/assignment_algorithms/three_dimensional/assignment.py +3 -3
  11. pytcl/astronomical/__init__.py +162 -8
  12. pytcl/astronomical/ephemerides.py +533 -0
  13. pytcl/astronomical/reference_frames.py +865 -56
  14. pytcl/astronomical/relativity.py +473 -0
  15. pytcl/astronomical/sgp4.py +710 -0
  16. pytcl/astronomical/special_orbits.py +532 -0
  17. pytcl/astronomical/tle.py +558 -0
  18. pytcl/atmosphere/__init__.py +45 -3
  19. pytcl/atmosphere/ionosphere.py +512 -0
  20. pytcl/atmosphere/nrlmsise00.py +809 -0
  21. pytcl/clustering/dbscan.py +2 -2
  22. pytcl/clustering/gaussian_mixture.py +3 -3
  23. pytcl/clustering/hierarchical.py +15 -15
  24. pytcl/clustering/kmeans.py +4 -4
  25. pytcl/containers/__init__.py +28 -21
  26. pytcl/containers/base.py +219 -0
  27. pytcl/containers/cluster_set.py +2 -1
  28. pytcl/containers/covertree.py +26 -29
  29. pytcl/containers/kd_tree.py +94 -29
  30. pytcl/containers/measurement_set.py +1 -9
  31. pytcl/containers/rtree.py +200 -1
  32. pytcl/containers/vptree.py +21 -28
  33. pytcl/coordinate_systems/conversions/geodetic.py +272 -5
  34. pytcl/coordinate_systems/jacobians/jacobians.py +2 -2
  35. pytcl/coordinate_systems/projections/__init__.py +4 -2
  36. pytcl/coordinate_systems/projections/projections.py +2 -2
  37. pytcl/coordinate_systems/rotations/rotations.py +10 -6
  38. pytcl/core/__init__.py +18 -0
  39. pytcl/core/validation.py +333 -2
  40. pytcl/dynamic_estimation/__init__.py +26 -0
  41. pytcl/dynamic_estimation/gaussian_sum_filter.py +434 -0
  42. pytcl/dynamic_estimation/imm.py +15 -18
  43. pytcl/dynamic_estimation/kalman/__init__.py +30 -0
  44. pytcl/dynamic_estimation/kalman/constrained.py +382 -0
  45. pytcl/dynamic_estimation/kalman/extended.py +9 -12
  46. pytcl/dynamic_estimation/kalman/h_infinity.py +613 -0
  47. pytcl/dynamic_estimation/kalman/square_root.py +60 -573
  48. pytcl/dynamic_estimation/kalman/sr_ukf.py +302 -0
  49. pytcl/dynamic_estimation/kalman/ud_filter.py +410 -0
  50. pytcl/dynamic_estimation/kalman/unscented.py +9 -10
  51. pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
  52. pytcl/dynamic_estimation/rbpf.py +589 -0
  53. pytcl/dynamic_estimation/smoothers.py +1 -5
  54. pytcl/dynamic_models/discrete_time/__init__.py +1 -5
  55. pytcl/dynamic_models/process_noise/__init__.py +1 -5
  56. pytcl/gravity/egm.py +13 -0
  57. pytcl/gravity/spherical_harmonics.py +98 -37
  58. pytcl/gravity/tides.py +6 -6
  59. pytcl/logging_config.py +328 -0
  60. pytcl/magnetism/__init__.py +10 -14
  61. pytcl/magnetism/emm.py +10 -3
  62. pytcl/magnetism/wmm.py +260 -23
  63. pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -5
  64. pytcl/mathematical_functions/geometry/geometry.py +5 -5
  65. pytcl/mathematical_functions/interpolation/__init__.py +2 -2
  66. pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -6
  67. pytcl/mathematical_functions/signal_processing/detection.py +24 -24
  68. pytcl/mathematical_functions/signal_processing/filters.py +14 -14
  69. pytcl/mathematical_functions/signal_processing/matched_filter.py +12 -12
  70. pytcl/mathematical_functions/special_functions/__init__.py +2 -2
  71. pytcl/mathematical_functions/special_functions/bessel.py +15 -3
  72. pytcl/mathematical_functions/special_functions/debye.py +136 -26
  73. pytcl/mathematical_functions/special_functions/error_functions.py +3 -1
  74. pytcl/mathematical_functions/special_functions/gamma_functions.py +4 -4
  75. pytcl/mathematical_functions/special_functions/hypergeometric.py +81 -15
  76. pytcl/mathematical_functions/transforms/fourier.py +8 -8
  77. pytcl/mathematical_functions/transforms/stft.py +12 -12
  78. pytcl/mathematical_functions/transforms/wavelets.py +9 -9
  79. pytcl/navigation/__init__.py +14 -10
  80. pytcl/navigation/geodesy.py +246 -160
  81. pytcl/navigation/great_circle.py +101 -19
  82. pytcl/navigation/ins.py +1 -5
  83. pytcl/plotting/coordinates.py +7 -7
  84. pytcl/plotting/tracks.py +2 -2
  85. pytcl/static_estimation/maximum_likelihood.py +16 -14
  86. pytcl/static_estimation/robust.py +5 -5
  87. pytcl/terrain/loaders.py +5 -5
  88. pytcl/trackers/__init__.py +3 -14
  89. pytcl/trackers/hypothesis.py +1 -1
  90. pytcl/trackers/mht.py +9 -9
  91. pytcl/trackers/multi_target.py +2 -5
  92. nrl_tracker-0.21.4.dist-info/RECORD +0 -148
  93. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/LICENSE +0 -0
  94. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/WHEEL +0 -0
  95. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/top_level.txt +0 -0
@@ -10,11 +10,20 @@ computing the shortest path on a sphere, including:
10
10
  - TDOA localization on a sphere
11
11
  """
12
12
 
13
- from typing import NamedTuple, Optional, Tuple
13
+ import logging
14
+ from functools import lru_cache
15
+ from typing import Any, NamedTuple, Optional, Tuple
14
16
 
15
17
  import numpy as np
16
18
  from numpy.typing import NDArray
17
19
 
20
+ # Module logger
21
+ _logger = logging.getLogger("pytcl.navigation.great_circle")
22
+
23
+ # Cache configuration for great circle calculations
24
+ _GC_CACHE_DECIMALS = 10 # ~0.01mm precision at Earth's surface
25
+ _GC_CACHE_MAXSIZE = 256 # Max cached coordinate pairs
26
+
18
27
 
19
28
  class GreatCircleResult(NamedTuple):
20
29
  """
@@ -96,6 +105,50 @@ class CrossTrackResult(NamedTuple):
96
105
  EARTH_RADIUS = 6371000.0
97
106
 
98
107
 
108
+ def _quantize_coord(val: float) -> float:
109
+ """Quantize coordinate value for cache key compatibility."""
110
+ return round(val, _GC_CACHE_DECIMALS)
111
+
112
+
113
+ @lru_cache(maxsize=_GC_CACHE_MAXSIZE)
114
+ def _gc_distance_cached(
115
+ lat1_q: float,
116
+ lon1_q: float,
117
+ lat2_q: float,
118
+ lon2_q: float,
119
+ ) -> float:
120
+ """Cached great circle distance computation (internal).
121
+
122
+ Uses haversine formula for numerical stability.
123
+ Returns angular distance in radians.
124
+ """
125
+ dlat = lat2_q - lat1_q
126
+ dlon = lon2_q - lon1_q
127
+
128
+ a = np.sin(dlat / 2) ** 2 + np.cos(lat1_q) * np.cos(lat2_q) * np.sin(dlon / 2) ** 2
129
+ return 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
130
+
131
+
132
+ @lru_cache(maxsize=_GC_CACHE_MAXSIZE)
133
+ def _gc_azimuth_cached(
134
+ lat1_q: float,
135
+ lon1_q: float,
136
+ lat2_q: float,
137
+ lon2_q: float,
138
+ ) -> float:
139
+ """Cached great circle azimuth computation (internal).
140
+
141
+ Returns azimuth in radians [0, 2π).
142
+ """
143
+ dlon = lon2_q - lon1_q
144
+
145
+ x = np.sin(dlon) * np.cos(lat2_q)
146
+ y = np.cos(lat1_q) * np.sin(lat2_q) - np.sin(lat1_q) * np.cos(lat2_q) * np.cos(dlon)
147
+
148
+ azimuth = np.arctan2(x, y)
149
+ return azimuth % (2 * np.pi)
150
+
151
+
99
152
  def great_circle_distance(
100
153
  lat1: float,
101
154
  lon1: float,
@@ -107,6 +160,7 @@ def great_circle_distance(
107
160
  Compute great circle distance between two points.
108
161
 
109
162
  Uses the haversine formula for numerical stability at small distances.
163
+ Results are cached for repeated queries with the same coordinates.
110
164
 
111
165
  Parameters
112
166
  ----------
@@ -131,13 +185,14 @@ def great_circle_distance(
131
185
  >>> dist = great_circle_distance(lat1, lon1, lat2, lon2)
132
186
  >>> print(f"Distance: {dist/1000:.0f} km")
133
187
  """
134
- dlat = lat2 - lat1
135
- dlon = lon2 - lon1
136
-
137
- a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
138
- c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
139
-
140
- return radius * c
188
+ # Use cached angular distance computation
189
+ angular_dist = _gc_distance_cached(
190
+ _quantize_coord(lat1),
191
+ _quantize_coord(lon1),
192
+ _quantize_coord(lat2),
193
+ _quantize_coord(lon2),
194
+ )
195
+ return radius * angular_dist
141
196
 
142
197
 
143
198
  def great_circle_azimuth(
@@ -149,6 +204,8 @@ def great_circle_azimuth(
149
204
  """
150
205
  Compute initial azimuth (bearing) from point 1 to point 2.
151
206
 
207
+ Results are cached for repeated queries with the same coordinates.
208
+
152
209
  Parameters
153
210
  ----------
154
211
  lat1, lon1 : float
@@ -170,15 +227,12 @@ def great_circle_azimuth(
170
227
  >>> az = great_circle_azimuth(lat1, lon1, lat2, lon2)
171
228
  >>> print(f"Initial bearing: {np.degrees(az):.1f}°")
172
229
  """
173
- dlon = lon2 - lon1
174
-
175
- x = np.sin(dlon) * np.cos(lat2)
176
- y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(dlon)
177
-
178
- azimuth = np.arctan2(x, y)
179
-
180
- # Normalize to [0, 2π)
181
- return azimuth % (2 * np.pi)
230
+ return _gc_azimuth_cached(
231
+ _quantize_coord(lat1),
232
+ _quantize_coord(lon1),
233
+ _quantize_coord(lat2),
234
+ _quantize_coord(lon2),
235
+ )
182
236
 
183
237
 
184
238
  def great_circle_inverse(
@@ -451,7 +505,7 @@ def great_circle_intersect(
451
505
  """
452
506
 
453
507
  # Convert to Cartesian unit vectors
454
- def to_cartesian(lat, lon):
508
+ def to_cartesian(lat: Any, lon: Any) -> NDArray[np.float64]:
455
509
  return np.array(
456
510
  [np.cos(lat) * np.cos(lon), np.cos(lat) * np.sin(lon), np.sin(lat)]
457
511
  )
@@ -607,7 +661,7 @@ def great_circle_tdoa_loc(
607
661
  delta_d12 = delta_r12 / radius
608
662
  delta_d13 = delta_r13 / radius
609
663
 
610
- def objective(lat, lon):
664
+ def objective(lat: Any, lon: Any) -> Any:
611
665
  """Objective function: difference between computed and observed TDOAs."""
612
666
  d1 = great_circle_distance(lat, lon, lat1, lon1, radius=1.0)
613
667
  d2 = great_circle_distance(lat, lon, lat2, lon2, radius=1.0)
@@ -771,6 +825,31 @@ def destination_point(
771
825
  return WaypointResult(float(lat2), float(lon2))
772
826
 
773
827
 
828
+ def clear_great_circle_cache() -> None:
829
+ """Clear all great circle computation caches.
830
+
831
+ This can be useful to free memory after processing large datasets
832
+ or when cache statistics are being monitored.
833
+ """
834
+ _gc_distance_cached.cache_clear()
835
+ _gc_azimuth_cached.cache_clear()
836
+ _logger.debug("Great circle caches cleared")
837
+
838
+
839
+ def get_cache_info() -> dict[str, Any]:
840
+ """Get cache statistics for great circle computations.
841
+
842
+ Returns
843
+ -------
844
+ dict[str, Any]
845
+ Dictionary with cache statistics for distance and azimuth caches.
846
+ """
847
+ return {
848
+ "distance": _gc_distance_cached.cache_info()._asdict(),
849
+ "azimuth": _gc_azimuth_cached.cache_info()._asdict(),
850
+ }
851
+
852
+
774
853
  __all__ = [
775
854
  # Constants
776
855
  "EARTH_RADIUS",
@@ -796,4 +875,7 @@ __all__ = [
796
875
  "great_circle_path_intersect",
797
876
  # TDOA
798
877
  "great_circle_tdoa_loc",
878
+ # Cache management
879
+ "clear_great_circle_cache",
880
+ "get_cache_info",
799
881
  ]
pytcl/navigation/ins.py CHANGED
@@ -23,11 +23,7 @@ from typing import NamedTuple, Optional, Tuple
23
23
  import numpy as np
24
24
  from numpy.typing import ArrayLike, NDArray
25
25
 
26
- from pytcl.coordinate_systems.rotations import (
27
- quat2rotmat,
28
- quat_multiply,
29
- rotmat2quat,
30
- )
26
+ from pytcl.coordinate_systems.rotations import quat2rotmat, quat_multiply, rotmat2quat
31
27
  from pytcl.navigation.geodesy import WGS84, Ellipsoid
32
28
 
33
29
  # =============================================================================
@@ -5,10 +5,10 @@ This module provides functions for visualizing coordinate systems,
5
5
  rotations, and transformations in 2D and 3D.
6
6
  """
7
7
 
8
- from typing import List, Optional, Tuple
8
+ from typing import Any, List, Optional, Tuple
9
9
 
10
10
  import numpy as np
11
- from numpy.typing import ArrayLike
11
+ from numpy.typing import ArrayLike, NDArray
12
12
 
13
13
  try:
14
14
  import plotly.graph_objects as go
@@ -180,17 +180,17 @@ def plot_euler_angles(
180
180
  angles = np.asarray(angles)
181
181
 
182
182
  # Create rotation matrices for each axis
183
- def rotx(a):
183
+ def rotx(a: Any) -> NDArray[np.float64]:
184
184
  return np.array(
185
185
  [[1, 0, 0], [0, np.cos(a), -np.sin(a)], [0, np.sin(a), np.cos(a)]]
186
186
  )
187
187
 
188
- def roty(a):
188
+ def roty(a: Any) -> NDArray[np.float64]:
189
189
  return np.array(
190
190
  [[np.cos(a), 0, np.sin(a)], [0, 1, 0], [-np.sin(a), 0, np.cos(a)]]
191
191
  )
192
192
 
193
- def rotz(a):
193
+ def rotz(a: Any) -> NDArray[np.float64]:
194
194
  return np.array(
195
195
  [[np.cos(a), -np.sin(a), 0], [np.sin(a), np.cos(a), 0], [0, 0, 1]]
196
196
  )
@@ -293,7 +293,7 @@ def plot_quaternion_interpolation(
293
293
  q_end = q_end / np.linalg.norm(q_end)
294
294
 
295
295
  # SLERP interpolation
296
- def quat_slerp(q1, q2, t):
296
+ def quat_slerp(q1: Any, q2: Any, t: Any) -> NDArray[np.float64]:
297
297
  dot = np.dot(q1, q2)
298
298
  if dot < 0:
299
299
  q2 = -q2
@@ -303,7 +303,7 @@ def plot_quaternion_interpolation(
303
303
  theta = np.arccos(dot)
304
304
  return (np.sin((1 - t) * theta) * q1 + np.sin(t * theta) * q2) / np.sin(theta)
305
305
 
306
- def quat_to_rotmat(q):
306
+ def quat_to_rotmat(q: Any) -> NDArray[np.float64]:
307
307
  w, x, y, z = q
308
308
  return np.array(
309
309
  [
pytcl/plotting/tracks.py CHANGED
@@ -196,7 +196,7 @@ def plot_tracking_result(
196
196
  covariances: Optional[List[ArrayLike]] = None,
197
197
  x_idx: int = 0,
198
198
  y_idx: int = 2,
199
- cov_xy_idx: tuple = (0, 2),
199
+ cov_xy_idx: tuple[int, int] = (0, 2),
200
200
  ellipse_interval: int = 5,
201
201
  n_std: float = 2.0,
202
202
  title: str = "Tracking Result",
@@ -596,7 +596,7 @@ def create_animated_tracking(
596
596
  covariances: Optional[List[ArrayLike]] = None,
597
597
  x_idx: int = 0,
598
598
  y_idx: int = 2,
599
- cov_xy_idx: tuple = (0, 2),
599
+ cov_xy_idx: tuple[int, int] = (0, 2),
600
600
  n_std: float = 2.0,
601
601
  frame_duration: int = 100,
602
602
  title: str = "Animated Tracking",
@@ -12,7 +12,7 @@ References
12
12
  Wiley, 2001.
13
13
  """
14
14
 
15
- from typing import Callable, NamedTuple, Optional
15
+ from typing import Any, Callable, NamedTuple, Optional
16
16
 
17
17
  import numpy as np
18
18
  from numpy.typing import ArrayLike, NDArray
@@ -75,10 +75,10 @@ class CRBResult(NamedTuple):
75
75
 
76
76
 
77
77
  def fisher_information_numerical(
78
- log_likelihood: Callable[[NDArray], float],
78
+ log_likelihood: Callable[[np.ndarray[Any, Any]], float],
79
79
  theta: ArrayLike,
80
80
  h: float = 1e-5,
81
- ) -> NDArray[np.floating]:
81
+ ) -> np.ndarray[Any, Any]:
82
82
  """
83
83
  Compute Fisher information matrix numerically.
84
84
 
@@ -189,11 +189,13 @@ def fisher_information_gaussian(
189
189
 
190
190
 
191
191
  def fisher_information_exponential_family(
192
- sufficient_stats: Callable[[NDArray, NDArray], NDArray],
192
+ sufficient_stats: Callable[
193
+ [np.ndarray[Any, Any], np.ndarray[Any, Any]], np.ndarray[Any, Any]
194
+ ],
193
195
  theta: ArrayLike,
194
196
  data: ArrayLike,
195
197
  h: float = 1e-5,
196
- ) -> NDArray[np.floating]:
198
+ ) -> np.ndarray[Any, Any]:
197
199
  """
198
200
  Fisher information for exponential family distributions.
199
201
 
@@ -232,10 +234,10 @@ def fisher_information_exponential_family(
232
234
 
233
235
 
234
236
  def observed_fisher_information(
235
- log_likelihood: Callable[[NDArray], float],
237
+ log_likelihood: Callable[[np.ndarray[Any, Any]], float],
236
238
  theta: ArrayLike,
237
239
  h: float = 1e-5,
238
- ) -> NDArray[np.floating]:
240
+ ) -> np.ndarray[Any, Any]:
239
241
  """
240
242
  Compute observed Fisher information (negative Hessian).
241
243
 
@@ -409,10 +411,10 @@ def efficiency(
409
411
 
410
412
 
411
413
  def mle_newton_raphson(
412
- log_likelihood: Callable[[NDArray], float],
413
- score: Callable[[NDArray], NDArray],
414
+ log_likelihood: Callable[[np.ndarray[Any, Any]], float],
415
+ score: Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]],
414
416
  theta_init: ArrayLike,
415
- hessian: Optional[Callable[[NDArray], NDArray]] = None,
417
+ hessian: Optional[Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]]] = None,
416
418
  max_iter: int = 100,
417
419
  tol: float = 1e-8,
418
420
  h: float = 1e-5,
@@ -460,7 +462,7 @@ def mle_newton_raphson(
460
462
 
461
463
  converged = False
462
464
 
463
- def numerical_hessian(t: NDArray) -> NDArray:
465
+ def numerical_hessian(t: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
464
466
  H = np.zeros((n_params, n_params))
465
467
  for i in range(n_params):
466
468
  for j in range(i, n_params):
@@ -532,9 +534,9 @@ def mle_newton_raphson(
532
534
 
533
535
 
534
536
  def mle_scoring(
535
- log_likelihood: Callable[[NDArray], float],
536
- score: Callable[[NDArray], NDArray],
537
- fisher_info: Callable[[NDArray], NDArray],
537
+ log_likelihood: Callable[[np.ndarray[Any, Any]], float],
538
+ score: Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]],
539
+ fisher_info: Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]],
538
540
  theta_init: ArrayLike,
539
541
  max_iter: int = 100,
540
542
  tol: float = 1e-8,
@@ -12,7 +12,7 @@ References
12
12
  Cartography," Communications of the ACM, 1981.
13
13
  """
14
14
 
15
- from typing import Callable, NamedTuple, Optional
15
+ from typing import Any, Callable, NamedTuple, Optional
16
16
 
17
17
  import numpy as np
18
18
  from numpy.typing import ArrayLike, NDArray
@@ -313,8 +313,8 @@ def tau_scale(
313
313
  def irls(
314
314
  A: ArrayLike,
315
315
  b: ArrayLike,
316
- weight_func: Callable[[NDArray], NDArray] = huber_weight,
317
- scale_func: Callable[[NDArray], float] = mad,
316
+ weight_func: Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]] = huber_weight,
317
+ scale_func: Callable[[np.ndarray[Any, Any]], float] = mad,
318
318
  max_iter: int = 50,
319
319
  tol: float = 1e-6,
320
320
  ) -> RobustResult:
@@ -455,7 +455,7 @@ def huber_regression(
455
455
  Gaussian errors and resistance to outliers.
456
456
  """
457
457
 
458
- def weight_func(r: NDArray) -> NDArray:
458
+ def weight_func(r: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
459
459
  return huber_weight(r, c)
460
460
 
461
461
  return irls(A, b, weight_func=weight_func, max_iter=max_iter, tol=tol)
@@ -504,7 +504,7 @@ def tukey_regression(
504
504
  Huber for gross outliers.
505
505
  """
506
506
 
507
- def weight_func(r: NDArray) -> NDArray:
507
+ def weight_func(r: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
508
508
  return tukey_weight(r, c)
509
509
 
510
510
  return irls(A, b, weight_func=weight_func, max_iter=max_iter, tol=tol)
pytcl/terrain/loaders.py CHANGED
@@ -21,7 +21,7 @@ References
21
21
  import os
22
22
  from functools import lru_cache
23
23
  from pathlib import Path
24
- from typing import Dict, NamedTuple, Optional, Tuple
24
+ from typing import Any, NamedTuple, Optional
25
25
 
26
26
  import numpy as np
27
27
  from numpy.typing import NDArray
@@ -31,7 +31,7 @@ from .dem import DEMGrid
31
31
  # Model parameters
32
32
  _GEBCO_BASE_URL = "https://www.gebco.net/data-products/gridded-bathymetry-data"
33
33
 
34
- GEBCO_PARAMETERS: Dict[str, Dict] = {
34
+ GEBCO_PARAMETERS: dict[str, dict[str, Any]] = {
35
35
  "GEBCO2024": {
36
36
  "resolution_arcsec": 15.0,
37
37
  "n_lat": 43200,
@@ -58,7 +58,7 @@ GEBCO_PARAMETERS: Dict[str, Dict] = {
58
58
  },
59
59
  }
60
60
 
61
- EARTH2014_PARAMETERS: Dict[str, Dict] = {
61
+ EARTH2014_PARAMETERS: dict[str, dict[str, Any]] = {
62
62
  "SUR": {
63
63
  "description": "Physical surface (topography, ice surface, 0 over oceans)",
64
64
  "file_pattern": "Earth2014.SUR2014.1min.geod.bin",
@@ -275,7 +275,7 @@ def parse_gebco_netcdf(
275
275
  lat_max: Optional[float] = None,
276
276
  lon_min: Optional[float] = None,
277
277
  lon_max: Optional[float] = None,
278
- ) -> Tuple[NDArray, float, float, float, float]:
278
+ ) -> tuple[NDArray[np.floating], float, float, float, float]:
279
279
  """Parse GEBCO NetCDF file and extract region.
280
280
 
281
281
  Parameters
@@ -369,7 +369,7 @@ def parse_earth2014_binary(
369
369
  lat_max: Optional[float] = None,
370
370
  lon_min: Optional[float] = None,
371
371
  lon_max: Optional[float] = None,
372
- ) -> Tuple[NDArray, float, float, float, float]:
372
+ ) -> tuple[NDArray[np.floating], float, float, float, float]:
373
373
  """Parse Earth2014 binary file and extract region.
374
374
 
375
375
  Earth2014 files are stored as int16 big-endian binary data,
@@ -16,20 +16,9 @@ from pytcl.trackers.hypothesis import (
16
16
  n_scan_prune,
17
17
  prune_hypotheses_by_probability,
18
18
  )
19
- from pytcl.trackers.mht import (
20
- MHTConfig,
21
- MHTResult,
22
- MHTTracker,
23
- )
24
- from pytcl.trackers.multi_target import (
25
- MultiTargetTracker,
26
- Track,
27
- TrackStatus,
28
- )
29
- from pytcl.trackers.single_target import (
30
- SingleTargetTracker,
31
- TrackState,
32
- )
19
+ from pytcl.trackers.mht import MHTConfig, MHTResult, MHTTracker
20
+ from pytcl.trackers.multi_target import MultiTargetTracker, Track, TrackStatus
21
+ from pytcl.trackers.single_target import SingleTargetTracker, TrackState
33
22
 
34
23
  __all__ = [
35
24
  # Single target
@@ -155,7 +155,7 @@ def generate_joint_associations(
155
155
  track_idx: int,
156
156
  current: Dict[int, int],
157
157
  used_meas: Set[int],
158
- ):
158
+ ) -> None:
159
159
  """Recursively enumerate associations."""
160
160
  if track_idx == n_tracks:
161
161
  associations.append(current.copy())
pytcl/trackers/mht.py CHANGED
@@ -15,7 +15,7 @@ References
15
15
  IEEE Trans. Automatic Control, 1979.
16
16
  """
17
17
 
18
- from typing import Callable, Dict, List, NamedTuple, Optional, Set
18
+ from typing import Callable, Dict, List, NamedTuple, Optional
19
19
 
20
20
  import numpy as np
21
21
  from numpy.typing import ArrayLike, NDArray
@@ -322,8 +322,8 @@ class MHTTracker:
322
322
  def _predict_tracks(
323
323
  self,
324
324
  tracks: Dict[int, MHTTrack],
325
- F: NDArray,
326
- Q: NDArray,
325
+ F: NDArray[np.floating],
326
+ Q: NDArray[np.floating],
327
327
  ) -> Dict[int, MHTTrack]:
328
328
  """Predict all tracks forward in time."""
329
329
  predicted = {}
@@ -352,8 +352,8 @@ class MHTTracker:
352
352
  def _compute_gating_and_likelihoods(
353
353
  self,
354
354
  tracks: Dict[int, MHTTrack],
355
- Z: NDArray,
356
- ) -> tuple[Set[tuple], Dict[tuple, float]]:
355
+ Z: NDArray[np.floating],
356
+ ) -> tuple[set[tuple[int, int]], dict[tuple[int, int], float]]:
357
357
  """Compute gating matrix and likelihood values."""
358
358
  gated = set()
359
359
  likelihood_matrix = {}
@@ -387,8 +387,8 @@ class MHTTracker:
387
387
  self,
388
388
  association: Dict[int, int],
389
389
  tracks: Dict[int, MHTTrack],
390
- Z: NDArray,
391
- likelihood_matrix: Dict[tuple, float],
390
+ Z: NDArray[np.floating],
391
+ likelihood_matrix: dict[tuple[int, int], float],
392
392
  ) -> float:
393
393
  """Compute likelihood of a joint association."""
394
394
  likelihood = 1.0
@@ -417,7 +417,7 @@ class MHTTracker:
417
417
  def _update_track(
418
418
  self,
419
419
  track: MHTTrack,
420
- measurement: NDArray,
420
+ measurement: NDArray[np.floating],
421
421
  meas_idx: int,
422
422
  ) -> MHTTrack:
423
423
  """Update a track with a measurement."""
@@ -502,7 +502,7 @@ class MHTTracker:
502
502
 
503
503
  def _initiate_track(
504
504
  self,
505
- measurement: NDArray,
505
+ measurement: NDArray[np.floating],
506
506
  meas_idx: int,
507
507
  ) -> MHTTrack:
508
508
  """Initiate a new track from a measurement."""
@@ -11,10 +11,7 @@ from typing import Callable, List, NamedTuple, Optional
11
11
  import numpy as np
12
12
  from numpy.typing import ArrayLike, NDArray
13
13
 
14
- from pytcl.assignment_algorithms import (
15
- chi2_gate_threshold,
16
- gnn_association,
17
- )
14
+ from pytcl.assignment_algorithms import chi2_gate_threshold, gnn_association
18
15
 
19
16
 
20
17
  class TrackStatus(Enum):
@@ -228,7 +225,7 @@ class MultiTargetTracker:
228
225
  track.covariance = F @ track.covariance @ F.T + Q
229
226
  track.time = self._time
230
227
 
231
- def _associate(self, Z: NDArray[np.float64]) -> dict:
228
+ def _associate(self, Z: NDArray[np.float64]) -> dict[int, int]:
232
229
  """
233
230
  Associate measurements to tracks using GNN.
234
231