nrl-tracker 0.21.5__py3-none-any.whl → 0.22.1__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-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/METADATA +2 -2
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/RECORD +84 -82
- pytcl/__init__.py +1 -1
- pytcl/assignment_algorithms/data_association.py +2 -7
- pytcl/assignment_algorithms/gating.py +3 -3
- pytcl/assignment_algorithms/jpda.py +4 -12
- pytcl/assignment_algorithms/two_dimensional/kbest.py +1 -3
- pytcl/astronomical/__init__.py +60 -7
- pytcl/astronomical/ephemerides.py +522 -0
- pytcl/astronomical/lambert.py +4 -14
- pytcl/astronomical/orbital_mechanics.py +1 -3
- pytcl/astronomical/reference_frames.py +1 -3
- pytcl/astronomical/relativity.py +466 -0
- pytcl/atmosphere/__init__.py +2 -2
- pytcl/atmosphere/models.py +1 -3
- pytcl/clustering/gaussian_mixture.py +4 -8
- pytcl/clustering/hierarchical.py +1 -5
- pytcl/clustering/kmeans.py +1 -3
- pytcl/containers/__init__.py +4 -21
- pytcl/containers/cluster_set.py +4 -19
- pytcl/containers/measurement_set.py +3 -15
- pytcl/containers/rtree.py +2 -5
- pytcl/coordinate_systems/conversions/geodetic.py +3 -15
- pytcl/coordinate_systems/projections/__init__.py +4 -2
- pytcl/coordinate_systems/projections/projections.py +11 -38
- pytcl/coordinate_systems/rotations/rotations.py +1 -3
- pytcl/core/array_utils.py +1 -4
- pytcl/core/constants.py +1 -3
- pytcl/core/validation.py +6 -17
- pytcl/dynamic_estimation/imm.py +4 -13
- pytcl/dynamic_estimation/kalman/extended.py +1 -4
- pytcl/dynamic_estimation/kalman/square_root.py +2 -6
- pytcl/dynamic_estimation/kalman/unscented.py +1 -4
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +2 -6
- pytcl/dynamic_estimation/smoothers.py +2 -8
- pytcl/dynamic_models/discrete_time/__init__.py +1 -5
- pytcl/dynamic_models/process_noise/__init__.py +1 -5
- pytcl/dynamic_models/process_noise/polynomial.py +2 -6
- pytcl/gravity/clenshaw.py +2 -6
- pytcl/gravity/egm.py +1 -3
- pytcl/gravity/models.py +1 -3
- pytcl/gravity/spherical_harmonics.py +4 -10
- pytcl/gravity/tides.py +7 -16
- pytcl/magnetism/__init__.py +3 -14
- pytcl/magnetism/emm.py +3 -12
- pytcl/magnetism/wmm.py +2 -9
- pytcl/mathematical_functions/basic_matrix/decompositions.py +1 -3
- pytcl/mathematical_functions/combinatorics/combinatorics.py +1 -3
- pytcl/mathematical_functions/geometry/geometry.py +4 -12
- pytcl/mathematical_functions/interpolation/__init__.py +2 -2
- pytcl/mathematical_functions/interpolation/interpolation.py +1 -3
- pytcl/mathematical_functions/signal_processing/detection.py +3 -6
- pytcl/mathematical_functions/signal_processing/filters.py +2 -6
- pytcl/mathematical_functions/signal_processing/matched_filter.py +2 -4
- pytcl/mathematical_functions/special_functions/__init__.py +2 -2
- pytcl/mathematical_functions/special_functions/elliptic.py +1 -3
- pytcl/mathematical_functions/special_functions/gamma_functions.py +1 -3
- pytcl/mathematical_functions/special_functions/lambert_w.py +1 -3
- pytcl/mathematical_functions/statistics/distributions.py +12 -36
- pytcl/mathematical_functions/statistics/estimators.py +1 -3
- pytcl/mathematical_functions/transforms/stft.py +3 -9
- pytcl/mathematical_functions/transforms/wavelets.py +6 -18
- pytcl/navigation/__init__.py +14 -10
- pytcl/navigation/geodesy.py +9 -31
- pytcl/navigation/great_circle.py +4 -12
- pytcl/navigation/ins.py +3 -13
- pytcl/navigation/ins_gnss.py +7 -25
- pytcl/navigation/rhumb.py +3 -10
- pytcl/performance_evaluation/track_metrics.py +1 -3
- pytcl/plotting/coordinates.py +5 -17
- pytcl/plotting/metrics.py +3 -9
- pytcl/plotting/tracks.py +3 -11
- pytcl/static_estimation/least_squares.py +1 -2
- pytcl/static_estimation/maximum_likelihood.py +3 -3
- pytcl/terrain/dem.py +2 -8
- pytcl/terrain/loaders.py +5 -13
- pytcl/terrain/visibility.py +2 -7
- pytcl/trackers/__init__.py +3 -14
- pytcl/trackers/hypothesis.py +2 -7
- pytcl/trackers/mht.py +5 -15
- pytcl/trackers/multi_target.py +1 -4
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/WHEEL +0 -0
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/top_level.txt +0 -0
pytcl/magnetism/emm.py
CHANGED
|
@@ -531,8 +531,7 @@ def _high_res_field_spherical(
|
|
|
531
531
|
factor = np.sqrt((n - m) * (n + m + 1))
|
|
532
532
|
if m + 1 <= n:
|
|
533
533
|
dP[n, m] = (
|
|
534
|
-
n * cos_theta / sin_theta * P[n, m]
|
|
535
|
-
- factor * P[n, m + 1] / sin_theta
|
|
534
|
+
n * cos_theta / sin_theta * P[n, m] - factor * P[n, m + 1] / sin_theta
|
|
536
535
|
if m + 1 <= n_max_eval
|
|
537
536
|
else n * cos_theta / sin_theta * P[n, m]
|
|
538
537
|
)
|
|
@@ -559,13 +558,7 @@ def _high_res_field_spherical(
|
|
|
559
558
|
B_theta += -r_power * dP[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
|
|
560
559
|
|
|
561
560
|
if abs(sin_theta) > 1e-10:
|
|
562
|
-
B_phi += (
|
|
563
|
-
r_power
|
|
564
|
-
* m
|
|
565
|
-
* P[n, m]
|
|
566
|
-
/ sin_theta
|
|
567
|
-
* (gnm * sin_m_lon - hnm * cos_m_lon)
|
|
568
|
-
)
|
|
561
|
+
B_phi += r_power * m * P[n, m] / sin_theta * (gnm * sin_m_lon - hnm * cos_m_lon)
|
|
569
562
|
|
|
570
563
|
return B_r, B_theta, B_phi
|
|
571
564
|
|
|
@@ -624,9 +617,7 @@ def emm(
|
|
|
624
617
|
r = a + h
|
|
625
618
|
|
|
626
619
|
# Compute field in spherical coordinates
|
|
627
|
-
B_r, B_theta, B_phi = _high_res_field_spherical(
|
|
628
|
-
lat_gc, lon, r, year, coefficients, n_max
|
|
629
|
-
)
|
|
620
|
+
B_r, B_theta, B_phi = _high_res_field_spherical(lat_gc, lon, r, year, coefficients, n_max)
|
|
630
621
|
|
|
631
622
|
# Convert to geodetic coordinates
|
|
632
623
|
X = -B_theta # North
|
pytcl/magnetism/wmm.py
CHANGED
|
@@ -468,8 +468,7 @@ def magnetic_field_spherical(
|
|
|
468
468
|
factor = np.sqrt((n - m) * (n + m + 1))
|
|
469
469
|
if m + 1 <= n:
|
|
470
470
|
dP[n, m] = (
|
|
471
|
-
n * cos_theta / sin_theta * P[n, m]
|
|
472
|
-
- factor * P[n, m + 1] / sin_theta
|
|
471
|
+
n * cos_theta / sin_theta * P[n, m] - factor * P[n, m + 1] / sin_theta
|
|
473
472
|
if m + 1 <= n_max
|
|
474
473
|
else n * cos_theta / sin_theta * P[n, m]
|
|
475
474
|
)
|
|
@@ -499,13 +498,7 @@ def magnetic_field_spherical(
|
|
|
499
498
|
B_theta += -r_power * dP[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
|
|
500
499
|
|
|
501
500
|
if abs(sin_theta) > 1e-10:
|
|
502
|
-
B_phi += (
|
|
503
|
-
r_power
|
|
504
|
-
* m
|
|
505
|
-
* P[n, m]
|
|
506
|
-
/ sin_theta
|
|
507
|
-
* (gnm * sin_m_lon - hnm * cos_m_lon)
|
|
508
|
-
)
|
|
501
|
+
B_phi += r_power * m * P[n, m] / sin_theta * (gnm * sin_m_lon - hnm * cos_m_lon)
|
|
509
502
|
|
|
510
503
|
return B_r, B_theta, B_phi
|
|
511
504
|
|
|
@@ -160,9 +160,7 @@ def tria_sqrt(
|
|
|
160
160
|
if B is not None:
|
|
161
161
|
B = np.asarray(B, dtype=np.float64)
|
|
162
162
|
if A.shape[0] != B.shape[0]:
|
|
163
|
-
raise ValueError(
|
|
164
|
-
f"A and B must have same number of rows: {A.shape[0]} vs {B.shape[0]}"
|
|
165
|
-
)
|
|
163
|
+
raise ValueError(f"A and B must have same number of rows: {A.shape[0]} vs {B.shape[0]}")
|
|
166
164
|
combined = np.hstack([A, B])
|
|
167
165
|
else:
|
|
168
166
|
combined = A
|
|
@@ -378,9 +378,7 @@ def partitions(n: int, k: Optional[int] = None) -> Iterator[Tuple[int, ...]]:
|
|
|
378
378
|
[(4,), (3, 1), (2, 2), (2, 1, 1), (1, 1, 1, 1)]
|
|
379
379
|
"""
|
|
380
380
|
|
|
381
|
-
def gen_partitions(
|
|
382
|
-
n: int, max_val: int, prefix: Tuple[int, ...]
|
|
383
|
-
) -> Iterator[Tuple[int, ...]]:
|
|
381
|
+
def gen_partitions(n: int, max_val: int, prefix: Tuple[int, ...]) -> Iterator[Tuple[int, ...]]:
|
|
384
382
|
if n == 0:
|
|
385
383
|
yield prefix
|
|
386
384
|
return
|
|
@@ -191,12 +191,8 @@ def polygon_centroid(vertices: ArrayLike) -> NDArray[np.floating]:
|
|
|
191
191
|
|
|
192
192
|
# Centroid
|
|
193
193
|
factor = 1.0 / (3.0 * a)
|
|
194
|
-
cx = factor * np.sum(
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
cy = factor * np.sum(
|
|
198
|
-
(y + np.roll(y, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y)
|
|
199
|
-
)
|
|
194
|
+
cx = factor * np.sum((x + np.roll(x, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y))
|
|
195
|
+
cy = factor * np.sum((y + np.roll(y, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y))
|
|
200
196
|
|
|
201
197
|
return np.array([cx, cy], dtype=np.float64)
|
|
202
198
|
|
|
@@ -551,14 +547,10 @@ def minimum_bounding_circle(
|
|
|
551
547
|
return circle_from_two_points(p1, p3)
|
|
552
548
|
|
|
553
549
|
ux = (
|
|
554
|
-
(ax**2 + ay**2) * (by - cy)
|
|
555
|
-
+ (bx**2 + by**2) * (cy - ay)
|
|
556
|
-
+ (cx**2 + cy**2) * (ay - by)
|
|
550
|
+
(ax**2 + ay**2) * (by - cy) + (bx**2 + by**2) * (cy - ay) + (cx**2 + cy**2) * (ay - by)
|
|
557
551
|
) / d
|
|
558
552
|
uy = (
|
|
559
|
-
(ax**2 + ay**2) * (cx - bx)
|
|
560
|
-
+ (bx**2 + by**2) * (ax - cx)
|
|
561
|
-
+ (cx**2 + cy**2) * (bx - ax)
|
|
553
|
+
(ax**2 + ay**2) * (cx - bx) + (bx**2 + by**2) * (ax - cx) + (cx**2 + cy**2) * (bx - ax)
|
|
562
554
|
) / d
|
|
563
555
|
|
|
564
556
|
center = np.array([ux, uy])
|
|
@@ -8,8 +8,8 @@ This module provides:
|
|
|
8
8
|
- Spherical interpolation
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from pytcl.mathematical_functions.interpolation.interpolation import
|
|
12
|
-
|
|
11
|
+
from pytcl.mathematical_functions.interpolation.interpolation import akima # noqa: E501
|
|
12
|
+
from pytcl.mathematical_functions.interpolation.interpolation import (
|
|
13
13
|
barycentric,
|
|
14
14
|
cubic_spline,
|
|
15
15
|
interp1d,
|
|
@@ -371,9 +371,7 @@ def rbf_interpolate(
|
|
|
371
371
|
points = np.asarray(points, dtype=np.float64)
|
|
372
372
|
values = np.asarray(values, dtype=np.float64)
|
|
373
373
|
|
|
374
|
-
return interpolate.RBFInterpolator(
|
|
375
|
-
points, values, kernel=kernel, smoothing=smoothing
|
|
376
|
-
)
|
|
374
|
+
return interpolate.RBFInterpolator(points, values, kernel=kernel, smoothing=smoothing)
|
|
377
375
|
|
|
378
376
|
|
|
379
377
|
def barycentric(
|
|
@@ -410,8 +410,7 @@ def _cfar_2d_ca_kernel(
|
|
|
410
410
|
for ri in range(row_min, row_max):
|
|
411
411
|
for ci in range(col_min, col_max):
|
|
412
412
|
if not (
|
|
413
|
-
guard_row_min <= ri < guard_row_max
|
|
414
|
-
and guard_col_min <= ci < guard_col_max
|
|
413
|
+
guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
|
|
415
414
|
):
|
|
416
415
|
ref_sum += image[ri, ci]
|
|
417
416
|
n_cells += 1
|
|
@@ -460,8 +459,7 @@ def _cfar_2d_go_kernel(
|
|
|
460
459
|
for ri in range(row_min, row_max):
|
|
461
460
|
for ci in range(col_min, col_max):
|
|
462
461
|
if not (
|
|
463
|
-
guard_row_min <= ri < guard_row_max
|
|
464
|
-
and guard_col_min <= ci < guard_col_max
|
|
462
|
+
guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
|
|
465
463
|
):
|
|
466
464
|
if ri < i:
|
|
467
465
|
top_sum += image[ri, ci]
|
|
@@ -512,8 +510,7 @@ def _cfar_2d_so_kernel(
|
|
|
512
510
|
for ri in range(row_min, row_max):
|
|
513
511
|
for ci in range(col_min, col_max):
|
|
514
512
|
if not (
|
|
515
|
-
guard_row_min <= ri < guard_row_max
|
|
516
|
-
and guard_col_min <= ci < guard_col_max
|
|
513
|
+
guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
|
|
517
514
|
):
|
|
518
515
|
if ri < i:
|
|
519
516
|
top_sum += image[ri, ci]
|
|
@@ -606,13 +606,9 @@ def filtfilt(
|
|
|
606
606
|
|
|
607
607
|
if isinstance(coeffs, FilterCoefficients):
|
|
608
608
|
if coeffs.sos is not None:
|
|
609
|
-
return scipy_signal.sosfiltfilt(
|
|
610
|
-
coeffs.sos, x, padtype=padtype, padlen=padlen
|
|
611
|
-
)
|
|
609
|
+
return scipy_signal.sosfiltfilt(coeffs.sos, x, padtype=padtype, padlen=padlen)
|
|
612
610
|
else:
|
|
613
|
-
return scipy_signal.filtfilt(
|
|
614
|
-
coeffs.b, coeffs.a, x, padtype=padtype, padlen=padlen
|
|
615
|
-
)
|
|
611
|
+
return scipy_signal.filtfilt(coeffs.b, coeffs.a, x, padtype=padtype, padlen=padlen)
|
|
616
612
|
elif isinstance(coeffs, tuple) and len(coeffs) == 2:
|
|
617
613
|
b, a = coeffs
|
|
618
614
|
return scipy_signal.filtfilt(b, a, x, padtype=padtype, padlen=padlen)
|
|
@@ -590,8 +590,7 @@ def _ambiguity_function_kernel(
|
|
|
590
590
|
for k in range(n_signal - delay_samples):
|
|
591
591
|
s1 = signal[k]
|
|
592
592
|
s2_conj = (
|
|
593
|
-
shifted[delay_samples + k].real
|
|
594
|
-
- 1j * shifted[delay_samples + k].imag
|
|
593
|
+
shifted[delay_samples + k].real - 1j * shifted[delay_samples + k].imag
|
|
595
594
|
)
|
|
596
595
|
result += s1 * s2_conj
|
|
597
596
|
|
|
@@ -638,8 +637,7 @@ def _cross_ambiguity_kernel(
|
|
|
638
637
|
for k in range(n_signal - delay_samples):
|
|
639
638
|
s1 = signal1[k]
|
|
640
639
|
s2_conj = (
|
|
641
|
-
shifted[delay_samples + k].real
|
|
642
|
-
- 1j * shifted[delay_samples + k].imag
|
|
640
|
+
shifted[delay_samples + k].real - 1j * shifted[delay_samples + k].imag
|
|
643
641
|
)
|
|
644
642
|
result += s1 * s2_conj
|
|
645
643
|
|
|
@@ -40,8 +40,8 @@ from pytcl.mathematical_functions.special_functions.debye import (
|
|
|
40
40
|
debye_entropy,
|
|
41
41
|
debye_heat_capacity,
|
|
42
42
|
)
|
|
43
|
-
from pytcl.mathematical_functions.special_functions.elliptic import
|
|
44
|
-
|
|
43
|
+
from pytcl.mathematical_functions.special_functions.elliptic import ellipe # noqa: E501
|
|
44
|
+
from pytcl.mathematical_functions.special_functions.elliptic import (
|
|
45
45
|
ellipeinc,
|
|
46
46
|
ellipk,
|
|
47
47
|
ellipkinc,
|
|
@@ -243,9 +243,7 @@ def elliprg(x: ArrayLike, y: ArrayLike, z: ArrayLike) -> NDArray[np.floating]:
|
|
|
243
243
|
return np.asarray(sp.elliprg(x, y, z), dtype=np.float64)
|
|
244
244
|
|
|
245
245
|
|
|
246
|
-
def elliprj(
|
|
247
|
-
x: ArrayLike, y: ArrayLike, z: ArrayLike, p: ArrayLike
|
|
248
|
-
) -> NDArray[np.floating]:
|
|
246
|
+
def elliprj(x: ArrayLike, y: ArrayLike, z: ArrayLike, p: ArrayLike) -> NDArray[np.floating]:
|
|
249
247
|
"""
|
|
250
248
|
Carlson symmetric elliptic integral R_J.
|
|
251
249
|
|
|
@@ -421,9 +421,7 @@ def comb(
|
|
|
421
421
|
--------
|
|
422
422
|
scipy.special.comb : Combinations.
|
|
423
423
|
"""
|
|
424
|
-
return np.asarray(
|
|
425
|
-
sp.comb(n, k, exact=exact, repetition=repetition), dtype=np.float64
|
|
426
|
-
)
|
|
424
|
+
return np.asarray(sp.comb(n, k, exact=exact, repetition=repetition), dtype=np.float64)
|
|
427
425
|
|
|
428
426
|
|
|
429
427
|
def perm(n: ArrayLike, k: ArrayLike, exact: bool = False) -> NDArray:
|
|
@@ -108,9 +108,7 @@ def lambert_w_real(
|
|
|
108
108
|
raise ValueError(f"For branch 0, x must be >= -1/e ≈ {branch_point:.6f}")
|
|
109
109
|
elif branch == -1:
|
|
110
110
|
if np.any((x < branch_point) | (x >= 0)):
|
|
111
|
-
raise ValueError(
|
|
112
|
-
f"For branch -1, x must be in [-1/e, 0) ≈ [{branch_point:.6f}, 0)"
|
|
113
|
-
)
|
|
111
|
+
raise ValueError(f"For branch -1, x must be in [-1/e, 0) ≈ [{branch_point:.6f}, 0)")
|
|
114
112
|
else:
|
|
115
113
|
raise ValueError(f"branch must be 0 or -1, got {branch}")
|
|
116
114
|
|
|
@@ -43,9 +43,7 @@ class Distribution(ABC):
|
|
|
43
43
|
pass
|
|
44
44
|
|
|
45
45
|
@abstractmethod
|
|
46
|
-
def sample(
|
|
47
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
48
|
-
) -> NDArray[np.floating]:
|
|
46
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
49
47
|
"""Generate random samples."""
|
|
50
48
|
pass
|
|
51
49
|
|
|
@@ -104,9 +102,7 @@ class Gaussian(Distribution):
|
|
|
104
102
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
105
103
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
106
104
|
|
|
107
|
-
def sample(
|
|
108
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
109
|
-
) -> NDArray[np.floating]:
|
|
105
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
110
106
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
111
107
|
|
|
112
108
|
def mean(self) -> float:
|
|
@@ -167,9 +163,7 @@ class MultivariateGaussian(Distribution):
|
|
|
167
163
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
168
164
|
raise NotImplementedError("PPF not available for multivariate normal")
|
|
169
165
|
|
|
170
|
-
def sample(
|
|
171
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
172
|
-
) -> NDArray[np.floating]:
|
|
166
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
173
167
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
174
168
|
|
|
175
169
|
def mean(self) -> NDArray[np.floating]:
|
|
@@ -239,9 +233,7 @@ class Uniform(Distribution):
|
|
|
239
233
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
240
234
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
241
235
|
|
|
242
|
-
def sample(
|
|
243
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
244
|
-
) -> NDArray[np.floating]:
|
|
236
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
245
237
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
246
238
|
|
|
247
239
|
def mean(self) -> float:
|
|
@@ -279,9 +271,7 @@ class Exponential(Distribution):
|
|
|
279
271
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
280
272
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
281
273
|
|
|
282
|
-
def sample(
|
|
283
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
284
|
-
) -> NDArray[np.floating]:
|
|
274
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
285
275
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
286
276
|
|
|
287
277
|
def mean(self) -> float:
|
|
@@ -347,9 +337,7 @@ class Gamma(Distribution):
|
|
|
347
337
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
348
338
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
349
339
|
|
|
350
|
-
def sample(
|
|
351
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
352
|
-
) -> NDArray[np.floating]:
|
|
340
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
353
341
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
354
342
|
|
|
355
343
|
def mean(self) -> float:
|
|
@@ -387,9 +375,7 @@ class ChiSquared(Distribution):
|
|
|
387
375
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
388
376
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
389
377
|
|
|
390
|
-
def sample(
|
|
391
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
392
|
-
) -> NDArray[np.floating]:
|
|
378
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
393
379
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
394
380
|
|
|
395
381
|
def mean(self) -> float:
|
|
@@ -436,9 +422,7 @@ class StudentT(Distribution):
|
|
|
436
422
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
437
423
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
438
424
|
|
|
439
|
-
def sample(
|
|
440
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
441
|
-
) -> NDArray[np.floating]:
|
|
425
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
442
426
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
443
427
|
|
|
444
428
|
def mean(self) -> float:
|
|
@@ -485,9 +469,7 @@ class Beta(Distribution):
|
|
|
485
469
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
486
470
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
487
471
|
|
|
488
|
-
def sample(
|
|
489
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
490
|
-
) -> NDArray[np.floating]:
|
|
472
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
491
473
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
492
474
|
|
|
493
475
|
def mean(self) -> float:
|
|
@@ -528,9 +510,7 @@ class Poisson(Distribution):
|
|
|
528
510
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
529
511
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
530
512
|
|
|
531
|
-
def sample(
|
|
532
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
533
|
-
) -> NDArray[np.floating]:
|
|
513
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
534
514
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
535
515
|
|
|
536
516
|
def mean(self) -> float:
|
|
@@ -573,9 +553,7 @@ class VonMises(Distribution):
|
|
|
573
553
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
574
554
|
return np.asarray(self._dist.ppf(q), dtype=np.float64)
|
|
575
555
|
|
|
576
|
-
def sample(
|
|
577
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
578
|
-
) -> NDArray[np.floating]:
|
|
556
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
579
557
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
580
558
|
|
|
581
559
|
def mean(self) -> float:
|
|
@@ -628,9 +606,7 @@ class Wishart(Distribution):
|
|
|
628
606
|
def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
|
|
629
607
|
raise NotImplementedError("PPF not available for Wishart distribution")
|
|
630
608
|
|
|
631
|
-
def sample(
|
|
632
|
-
self, size: Optional[Union[int, Tuple[int, ...]]] = None
|
|
633
|
-
) -> NDArray[np.floating]:
|
|
609
|
+
def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
|
|
634
610
|
return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
|
|
635
611
|
|
|
636
612
|
def mean(self) -> NDArray[np.floating]:
|
|
@@ -364,9 +364,7 @@ def kurtosis(
|
|
|
364
364
|
"""
|
|
365
365
|
from scipy.stats import kurtosis as scipy_kurtosis
|
|
366
366
|
|
|
367
|
-
return np.asarray(
|
|
368
|
-
scipy_kurtosis(x, axis=axis, fisher=fisher, bias=bias), dtype=np.float64
|
|
369
|
-
)
|
|
367
|
+
return np.asarray(scipy_kurtosis(x, axis=axis, fisher=fisher, bias=bias), dtype=np.float64)
|
|
370
368
|
|
|
371
369
|
|
|
372
370
|
def moment(
|
|
@@ -503,12 +503,8 @@ def reassigned_spectrogram(
|
|
|
503
503
|
win_d = np.gradient(win)
|
|
504
504
|
|
|
505
505
|
# STFT with modified windows
|
|
506
|
-
result_t = stft(
|
|
507
|
-
|
|
508
|
-
)
|
|
509
|
-
result_d = stft(
|
|
510
|
-
x, fs=fs, window=win_d, nperseg=nperseg, noverlap=noverlap, nfft=nfft
|
|
511
|
-
)
|
|
506
|
+
result_t = stft(x, fs=fs, window=win_t, nperseg=nperseg, noverlap=noverlap, nfft=nfft)
|
|
507
|
+
result_d = stft(x, fs=fs, window=win_d, nperseg=nperseg, noverlap=noverlap, nfft=nfft)
|
|
512
508
|
|
|
513
509
|
# Compute reassigned coordinates
|
|
514
510
|
Zxx = result1.Zxx
|
|
@@ -592,9 +588,7 @@ def mel_spectrogram(
|
|
|
592
588
|
noverlap = nperseg // 4
|
|
593
589
|
|
|
594
590
|
# Compute linear spectrogram
|
|
595
|
-
spec_result = spectrogram(
|
|
596
|
-
x, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap
|
|
597
|
-
)
|
|
591
|
+
spec_result = spectrogram(x, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap)
|
|
598
592
|
|
|
599
593
|
# Create mel filterbank
|
|
600
594
|
mel_fb = _mel_filterbank(
|
|
@@ -526,9 +526,7 @@ def dwt(
|
|
|
526
526
|
- 'biorN.M': Biorthogonal wavelets
|
|
527
527
|
"""
|
|
528
528
|
if not PYWT_AVAILABLE:
|
|
529
|
-
raise ImportError(
|
|
530
|
-
"pywavelets is required for DWT. Install with: pip install pywavelets"
|
|
531
|
-
)
|
|
529
|
+
raise ImportError("pywavelets is required for DWT. Install with: pip install pywavelets")
|
|
532
530
|
|
|
533
531
|
signal = np.asarray(signal, dtype=np.float64)
|
|
534
532
|
|
|
@@ -579,9 +577,7 @@ def idwt(
|
|
|
579
577
|
True
|
|
580
578
|
"""
|
|
581
579
|
if not PYWT_AVAILABLE:
|
|
582
|
-
raise ImportError(
|
|
583
|
-
"pywavelets is required for IDWT. Install with: pip install pywavelets"
|
|
584
|
-
)
|
|
580
|
+
raise ImportError("pywavelets is required for IDWT. Install with: pip install pywavelets")
|
|
585
581
|
|
|
586
582
|
# Reconstruct coeffs list in pywt format
|
|
587
583
|
# [cA_n, cD_n, cD_n-1, ..., cD_1]
|
|
@@ -617,9 +613,7 @@ def dwt_single_level(
|
|
|
617
613
|
Detail coefficients.
|
|
618
614
|
"""
|
|
619
615
|
if not PYWT_AVAILABLE:
|
|
620
|
-
raise ImportError(
|
|
621
|
-
"pywavelets is required for DWT. Install with: pip install pywavelets"
|
|
622
|
-
)
|
|
616
|
+
raise ImportError("pywavelets is required for DWT. Install with: pip install pywavelets")
|
|
623
617
|
|
|
624
618
|
signal = np.asarray(signal, dtype=np.float64)
|
|
625
619
|
cA, cD = pywt.dwt(signal, wavelet, mode=mode)
|
|
@@ -653,9 +647,7 @@ def idwt_single_level(
|
|
|
653
647
|
Reconstructed signal.
|
|
654
648
|
"""
|
|
655
649
|
if not PYWT_AVAILABLE:
|
|
656
|
-
raise ImportError(
|
|
657
|
-
"pywavelets is required for IDWT. Install with: pip install pywavelets"
|
|
658
|
-
)
|
|
650
|
+
raise ImportError("pywavelets is required for IDWT. Install with: pip install pywavelets")
|
|
659
651
|
|
|
660
652
|
cA = np.asarray(cA, dtype=np.float64)
|
|
661
653
|
cD = np.asarray(cD, dtype=np.float64)
|
|
@@ -709,9 +701,7 @@ def wpt(
|
|
|
709
701
|
True
|
|
710
702
|
"""
|
|
711
703
|
if not PYWT_AVAILABLE:
|
|
712
|
-
raise ImportError(
|
|
713
|
-
"pywavelets is required for WPT. Install with: pip install pywavelets"
|
|
714
|
-
)
|
|
704
|
+
raise ImportError("pywavelets is required for WPT. Install with: pip install pywavelets")
|
|
715
705
|
|
|
716
706
|
signal = np.asarray(signal, dtype=np.float64)
|
|
717
707
|
|
|
@@ -817,9 +807,7 @@ def threshold_coefficients(
|
|
|
817
807
|
Thresholded coefficients.
|
|
818
808
|
"""
|
|
819
809
|
if not PYWT_AVAILABLE:
|
|
820
|
-
raise ImportError(
|
|
821
|
-
"pywavelets is required. Install with: pip install pywavelets"
|
|
822
|
-
)
|
|
810
|
+
raise ImportError("pywavelets is required. Install with: pip install pywavelets")
|
|
823
811
|
|
|
824
812
|
# Estimate noise from finest detail coefficients
|
|
825
813
|
if value is None:
|
pytcl/navigation/__init__.py
CHANGED
|
@@ -11,8 +11,10 @@ needed in tracking applications, including:
|
|
|
11
11
|
- Rhumb line navigation
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from pytcl.navigation.geodesy import (
|
|
15
|
-
GRS80,
|
|
14
|
+
from pytcl.navigation.geodesy import (
|
|
15
|
+
GRS80, # Ellipsoids; Coordinate conversions; Geodetic problems
|
|
16
|
+
)
|
|
17
|
+
from pytcl.navigation.geodesy import (
|
|
16
18
|
SPHERE,
|
|
17
19
|
WGS84,
|
|
18
20
|
Ellipsoid,
|
|
@@ -26,8 +28,8 @@ from pytcl.navigation.geodesy import ( # Ellipsoids; Coordinate conversions; Ge
|
|
|
26
28
|
inverse_geodetic,
|
|
27
29
|
ned_to_ecef,
|
|
28
30
|
)
|
|
29
|
-
from pytcl.navigation.great_circle import
|
|
30
|
-
|
|
31
|
+
from pytcl.navigation.great_circle import EARTH_RADIUS # Great circle navigation
|
|
32
|
+
from pytcl.navigation.great_circle import (
|
|
31
33
|
CrossTrackResult,
|
|
32
34
|
GreatCircleResult,
|
|
33
35
|
IntersectionResult,
|
|
@@ -45,8 +47,10 @@ from pytcl.navigation.great_circle import ( # Great circle navigation
|
|
|
45
47
|
great_circle_waypoint,
|
|
46
48
|
great_circle_waypoints,
|
|
47
49
|
)
|
|
48
|
-
from pytcl.navigation.ins import (
|
|
49
|
-
A_EARTH,
|
|
50
|
+
from pytcl.navigation.ins import (
|
|
51
|
+
A_EARTH, # Constants; State representation; Gravity and Earth rate
|
|
52
|
+
)
|
|
53
|
+
from pytcl.navigation.ins import (
|
|
50
54
|
B_EARTH,
|
|
51
55
|
E2_EARTH,
|
|
52
56
|
F_EARTH,
|
|
@@ -73,8 +77,8 @@ from pytcl.navigation.ins import ( # Constants; State representation; Gravity a
|
|
|
73
77
|
update_attitude_ned,
|
|
74
78
|
update_quaternion,
|
|
75
79
|
)
|
|
76
|
-
from pytcl.navigation.ins_gnss import
|
|
77
|
-
|
|
80
|
+
from pytcl.navigation.ins_gnss import GPS_L1_FREQ # INS/GNSS integration
|
|
81
|
+
from pytcl.navigation.ins_gnss import (
|
|
78
82
|
GPS_L1_WAVELENGTH,
|
|
79
83
|
SPEED_OF_LIGHT,
|
|
80
84
|
GNSSMeasurement,
|
|
@@ -99,8 +103,8 @@ from pytcl.navigation.ins_gnss import ( # INS/GNSS integration
|
|
|
99
103
|
tight_coupled_update,
|
|
100
104
|
velocity_measurement_matrix,
|
|
101
105
|
)
|
|
102
|
-
from pytcl.navigation.rhumb import
|
|
103
|
-
|
|
106
|
+
from pytcl.navigation.rhumb import RhumbDirectResult # Rhumb line navigation
|
|
107
|
+
from pytcl.navigation.rhumb import (
|
|
104
108
|
RhumbIntersectionResult,
|
|
105
109
|
RhumbResult,
|
|
106
110
|
compare_great_circle_rhumb,
|
pytcl/navigation/geodesy.py
CHANGED
|
@@ -356,9 +356,7 @@ def ned_to_ecef(
|
|
|
356
356
|
x, y, z : ndarray
|
|
357
357
|
ECEF coordinates in meters.
|
|
358
358
|
"""
|
|
359
|
-
return enu_to_ecef(
|
|
360
|
-
east, north, -np.asarray(down), lat_ref, lon_ref, alt_ref, ellipsoid
|
|
361
|
-
)
|
|
359
|
+
return enu_to_ecef(east, north, -np.asarray(down), lat_ref, lon_ref, alt_ref, ellipsoid)
|
|
362
360
|
|
|
363
361
|
|
|
364
362
|
def direct_geodetic(
|
|
@@ -436,11 +434,7 @@ def direct_geodetic(
|
|
|
436
434
|
/ 4
|
|
437
435
|
* (
|
|
438
436
|
cos_sigma * (-1 + 2 * cos_2sigma_m**2)
|
|
439
|
-
- B
|
|
440
|
-
/ 6
|
|
441
|
-
* cos_2sigma_m
|
|
442
|
-
* (-3 + 4 * sin_sigma**2)
|
|
443
|
-
* (-3 + 4 * cos_2sigma_m**2)
|
|
437
|
+
- B / 6 * cos_2sigma_m * (-3 + 4 * sin_sigma**2) * (-3 + 4 * cos_2sigma_m**2)
|
|
444
438
|
)
|
|
445
439
|
)
|
|
446
440
|
)
|
|
@@ -458,27 +452,20 @@ def direct_geodetic(
|
|
|
458
452
|
lat2 = np.arctan2(
|
|
459
453
|
sin_U2,
|
|
460
454
|
(1 - f)
|
|
461
|
-
* np.sqrt(
|
|
462
|
-
sin_alpha**2 + (sin_U1 * sin_sigma - cos_U1 * cos_sigma * cos_alpha1) ** 2
|
|
463
|
-
),
|
|
455
|
+
* np.sqrt(sin_alpha**2 + (sin_U1 * sin_sigma - cos_U1 * cos_sigma * cos_alpha1) ** 2),
|
|
464
456
|
)
|
|
465
457
|
|
|
466
|
-
lam = np.arctan2(
|
|
467
|
-
sin_sigma * sin_alpha1, cos_U1 * cos_sigma - sin_U1 * sin_sigma * cos_alpha1
|
|
468
|
-
)
|
|
458
|
+
lam = np.arctan2(sin_sigma * sin_alpha1, cos_U1 * cos_sigma - sin_U1 * sin_sigma * cos_alpha1)
|
|
469
459
|
|
|
470
460
|
C = f / 16 * cos2_alpha * (4 + f * (4 - 3 * cos2_alpha))
|
|
471
461
|
L = lam - (1 - C) * f * sin_alpha * (
|
|
472
|
-
sigma
|
|
473
|
-
+ C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
|
|
462
|
+
sigma + C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
|
|
474
463
|
)
|
|
475
464
|
|
|
476
465
|
lon2 = lon1 + L
|
|
477
466
|
|
|
478
467
|
# Back azimuth
|
|
479
|
-
azimuth2 = np.arctan2(
|
|
480
|
-
sin_alpha, -sin_U1 * sin_sigma + cos_U1 * cos_sigma * cos_alpha1
|
|
481
|
-
)
|
|
468
|
+
azimuth2 = np.arctan2(sin_alpha, -sin_U1 * sin_sigma + cos_U1 * cos_sigma * cos_alpha1)
|
|
482
469
|
|
|
483
470
|
return float(lat2), float(lon2), float(azimuth2)
|
|
484
471
|
|
|
@@ -565,10 +552,7 @@ def inverse_geodetic(
|
|
|
565
552
|
C = f / 16 * cos2_alpha * (4 + f * (4 - 3 * cos2_alpha))
|
|
566
553
|
|
|
567
554
|
lam_new = L + (1 - C) * f * sin_alpha * (
|
|
568
|
-
sigma
|
|
569
|
-
+ C
|
|
570
|
-
* sin_sigma
|
|
571
|
-
* (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
|
|
555
|
+
sigma + C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
|
|
572
556
|
)
|
|
573
557
|
|
|
574
558
|
if abs(lam_new - lam) < 1e-12:
|
|
@@ -588,11 +572,7 @@ def inverse_geodetic(
|
|
|
588
572
|
/ 4
|
|
589
573
|
* (
|
|
590
574
|
cos_sigma * (-1 + 2 * cos_2sigma_m**2)
|
|
591
|
-
- B
|
|
592
|
-
/ 6
|
|
593
|
-
* cos_2sigma_m
|
|
594
|
-
* (-3 + 4 * sin_sigma**2)
|
|
595
|
-
* (-3 + 4 * cos_2sigma_m**2)
|
|
575
|
+
- B / 6 * cos_2sigma_m * (-3 + 4 * sin_sigma**2) * (-3 + 4 * cos_2sigma_m**2)
|
|
596
576
|
)
|
|
597
577
|
)
|
|
598
578
|
)
|
|
@@ -601,9 +581,7 @@ def inverse_geodetic(
|
|
|
601
581
|
|
|
602
582
|
# Azimuths
|
|
603
583
|
azimuth1 = np.arctan2(cos_U2 * sin_lam, cos_U1 * sin_U2 - sin_U1 * cos_U2 * cos_lam)
|
|
604
|
-
azimuth2 = np.arctan2(
|
|
605
|
-
cos_U1 * sin_lam, -sin_U1 * cos_U2 + cos_U1 * sin_U2 * cos_lam
|
|
606
|
-
)
|
|
584
|
+
azimuth2 = np.arctan2(cos_U1 * sin_lam, -sin_U1 * cos_U2 + cos_U1 * sin_U2 * cos_lam)
|
|
607
585
|
|
|
608
586
|
return float(distance), float(azimuth1), float(azimuth2)
|
|
609
587
|
|