nrl-tracker 1.7.5__py3-none-any.whl → 1.9.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.
Files changed (33) hide show
  1. {nrl_tracker-1.7.5.dist-info → nrl_tracker-1.9.0.dist-info}/METADATA +2 -2
  2. {nrl_tracker-1.7.5.dist-info → nrl_tracker-1.9.0.dist-info}/RECORD +33 -27
  3. pytcl/__init__.py +3 -3
  4. pytcl/assignment_algorithms/dijkstra_min_cost.py +183 -0
  5. pytcl/assignment_algorithms/network_flow.py +94 -1
  6. pytcl/assignment_algorithms/network_simplex.py +165 -0
  7. pytcl/astronomical/ephemerides.py +8 -4
  8. pytcl/astronomical/relativity.py +20 -0
  9. pytcl/containers/__init__.py +19 -8
  10. pytcl/containers/base.py +82 -9
  11. pytcl/containers/covertree.py +14 -21
  12. pytcl/containers/kd_tree.py +18 -45
  13. pytcl/containers/rtree.py +43 -4
  14. pytcl/containers/vptree.py +14 -21
  15. pytcl/core/__init__.py +59 -2
  16. pytcl/core/constants.py +59 -0
  17. pytcl/core/exceptions.py +865 -0
  18. pytcl/core/optional_deps.py +531 -0
  19. pytcl/core/validation.py +4 -6
  20. pytcl/dynamic_estimation/kalman/matrix_utils.py +427 -0
  21. pytcl/dynamic_estimation/kalman/square_root.py +20 -213
  22. pytcl/dynamic_estimation/kalman/sr_ukf.py +5 -5
  23. pytcl/dynamic_estimation/kalman/types.py +98 -0
  24. pytcl/mathematical_functions/signal_processing/detection.py +19 -0
  25. pytcl/mathematical_functions/transforms/wavelets.py +7 -6
  26. pytcl/plotting/coordinates.py +25 -27
  27. pytcl/plotting/ellipses.py +14 -16
  28. pytcl/plotting/metrics.py +7 -5
  29. pytcl/plotting/tracks.py +8 -7
  30. pytcl/terrain/loaders.py +10 -6
  31. {nrl_tracker-1.7.5.dist-info → nrl_tracker-1.9.0.dist-info}/LICENSE +0 -0
  32. {nrl_tracker-1.7.5.dist-info → nrl_tracker-1.9.0.dist-info}/WHEEL +0 -0
  33. {nrl_tracker-1.7.5.dist-info → nrl_tracker-1.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,98 @@
1
+ """
2
+ Type definitions for Kalman filter implementations.
3
+
4
+ This module provides shared NamedTuple types used across multiple Kalman
5
+ filter implementations. Separating types into their own module prevents
6
+ circular imports between filter implementations.
7
+ """
8
+
9
+ from typing import NamedTuple
10
+
11
+ import numpy as np
12
+ from numpy.typing import NDArray
13
+
14
+
15
+ class SRKalmanState(NamedTuple):
16
+ """State of a square-root Kalman filter.
17
+
18
+ Attributes
19
+ ----------
20
+ x : ndarray
21
+ State estimate.
22
+ S : ndarray
23
+ Lower triangular Cholesky factor of covariance (P = S @ S.T).
24
+ """
25
+
26
+ x: NDArray[np.floating]
27
+ S: NDArray[np.floating]
28
+
29
+
30
+ class SRKalmanPrediction(NamedTuple):
31
+ """Result of square-root Kalman filter prediction step.
32
+
33
+ Attributes
34
+ ----------
35
+ x : ndarray
36
+ Predicted state estimate.
37
+ S : ndarray
38
+ Lower triangular Cholesky factor of predicted covariance.
39
+ """
40
+
41
+ x: NDArray[np.floating]
42
+ S: NDArray[np.floating]
43
+
44
+
45
+ class SRKalmanUpdate(NamedTuple):
46
+ """Result of square-root Kalman filter update step.
47
+
48
+ Attributes
49
+ ----------
50
+ x : ndarray
51
+ Updated state estimate.
52
+ S : ndarray
53
+ Lower triangular Cholesky factor of updated covariance.
54
+ y : ndarray
55
+ Innovation (measurement residual).
56
+ S_y : ndarray
57
+ Lower triangular Cholesky factor of innovation covariance.
58
+ K : ndarray
59
+ Kalman gain.
60
+ likelihood : float
61
+ Measurement likelihood (for association).
62
+ """
63
+
64
+ x: NDArray[np.floating]
65
+ S: NDArray[np.floating]
66
+ y: NDArray[np.floating]
67
+ S_y: NDArray[np.floating]
68
+ K: NDArray[np.floating]
69
+ likelihood: float
70
+
71
+
72
+ class UDState(NamedTuple):
73
+ """State of a U-D factorization filter.
74
+
75
+ The covariance is represented as P = U @ D @ U.T where U is
76
+ unit upper triangular and D is diagonal.
77
+
78
+ Attributes
79
+ ----------
80
+ x : ndarray
81
+ State estimate.
82
+ U : ndarray
83
+ Unit upper triangular factor.
84
+ D : ndarray
85
+ Diagonal elements (1D array).
86
+ """
87
+
88
+ x: NDArray[np.floating]
89
+ U: NDArray[np.floating]
90
+ D: NDArray[np.floating]
91
+
92
+
93
+ __all__ = [
94
+ "SRKalmanState",
95
+ "SRKalmanPrediction",
96
+ "SRKalmanUpdate",
97
+ "UDState",
98
+ ]
@@ -1045,3 +1045,22 @@ def snr_loss(
1045
1045
  raise ValueError(f"Unknown method: {method}")
1046
1046
 
1047
1047
  return float(loss_db)
1048
+
1049
+
1050
+ __all__ = [
1051
+ # Result Types
1052
+ "CFARResult",
1053
+ "CFARResult2D",
1054
+ # Threshold and Detection Probability
1055
+ "threshold_factor",
1056
+ "detection_probability",
1057
+ # CFAR Detectors
1058
+ "cfar_ca",
1059
+ "cfar_go",
1060
+ "cfar_so",
1061
+ "cfar_os",
1062
+ "cfar_2d",
1063
+ # Utilities
1064
+ "cluster_detections",
1065
+ "snr_loss",
1066
+ ]
@@ -25,13 +25,14 @@ from typing import Any, Callable, List, NamedTuple, Optional, Union
25
25
  import numpy as np
26
26
  from numpy.typing import ArrayLike, NDArray
27
27
 
28
- # Try to import pywavelets for DWT support
29
- try:
30
- import pywt
28
+ from pytcl.core.optional_deps import is_available
29
+
30
+ # Use the unified availability check
31
+ PYWT_AVAILABLE = is_available("pywt")
31
32
 
32
- PYWT_AVAILABLE = True
33
- except ImportError:
34
- PYWT_AVAILABLE = False
33
+ # Import pywavelets if available (for use in functions)
34
+ if PYWT_AVAILABLE:
35
+ import pywt
35
36
 
36
37
 
37
38
  # =============================================================================
@@ -10,15 +10,13 @@ from typing import Any, List, Optional, Tuple
10
10
  import numpy as np
11
11
  from numpy.typing import ArrayLike, NDArray
12
12
 
13
- try:
14
- import plotly.graph_objects as go
15
- from plotly.subplots import make_subplots
13
+ from pytcl.core.optional_deps import is_available, requires
16
14
 
17
- HAS_PLOTLY = True
18
- except ImportError:
19
- HAS_PLOTLY = False
15
+ # Lazy flag for backward compatibility
16
+ HAS_PLOTLY = is_available("plotly")
20
17
 
21
18
 
19
+ @requires("plotly", extra="visualization")
22
20
  def plot_coordinate_axes_3d(
23
21
  origin: ArrayLike = (0, 0, 0),
24
22
  rotation_matrix: Optional[ArrayLike] = None,
@@ -28,7 +26,7 @@ def plot_coordinate_axes_3d(
28
26
  line_width: int = 4,
29
27
  showlegend: bool = True,
30
28
  name_prefix: str = "",
31
- ) -> List["go.Scatter3d"]:
29
+ ) -> List[Any]:
32
30
  """
33
31
  Create Plotly traces for 3D coordinate axes.
34
32
 
@@ -56,8 +54,7 @@ def plot_coordinate_axes_3d(
56
54
  traces : list of go.Scatter3d
57
55
  List of three Plotly traces for the axes.
58
56
  """
59
- if not HAS_PLOTLY:
60
- raise ImportError("plotly is required for plotting functions")
57
+ import plotly.graph_objects as go
61
58
 
62
59
  origin = np.asarray(origin, dtype=np.float64)
63
60
  if rotation_matrix is None:
@@ -90,12 +87,13 @@ def plot_coordinate_axes_3d(
90
87
  return traces
91
88
 
92
89
 
90
+ @requires("plotly", extra="visualization")
93
91
  def plot_rotation_comparison(
94
92
  R1: ArrayLike,
95
93
  R2: ArrayLike,
96
94
  labels: Tuple[str, str] = ("Original", "Rotated"),
97
95
  title: str = "Rotation Comparison",
98
- ) -> "go.Figure":
96
+ ) -> Any:
99
97
  """
100
98
  Compare two rotation matrices by visualizing their coordinate frames.
101
99
 
@@ -115,8 +113,7 @@ def plot_rotation_comparison(
115
113
  fig : go.Figure
116
114
  Plotly figure.
117
115
  """
118
- if not HAS_PLOTLY:
119
- raise ImportError("plotly is required for plotting functions")
116
+ import plotly.graph_objects as go
120
117
 
121
118
  fig = go.Figure()
122
119
 
@@ -152,11 +149,12 @@ def plot_rotation_comparison(
152
149
  return fig
153
150
 
154
151
 
152
+ @requires("plotly", extra="visualization")
155
153
  def plot_euler_angles(
156
154
  angles: ArrayLike,
157
155
  sequence: str = "ZYX",
158
156
  title: Optional[str] = None,
159
- ) -> "go.Figure":
157
+ ) -> Any:
160
158
  """
161
159
  Visualize Euler angle rotations step by step.
162
160
 
@@ -174,8 +172,7 @@ def plot_euler_angles(
174
172
  fig : go.Figure
175
173
  Plotly figure with subplots showing each rotation step.
176
174
  """
177
- if not HAS_PLOTLY:
178
- raise ImportError("plotly is required for plotting functions")
175
+ from plotly.subplots import make_subplots
179
176
 
180
177
  angles = np.asarray(angles)
181
178
 
@@ -257,12 +254,13 @@ def plot_euler_angles(
257
254
  return fig
258
255
 
259
256
 
257
+ @requires("plotly", extra="visualization")
260
258
  def plot_quaternion_interpolation(
261
259
  q_start: ArrayLike,
262
260
  q_end: ArrayLike,
263
261
  n_steps: int = 10,
264
262
  title: str = "Quaternion SLERP Interpolation",
265
- ) -> "go.Figure":
263
+ ) -> Any:
266
264
  """
267
265
  Visualize quaternion interpolation (SLERP) between two orientations.
268
266
 
@@ -282,8 +280,7 @@ def plot_quaternion_interpolation(
282
280
  fig : go.Figure
283
281
  Plotly figure with animation.
284
282
  """
285
- if not HAS_PLOTLY:
286
- raise ImportError("plotly is required for plotting functions")
283
+ import plotly.graph_objects as go
287
284
 
288
285
  q_start = np.asarray(q_start)
289
286
  q_end = np.asarray(q_end)
@@ -410,6 +407,7 @@ def plot_quaternion_interpolation(
410
407
  return fig
411
408
 
412
409
 
410
+ @requires("plotly", extra="visualization")
413
411
  def plot_spherical_grid(
414
412
  r: float = 1.0,
415
413
  n_lat: int = 10,
@@ -417,7 +415,7 @@ def plot_spherical_grid(
417
415
  color: str = "lightblue",
418
416
  opacity: float = 0.5,
419
417
  title: str = "Spherical Coordinate Grid",
420
- ) -> "go.Figure":
418
+ ) -> Any:
421
419
  """
422
420
  Plot a spherical coordinate grid.
423
421
 
@@ -441,8 +439,7 @@ def plot_spherical_grid(
441
439
  fig : go.Figure
442
440
  Plotly figure.
443
441
  """
444
- if not HAS_PLOTLY:
445
- raise ImportError("plotly is required for plotting functions")
442
+ import plotly.graph_objects as go
446
443
 
447
444
  # Generate sphere surface
448
445
  theta = np.linspace(0, 2 * np.pi, n_lon)
@@ -485,6 +482,7 @@ def plot_spherical_grid(
485
482
  return fig
486
483
 
487
484
 
485
+ @requires("plotly", extra="visualization")
488
486
  def plot_points_spherical(
489
487
  points_spherical: ArrayLike,
490
488
  r_idx: int = 0,
@@ -494,7 +492,7 @@ def plot_points_spherical(
494
492
  size: int = 5,
495
493
  name: str = "Points",
496
494
  title: str = "Points in Spherical Coordinates",
497
- ) -> "go.Figure":
495
+ ) -> Any:
498
496
  """
499
497
  Plot points given in spherical coordinates.
500
498
 
@@ -522,8 +520,7 @@ def plot_points_spherical(
522
520
  fig : go.Figure
523
521
  Plotly figure.
524
522
  """
525
- if not HAS_PLOTLY:
526
- raise ImportError("plotly is required for plotting functions")
523
+ import plotly.graph_objects as go
527
524
 
528
525
  points = np.asarray(points_spherical)
529
526
  r = points[:, r_idx]
@@ -567,12 +564,13 @@ def plot_points_spherical(
567
564
  return fig
568
565
 
569
566
 
567
+ @requires("plotly", extra="visualization")
570
568
  def plot_coordinate_transform(
571
569
  points_original: ArrayLike,
572
570
  points_transformed: ArrayLike,
573
571
  transform_name: str = "Transform",
574
572
  title: Optional[str] = None,
575
- ) -> "go.Figure":
573
+ ) -> Any:
576
574
  """
577
575
  Visualize a coordinate transformation between two point sets.
578
576
 
@@ -592,8 +590,8 @@ def plot_coordinate_transform(
592
590
  fig : go.Figure
593
591
  Plotly figure with two subplots.
594
592
  """
595
- if not HAS_PLOTLY:
596
- raise ImportError("plotly is required for plotting functions")
593
+ import plotly.graph_objects as go
594
+ from plotly.subplots import make_subplots
597
595
 
598
596
  points_original = np.asarray(points_original)
599
597
  points_transformed = np.asarray(points_transformed)
@@ -5,17 +5,15 @@ This module provides functions for visualizing uncertainty as ellipses
5
5
  in 2D and 3D spaces, commonly used in tracking and estimation applications.
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
11
  from numpy.typing import ArrayLike, NDArray
12
12
 
13
- try:
14
- import plotly.graph_objects as go
13
+ from pytcl.core.optional_deps import is_available, requires
15
14
 
16
- HAS_PLOTLY = True
17
- except ImportError:
18
- HAS_PLOTLY = False
15
+ # Lazy flag for backward compatibility
16
+ HAS_PLOTLY = is_available("plotly")
19
17
 
20
18
 
21
19
  def covariance_ellipse_points(
@@ -240,6 +238,7 @@ def confidence_region_radius(n_dims: int, confidence: float = 0.95) -> float:
240
238
  return np.sqrt(chi2_val)
241
239
 
242
240
 
241
+ @requires("plotly", extra="visualization")
243
242
  def plot_covariance_ellipse(
244
243
  mean: ArrayLike,
245
244
  cov: ArrayLike,
@@ -249,7 +248,7 @@ def plot_covariance_ellipse(
249
248
  opacity: float = 0.3,
250
249
  name: Optional[str] = None,
251
250
  showlegend: bool = True,
252
- ) -> "go.Scatter":
251
+ ) -> Any:
253
252
  """
254
253
  Create a Plotly trace for a covariance ellipse.
255
254
 
@@ -279,11 +278,10 @@ def plot_covariance_ellipse(
279
278
 
280
279
  Raises
281
280
  ------
282
- ImportError
281
+ DependencyError
283
282
  If plotly is not installed.
284
283
  """
285
- if not HAS_PLOTLY:
286
- raise ImportError("plotly is required for plotting functions")
284
+ import plotly.graph_objects as go
287
285
 
288
286
  x, y = covariance_ellipse_points(mean, cov, n_std=n_std)
289
287
 
@@ -309,6 +307,7 @@ def plot_covariance_ellipse(
309
307
  )
310
308
 
311
309
 
310
+ @requires("plotly", extra="visualization")
312
311
  def plot_covariance_ellipses(
313
312
  means: List[ArrayLike],
314
313
  covariances: List[ArrayLike],
@@ -316,7 +315,7 @@ def plot_covariance_ellipses(
316
315
  colors: Optional[List[str]] = None,
317
316
  opacity: float = 0.3,
318
317
  show_centers: bool = True,
319
- ) -> "go.Figure":
318
+ ) -> Any:
320
319
  """
321
320
  Create a figure with multiple covariance ellipses.
322
321
 
@@ -340,8 +339,7 @@ def plot_covariance_ellipses(
340
339
  fig : go.Figure
341
340
  Plotly figure with the ellipses.
342
341
  """
343
- if not HAS_PLOTLY:
344
- raise ImportError("plotly is required for plotting functions")
342
+ import plotly.graph_objects as go
345
343
 
346
344
  fig = go.Figure()
347
345
 
@@ -394,6 +392,7 @@ def plot_covariance_ellipses(
394
392
  return fig
395
393
 
396
394
 
395
+ @requires("plotly", extra="visualization")
397
396
  def plot_covariance_ellipsoid(
398
397
  mean: ArrayLike,
399
398
  cov: ArrayLike,
@@ -401,7 +400,7 @@ def plot_covariance_ellipsoid(
401
400
  color: str = "blue",
402
401
  opacity: float = 0.5,
403
402
  name: Optional[str] = None,
404
- ) -> "go.Surface":
403
+ ) -> Any:
405
404
  """
406
405
  Create a Plotly surface trace for a 3D covariance ellipsoid.
407
406
 
@@ -425,8 +424,7 @@ def plot_covariance_ellipsoid(
425
424
  trace : go.Surface
426
425
  Plotly surface trace for the ellipsoid.
427
426
  """
428
- if not HAS_PLOTLY:
429
- raise ImportError("plotly is required for plotting functions")
427
+ import plotly.graph_objects as go
430
428
 
431
429
  x, y, z = covariance_ellipsoid_points(mean, cov, n_std=n_std)
432
430
 
pytcl/plotting/metrics.py CHANGED
@@ -10,14 +10,16 @@ from typing import List, Optional
10
10
  import numpy as np
11
11
  from numpy.typing import ArrayLike
12
12
 
13
- try:
13
+ from pytcl.core.optional_deps import is_available
14
+
15
+ # Use the unified availability check
16
+ HAS_PLOTLY = is_available("plotly")
17
+
18
+ # Import plotly if available (for use in functions)
19
+ if HAS_PLOTLY:
14
20
  import plotly.graph_objects as go
15
21
  from plotly.subplots import make_subplots
16
22
 
17
- HAS_PLOTLY = True
18
- except ImportError:
19
- HAS_PLOTLY = False
20
-
21
23
 
22
24
  def plot_rmse_over_time(
23
25
  errors: ArrayLike,
pytcl/plotting/tracks.py CHANGED
@@ -10,15 +10,16 @@ from typing import Any, Dict, List, Optional
10
10
  import numpy as np
11
11
  from numpy.typing import ArrayLike
12
12
 
13
- try:
14
- import plotly.graph_objects as go
15
- from plotly.subplots import make_subplots
13
+ from pytcl.core.optional_deps import is_available
14
+ from pytcl.plotting.ellipses import covariance_ellipse_points
16
15
 
17
- HAS_PLOTLY = True
18
- except ImportError:
19
- HAS_PLOTLY = False
16
+ # Use the unified availability check
17
+ HAS_PLOTLY = is_available("plotly")
20
18
 
21
- from pytcl.plotting.ellipses import covariance_ellipse_points
19
+ # Import plotly if available (for use in functions)
20
+ if HAS_PLOTLY:
21
+ import plotly.graph_objects as go
22
+ from plotly.subplots import make_subplots
22
23
 
23
24
 
24
25
  def plot_trajectory_2d(
pytcl/terrain/loaders.py CHANGED
@@ -26,6 +26,8 @@ from typing import Any, NamedTuple, Optional
26
26
  import numpy as np
27
27
  from numpy.typing import NDArray
28
28
 
29
+ from pytcl.core.exceptions import DependencyError
30
+
29
31
  from .dem import DEMGrid
30
32
 
31
33
  # Model parameters
@@ -306,11 +308,13 @@ def parse_gebco_netcdf(
306
308
  """
307
309
  try:
308
310
  import netCDF4 as nc
309
- except ImportError:
310
- raise ImportError(
311
- "netCDF4 is required for loading GEBCO files.\n"
312
- "Install with: pip install netCDF4"
313
- )
311
+ except ImportError as e:
312
+ raise DependencyError(
313
+ "netCDF4 is required for loading GEBCO files.",
314
+ package="netCDF4",
315
+ feature="NetCDF file reading",
316
+ install_command="pip install pytcl[terrain]",
317
+ ) from e
314
318
 
315
319
  # Set defaults for global extent
316
320
  if lat_min is None:
@@ -554,7 +558,7 @@ def load_gebco(
554
558
  ------
555
559
  FileNotFoundError
556
560
  If the GEBCO file is not found.
557
- ImportError
561
+ DependencyError
558
562
  If netCDF4 is not installed.
559
563
 
560
564
  Examples