nrl-tracker 1.8.0__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.
- {nrl_tracker-1.8.0.dist-info → nrl_tracker-1.9.0.dist-info}/METADATA +2 -2
- {nrl_tracker-1.8.0.dist-info → nrl_tracker-1.9.0.dist-info}/RECORD +32 -28
- pytcl/__init__.py +3 -3
- pytcl/assignment_algorithms/dijkstra_min_cost.py +0 -1
- pytcl/assignment_algorithms/network_simplex.py +0 -2
- pytcl/astronomical/ephemerides.py +8 -4
- pytcl/astronomical/relativity.py +20 -0
- pytcl/containers/__init__.py +19 -8
- pytcl/containers/base.py +82 -9
- pytcl/containers/covertree.py +14 -21
- pytcl/containers/kd_tree.py +18 -45
- pytcl/containers/rtree.py +43 -4
- pytcl/containers/vptree.py +14 -21
- pytcl/core/__init__.py +59 -2
- pytcl/core/constants.py +59 -0
- pytcl/core/exceptions.py +865 -0
- pytcl/core/optional_deps.py +531 -0
- pytcl/core/validation.py +4 -6
- pytcl/dynamic_estimation/kalman/matrix_utils.py +427 -0
- pytcl/dynamic_estimation/kalman/square_root.py +20 -213
- pytcl/dynamic_estimation/kalman/sr_ukf.py +5 -5
- pytcl/dynamic_estimation/kalman/types.py +98 -0
- pytcl/mathematical_functions/signal_processing/detection.py +19 -0
- pytcl/mathematical_functions/transforms/wavelets.py +7 -6
- pytcl/plotting/coordinates.py +25 -27
- pytcl/plotting/ellipses.py +14 -16
- pytcl/plotting/metrics.py +7 -5
- pytcl/plotting/tracks.py +8 -7
- pytcl/terrain/loaders.py +10 -6
- {nrl_tracker-1.8.0.dist-info → nrl_tracker-1.9.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-1.8.0.dist-info → nrl_tracker-1.9.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-1.8.0.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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# Import pywavelets if available (for use in functions)
|
|
34
|
+
if PYWT_AVAILABLE:
|
|
35
|
+
import pywt
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
# =============================================================================
|
pytcl/plotting/coordinates.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
596
|
-
|
|
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)
|
pytcl/plotting/ellipses.py
CHANGED
|
@@ -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
|
-
|
|
14
|
-
import plotly.graph_objects as go
|
|
13
|
+
from pytcl.core.optional_deps import is_available, requires
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
281
|
+
DependencyError
|
|
283
282
|
If plotly is not installed.
|
|
284
283
|
"""
|
|
285
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
HAS_PLOTLY = False
|
|
16
|
+
# Use the unified availability check
|
|
17
|
+
HAS_PLOTLY = is_available("plotly")
|
|
20
18
|
|
|
21
|
-
|
|
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
|
|
311
|
-
"netCDF4 is required for loading GEBCO files
|
|
312
|
-
"
|
|
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
|
-
|
|
561
|
+
DependencyError
|
|
558
562
|
If netCDF4 is not installed.
|
|
559
563
|
|
|
560
564
|
Examples
|
|
File without changes
|
|
File without changes
|
|
File without changes
|