doppy 0.0.3__cp310-abi3-macosx_10_12_x86_64.whl → 0.0.5__cp310-abi3-macosx_10_12_x86_64.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.
Potentially problematic release.
This version of doppy might be problematic. Click here for more details.
- doppy/product/__init__.py +2 -1
- doppy/product/stare.py +1 -5
- doppy/product/wind.py +244 -0
- doppy/raw/halo_hpl.py +1 -1
- doppy/rs.abi3.so +0 -0
- {doppy-0.0.3.dist-info → doppy-0.0.5.dist-info}/METADATA +1 -1
- {doppy-0.0.3.dist-info → doppy-0.0.5.dist-info}/RECORD +11 -10
- {doppy-0.0.3.dist-info → doppy-0.0.5.dist-info}/WHEEL +0 -0
- {doppy-0.0.3.dist-info → doppy-0.0.5.dist-info}/entry_points.txt +0 -0
- {doppy-0.0.3.dist-info → doppy-0.0.5.dist-info}/license_files/LICENSE +0 -0
doppy/product/__init__.py
CHANGED
doppy/product/stare.py
CHANGED
|
@@ -508,11 +508,7 @@ def _select_raws_for_stare(
|
|
|
508
508
|
raise doppy.exceptions.NoDataError("No data to select from")
|
|
509
509
|
|
|
510
510
|
# Select files that stare
|
|
511
|
-
raws_stare = [
|
|
512
|
-
raw
|
|
513
|
-
for raw in raws
|
|
514
|
-
if len(raw.azimuth_angles) == 1 or raw.azimuth_angles == {0, 360}
|
|
515
|
-
]
|
|
511
|
+
raws_stare = [raw for raw in raws if len(raw.azimuth_angles) == 1]
|
|
516
512
|
if len(raws_stare) == 0:
|
|
517
513
|
raise doppy.exceptions.NoDataError(
|
|
518
514
|
"No data suitable for stare product. Data is probably from scans"
|
doppy/product/wind.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from io import BufferedIOBase
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Sequence, TypeAlias
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import numpy.typing as npt
|
|
12
|
+
from scipy.ndimage import generic_filter
|
|
13
|
+
from sklearn.cluster import KMeans
|
|
14
|
+
|
|
15
|
+
import doppy
|
|
16
|
+
|
|
17
|
+
# ngates, elevation angle, tuple of sorted azimuth angles
|
|
18
|
+
SelectionGroupKeyType: TypeAlias = tuple[int, int, tuple[int, ...]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Wind:
|
|
23
|
+
time: npt.NDArray[np.datetime64]
|
|
24
|
+
height: npt.NDArray[np.float64]
|
|
25
|
+
zonal_wind: npt.NDArray[np.float64]
|
|
26
|
+
meridional_wind: npt.NDArray[np.float64]
|
|
27
|
+
vertical_wind: npt.NDArray[np.float64]
|
|
28
|
+
mask: npt.NDArray[np.bool_]
|
|
29
|
+
|
|
30
|
+
@functools.cached_property
|
|
31
|
+
def horizontal_wind_speed(self) -> npt.NDArray[np.float64]:
|
|
32
|
+
return np.sqrt(self.zonal_wind**2 + self.meridional_wind**2)
|
|
33
|
+
|
|
34
|
+
@functools.cached_property
|
|
35
|
+
def horizontal_wind_direction(self) -> npt.NDArray[np.float64]:
|
|
36
|
+
direction = np.arctan2(self.zonal_wind, self.meridional_wind)
|
|
37
|
+
direction[direction < 0] += 2 * np.pi
|
|
38
|
+
return np.array(np.degrees(direction), dtype=np.float64)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_halo_data(
|
|
42
|
+
cls,
|
|
43
|
+
data: Sequence[str]
|
|
44
|
+
| Sequence[Path]
|
|
45
|
+
| Sequence[bytes]
|
|
46
|
+
| Sequence[BufferedIOBase],
|
|
47
|
+
) -> Wind:
|
|
48
|
+
raws = doppy.raw.HaloHpl.from_srcs(data)
|
|
49
|
+
|
|
50
|
+
if len(raws) == 0:
|
|
51
|
+
raise doppy.exceptions.NoDataError("HaloHpl data missing")
|
|
52
|
+
|
|
53
|
+
raw = (
|
|
54
|
+
doppy.raw.HaloHpl.merge(_select_raws_for_wind(raws))
|
|
55
|
+
.sorted_by_time()
|
|
56
|
+
.non_strictly_increasing_timesteps_removed()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
groups = _group_scans(raw)
|
|
60
|
+
time_list = []
|
|
61
|
+
elevation_list = []
|
|
62
|
+
wind_list = []
|
|
63
|
+
rmse_list = []
|
|
64
|
+
|
|
65
|
+
for group_index in set(groups):
|
|
66
|
+
pick = group_index == groups
|
|
67
|
+
time_, elevation_, wind_, rmse_ = _compute_wind(raw[pick])
|
|
68
|
+
time_list.append(time_)
|
|
69
|
+
elevation_list.append(elevation_)
|
|
70
|
+
wind_list.append(wind_[np.newaxis, :, :])
|
|
71
|
+
rmse_list.append(rmse_[np.newaxis, :])
|
|
72
|
+
time = np.array(time_list)
|
|
73
|
+
elevation = np.array(elevation_list)
|
|
74
|
+
wind = np.concatenate(wind_list)
|
|
75
|
+
rmse = np.concatenate(rmse_list)
|
|
76
|
+
if not np.allclose(elevation, elevation[0]):
|
|
77
|
+
raise ValueError("Elevation is expected to stay same")
|
|
78
|
+
height = raw.radial_distance * np.sin(np.deg2rad(elevation[0]))
|
|
79
|
+
mask = _compute_mask(wind, rmse)
|
|
80
|
+
return Wind(
|
|
81
|
+
time=time,
|
|
82
|
+
height=height,
|
|
83
|
+
zonal_wind=wind[:, :, 0],
|
|
84
|
+
meridional_wind=wind[:, :, 1],
|
|
85
|
+
vertical_wind=wind[:, :, 2],
|
|
86
|
+
mask=mask,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _compute_wind(
|
|
91
|
+
raw: doppy.raw.HaloHpl,
|
|
92
|
+
) -> tuple[float, float, npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
93
|
+
"""
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
time
|
|
97
|
+
|
|
98
|
+
elevation
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
wind (range,component):
|
|
102
|
+
Wind components for each range gate.
|
|
103
|
+
Components:
|
|
104
|
+
0: zonal wind
|
|
105
|
+
1: meridional wind
|
|
106
|
+
2: vertical wind
|
|
107
|
+
|
|
108
|
+
rmse (range,):
|
|
109
|
+
Root-mean-square error of radial velocity fit for each range gate.
|
|
110
|
+
"""
|
|
111
|
+
elevation = np.deg2rad(raw.elevation)
|
|
112
|
+
azimuth = np.deg2rad(raw.azimuth)
|
|
113
|
+
radial_velocity = raw.radial_velocity
|
|
114
|
+
|
|
115
|
+
cos_elevation = np.cos(elevation)
|
|
116
|
+
A = np.hstack(
|
|
117
|
+
(
|
|
118
|
+
(np.sin(azimuth) * cos_elevation).reshape(-1, 1),
|
|
119
|
+
(np.cos(azimuth) * cos_elevation).reshape(-1, 1),
|
|
120
|
+
(np.sin(elevation)).reshape(-1, 1),
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
A_inv = np.linalg.pinv(A)
|
|
124
|
+
|
|
125
|
+
w = A_inv @ radial_velocity
|
|
126
|
+
r_appr = A @ w
|
|
127
|
+
rmse = np.sqrt(np.sum((r_appr - radial_velocity) ** 2, axis=0) / r_appr.shape[0])
|
|
128
|
+
wind = w.T
|
|
129
|
+
time = raw.time[len(raw.time) // 2]
|
|
130
|
+
elevation = np.round(raw.elevation)
|
|
131
|
+
if not np.allclose(elevation, elevation[0]):
|
|
132
|
+
raise ValueError("Elevations in the scan differ")
|
|
133
|
+
return time, elevation[0], wind, rmse
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _compute_mask(
|
|
137
|
+
wind: npt.NDArray[np.float64], rmse: npt.NDArray[np.float64]
|
|
138
|
+
) -> npt.NDArray[np.bool_]:
|
|
139
|
+
"""
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
|
|
143
|
+
wind (time,range,component)
|
|
144
|
+
intensty (time,range)
|
|
145
|
+
rmse (time,range)
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def neighbour_diff(X: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
149
|
+
mdiff = np.max(np.abs(X - X[len(X) // 2]))
|
|
150
|
+
return np.array(mdiff, dtype=np.float64)
|
|
151
|
+
|
|
152
|
+
WIND_NEIGHBOUR_DIFFERENCE = 20
|
|
153
|
+
neighbour_mask = np.any(
|
|
154
|
+
generic_filter(wind, neighbour_diff, size=(1, 3, 1))
|
|
155
|
+
> WIND_NEIGHBOUR_DIFFERENCE,
|
|
156
|
+
axis=2,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
rmse_th = 5
|
|
160
|
+
return np.array((rmse > rmse_th) | neighbour_mask, dtype=np.bool_)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _group_scans(raw: doppy.raw.HaloHpl) -> npt.NDArray[np.int64]:
|
|
164
|
+
if len(raw.time) < 4:
|
|
165
|
+
raise ValueError("Expected at least 4 profiles to compute wind profile")
|
|
166
|
+
if raw.time.dtype != "<M8[us]":
|
|
167
|
+
raise TypeError("time expected to be in numpy datetime[us]")
|
|
168
|
+
time = raw.time.astype(np.float64) * 1e-6
|
|
169
|
+
timediff_in_seconds = np.diff(time)
|
|
170
|
+
kmeans = KMeans(n_clusters=2, n_init="auto").fit(timediff_in_seconds.reshape(-1, 1))
|
|
171
|
+
centers = kmeans.cluster_centers_.flatten()
|
|
172
|
+
scanstep_timediff = centers[np.argmin(centers)]
|
|
173
|
+
|
|
174
|
+
if scanstep_timediff < 0.1 or scanstep_timediff > 30:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
"Time difference between profiles in one scan "
|
|
177
|
+
"expected to be between 0.1 and 30 seconds"
|
|
178
|
+
)
|
|
179
|
+
scanstep_timediff_upperbound = 2 * scanstep_timediff
|
|
180
|
+
groups_by_time = -1 * np.ones_like(time, dtype=np.int64)
|
|
181
|
+
groups_by_time[0] = 0
|
|
182
|
+
scan_index = 0
|
|
183
|
+
for i, (t_prev, t) in enumerate(zip(time[:-1], time[1:]), start=1):
|
|
184
|
+
if t - t_prev > scanstep_timediff_upperbound:
|
|
185
|
+
scan_index += 1
|
|
186
|
+
groups_by_time[i] = scan_index
|
|
187
|
+
|
|
188
|
+
return _subgroup_scans(raw, groups_by_time)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _subgroup_scans(
|
|
192
|
+
raw: doppy.raw.HaloHpl, time_groups: npt.NDArray[np.int64]
|
|
193
|
+
) -> npt.NDArray[np.int64]:
|
|
194
|
+
"""
|
|
195
|
+
Groups scans further based on the azimuth angles
|
|
196
|
+
"""
|
|
197
|
+
group = -1 * np.ones_like(raw.time, dtype=np.int64)
|
|
198
|
+
i = -1
|
|
199
|
+
for time_group in set(time_groups):
|
|
200
|
+
i += 1
|
|
201
|
+
(pick,) = np.where(time_group == time_groups)
|
|
202
|
+
raw_group = raw[pick]
|
|
203
|
+
first_azimuth_angle = int(np.round(raw_group.azimuth[0])) % 360
|
|
204
|
+
group[pick[0]] = i
|
|
205
|
+
for j, azi in enumerate(
|
|
206
|
+
(int(np.round(azi)) % 360 for azi in raw_group.azimuth[1:]), start=1
|
|
207
|
+
):
|
|
208
|
+
if azi == first_azimuth_angle:
|
|
209
|
+
i += 1
|
|
210
|
+
group[pick[j]] = i
|
|
211
|
+
return group
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _select_raws_for_wind(
|
|
215
|
+
raws: Sequence[doppy.raw.HaloHpl],
|
|
216
|
+
) -> Sequence[doppy.raw.HaloHpl]:
|
|
217
|
+
raws_wind = [
|
|
218
|
+
raw
|
|
219
|
+
for raw in raws
|
|
220
|
+
if len(raw.elevation_angles) == 1
|
|
221
|
+
and next(iter(raw.elevation_angles)) < 80
|
|
222
|
+
and len(raw.azimuth_angles) > 3
|
|
223
|
+
]
|
|
224
|
+
groups: dict[SelectionGroupKeyType, int] = defaultdict(int)
|
|
225
|
+
|
|
226
|
+
for raw in raws_wind:
|
|
227
|
+
groups[_selection_key(raw)] += len(raw.time)
|
|
228
|
+
|
|
229
|
+
def key_func(key: SelectionGroupKeyType) -> int:
|
|
230
|
+
return groups[key]
|
|
231
|
+
|
|
232
|
+
select_tuple = max(groups, key=key_func)
|
|
233
|
+
|
|
234
|
+
return [raw for raw in raws_wind if _selection_key(raw) == select_tuple]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _selection_key(raw: doppy.raw.HaloHpl) -> SelectionGroupKeyType:
|
|
238
|
+
if len(raw.elevation_angles) != 1:
|
|
239
|
+
raise ValueError("Expected only one elevation angle")
|
|
240
|
+
return (
|
|
241
|
+
raw.header.ngates,
|
|
242
|
+
next(iter(raw.elevation_angles)),
|
|
243
|
+
tuple(sorted(raw.azimuth_angles)),
|
|
244
|
+
)
|
doppy/raw/halo_hpl.py
CHANGED
|
@@ -149,7 +149,7 @@ class HaloHpl:
|
|
|
149
149
|
|
|
150
150
|
@functools.cached_property
|
|
151
151
|
def azimuth_angles(self) -> set[int]:
|
|
152
|
-
return set(int(x) for x in np.round(self.azimuth))
|
|
152
|
+
return set(int(x) % 360 for x in np.round(self.azimuth))
|
|
153
153
|
|
|
154
154
|
@functools.cached_property
|
|
155
155
|
def elevation_angles(self) -> set[int]:
|
doppy/rs.abi3.so
CHANGED
|
Binary file
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
doppy-0.0.
|
|
2
|
-
doppy-0.0.
|
|
3
|
-
doppy-0.0.
|
|
4
|
-
doppy-0.0.
|
|
5
|
-
doppy-0.0.
|
|
1
|
+
doppy-0.0.5.dist-info/METADATA,sha256=n_NUKr3yktaNmPOZY4ERv9-D9G9FfmE-c2jfwnRWdjI,1583
|
|
2
|
+
doppy-0.0.5.dist-info/WHEEL,sha256=6lWwZR4XDMZCOGKxNbitp3oy9XC3VeW-i3RwU9wYcOc,105
|
|
3
|
+
doppy-0.0.5.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
|
|
4
|
+
doppy-0.0.5.dist-info/license_files/LICENSE,sha256=V-0iroMNMI8ctnLgUau1kdFvwhkYhr9vi-5kWKxw2wc,1089
|
|
5
|
+
doppy-0.0.5.dist-info/license_files/LICENSE,sha256=V-0iroMNMI8ctnLgUau1kdFvwhkYhr9vi-5kWKxw2wc,1089
|
|
6
6
|
doppy/options.py,sha256=73BDODO4OYHn2qOshhwz6u6G3J1kNd3uj6P0a3V4HBE,205
|
|
7
7
|
doppy/__init__.py,sha256=Z9aEUlbPRWRUAoB8_-djkgrJuS4-6pjem4-mVSB6Z9I,191
|
|
8
|
-
doppy/product/
|
|
9
|
-
doppy/product/
|
|
8
|
+
doppy/product/wind.py,sha256=GA8tzceKYSlBg0ic-gwGXtiVBlpD85V-fDTSnxsZs9E,7573
|
|
9
|
+
doppy/product/__init__.py,sha256=lyp88zs7GBk8uUzkj8lxaXPuYwPa52xXeTwf5qwFddU,103
|
|
10
|
+
doppy/product/stare.py,sha256=rqZJkC-y_aDJFiVnZZj3UzRboR6rzbV3Tlggmzrl16w,19600
|
|
10
11
|
doppy/bench.py,sha256=iVNYveMVGGRES2oe3Orsn31jQFCKTXOmxRFuFiJ8_OA,248
|
|
11
12
|
doppy/netcdf.py,sha256=NW5zTW2OY-M4ApMzgxwN7ysmL0LRRWOfyKFPinCw6gg,3403
|
|
12
13
|
doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -19,7 +20,7 @@ doppy/data/exceptions.py,sha256=6CS6OHIWq8CqlxiceEvC1j0EfWUYoIfM7dW88apQVn4,89
|
|
|
19
20
|
doppy/raw/halo_sys_params.py,sha256=IXH40xBHyXCGX0ZE79KnSeXRj1wbqoqL0RYUQyBJqdE,3937
|
|
20
21
|
doppy/raw/__init__.py,sha256=GrBbsTkoD2DH_Xwt6apZGwpT1cCIbfOWHzdQVjAPVh0,151
|
|
21
22
|
doppy/raw/halo_bg.py,sha256=kO03yGlKS-DpMMGHYuy_BuidyeUL38TxT5vMn8H_8lE,4809
|
|
22
|
-
doppy/raw/halo_hpl.py,sha256=
|
|
23
|
+
doppy/raw/halo_hpl.py,sha256=Vp90S_nL9nhkQ4SkBNaFyr8i5kSWLzq6d50kvG3jW6I,17900
|
|
23
24
|
doppy/__main__.py,sha256=zrKQJVj0k0ypBQCGK65Czt9G9FZ_qx3ussw6Q9VJ14g,346
|
|
24
|
-
doppy/rs.abi3.so,sha256=
|
|
25
|
-
doppy-0.0.
|
|
25
|
+
doppy/rs.abi3.so,sha256=7gsnRLxHr7LW8LJn_ZuG4IGHX_ymtIqfJsz_Z8-_Cyg,2775816
|
|
26
|
+
doppy-0.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|