doppy 0.4.1__cp310-abi3-win_amd64.whl → 0.5.0__cp310-abi3-win_amd64.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/bench.py +3 -2
- doppy/data/api.py +1 -1
- doppy/netcdf.py +8 -4
- doppy/product/noise_utils.py +106 -0
- doppy/product/stare.py +60 -10
- doppy/product/stare_depol.py +31 -8
- doppy/product/turbulence.py +265 -0
- doppy/product/wind.py +9 -2
- doppy/raw/halo_hpl.py +6 -55
- doppy/raw/halo_sys_params.py +6 -6
- doppy/raw/utils.py +14 -0
- doppy/raw/windcube.py +4 -3
- doppy/raw/wls70.py +4 -36
- doppy/rs.pyd +0 -0
- {doppy-0.4.1.dist-info → doppy-0.5.0.dist-info}/METADATA +3 -2
- doppy-0.5.0.dist-info/RECORD +32 -0
- {doppy-0.4.1.dist-info → doppy-0.5.0.dist-info}/WHEEL +1 -1
- doppy-0.4.1.dist-info/RECORD +0 -29
- {doppy-0.4.1.dist-info → doppy-0.5.0.dist-info}/entry_points.txt +0 -0
- {doppy-0.4.1.dist-info → doppy-0.5.0.dist-info}/licenses/LICENSE +0 -0
doppy/bench.py
CHANGED
|
@@ -3,10 +3,11 @@ import time
|
|
|
3
3
|
|
|
4
4
|
class Timer:
|
|
5
5
|
def __init__(self):
|
|
6
|
-
self.start = None
|
|
6
|
+
self.start: float | None = None
|
|
7
7
|
|
|
8
8
|
def __enter__(self):
|
|
9
9
|
self.start = time.time()
|
|
10
10
|
|
|
11
11
|
def __exit__(self, type, value, traceback):
|
|
12
|
-
|
|
12
|
+
if isinstance(self.start, float):
|
|
13
|
+
print(f"Elapsed time: {time.time() - self.start:.2f}")
|
doppy/data/api.py
CHANGED
|
@@ -20,7 +20,7 @@ class Api:
|
|
|
20
20
|
self.api_endpoint = "https://cloudnet.fmi.fi/api"
|
|
21
21
|
self.cache = cache
|
|
22
22
|
|
|
23
|
-
def get(self, path: str, params: dict[str, str]) -> list:
|
|
23
|
+
def get(self, path: str, params: dict[str, str | list[str]]) -> list:
|
|
24
24
|
res = self.session.get(
|
|
25
25
|
f"{self.api_endpoint}/{path}", params=params, timeout=1800
|
|
26
26
|
)
|
doppy/netcdf.py
CHANGED
|
@@ -13,8 +13,12 @@ NetCDFDataType: TypeAlias = Literal["f4", "f8", "i4", "i8", "u4", "u8"]
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Dataset:
|
|
16
|
-
def __init__(
|
|
17
|
-
self
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
filename: str | pathlib.Path,
|
|
19
|
+
format: Literal["NETCDF4", "NETCDF4_CLASSIC"] = "NETCDF4",
|
|
20
|
+
) -> None:
|
|
21
|
+
self.nc = netCDF4.Dataset(filename, mode="w", format=format)
|
|
18
22
|
|
|
19
23
|
def __enter__(self) -> Dataset:
|
|
20
24
|
return self
|
|
@@ -27,8 +31,8 @@ class Dataset:
|
|
|
27
31
|
) -> None:
|
|
28
32
|
self.close()
|
|
29
33
|
|
|
30
|
-
def add_dimension(self, dim: str) -> Dataset:
|
|
31
|
-
self.nc.createDimension(dim,
|
|
34
|
+
def add_dimension(self, dim: str, size: int | None = None) -> Dataset:
|
|
35
|
+
self.nc.createDimension(dim, size)
|
|
32
36
|
return self
|
|
33
37
|
|
|
34
38
|
def add_attribute(self, key: str, val: str) -> Dataset:
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
import scipy
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def detect_wind_noise(
|
|
9
|
+
w: npt.NDArray[np.float64],
|
|
10
|
+
height: npt.NDArray[np.float64],
|
|
11
|
+
mask: npt.NDArray[np.bool_],
|
|
12
|
+
window: float = 150,
|
|
13
|
+
stride: int = 1,
|
|
14
|
+
) -> npt.NDArray[np.bool_]:
|
|
15
|
+
"""
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
w
|
|
19
|
+
vertical velocity, dims: (time,height)
|
|
20
|
+
|
|
21
|
+
height
|
|
22
|
+
dims: (height,)
|
|
23
|
+
|
|
24
|
+
mask
|
|
25
|
+
old mask that still contains noisy velocity data,
|
|
26
|
+
mask[t,h] = True iff w[t,h] is noise
|
|
27
|
+
|
|
28
|
+
window
|
|
29
|
+
size of window used to compute rolling median in meters
|
|
30
|
+
|
|
31
|
+
stride
|
|
32
|
+
stride used to compute rolling median
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
improved noise mask such that new_mask[t,h] = True iff w[t,h] is noise
|
|
37
|
+
"""
|
|
38
|
+
warnings.simplefilter("ignore", RuntimeWarning)
|
|
39
|
+
v = _rolling_median_over_range(
|
|
40
|
+
height,
|
|
41
|
+
w,
|
|
42
|
+
mask,
|
|
43
|
+
window=window, # meters
|
|
44
|
+
stride=stride,
|
|
45
|
+
fill_gaps=True,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
th = 2
|
|
49
|
+
diff = np.abs(v - w)
|
|
50
|
+
new_mask = (diff > th) | mask
|
|
51
|
+
new_mask = _remove_one_hot(new_mask)
|
|
52
|
+
return np.array(new_mask, dtype=np.bool_)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _remove_one_hot(m: npt.NDArray[np.bool_]) -> npt.NDArray[np.bool_]:
|
|
56
|
+
if m.ndim != 2:
|
|
57
|
+
raise ValueError
|
|
58
|
+
if m.shape[1] < 3:
|
|
59
|
+
return m
|
|
60
|
+
x = ~m
|
|
61
|
+
y = np.full(x.shape, np.False_)
|
|
62
|
+
y[:, 0] = x[:, 0] & x[:, 1]
|
|
63
|
+
y[:, 1:-1] = x[:, 1:-1] & (x[:, 2:] | x[:, :-2])
|
|
64
|
+
y[:, -1] = x[:, -1] & x[:, -2]
|
|
65
|
+
return np.array(~y, dtype=np.bool_)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _rolling_median_over_range(
|
|
69
|
+
range_: npt.NDArray[np.float64],
|
|
70
|
+
arr: npt.NDArray[np.float64],
|
|
71
|
+
mask: npt.NDArray[np.bool_],
|
|
72
|
+
window: float,
|
|
73
|
+
stride: int = 1,
|
|
74
|
+
fill_gaps: bool = False,
|
|
75
|
+
) -> npt.NDArray[np.float64]:
|
|
76
|
+
"""
|
|
77
|
+
window
|
|
78
|
+
range window in meters
|
|
79
|
+
"""
|
|
80
|
+
X = arr.T.copy()
|
|
81
|
+
X[mask.T] = np.nan
|
|
82
|
+
|
|
83
|
+
half_window = window / 2
|
|
84
|
+
|
|
85
|
+
i = 0
|
|
86
|
+
j = 0
|
|
87
|
+
n = len(range_)
|
|
88
|
+
med = np.full(X.shape, np.nan, dtype=np.float64)
|
|
89
|
+
for k in range(0, n, stride):
|
|
90
|
+
r = range_[k]
|
|
91
|
+
while i + 1 < n and r - range_[i + 1] >= half_window:
|
|
92
|
+
i += 1
|
|
93
|
+
while j + 1 < n and range_[j] - r < half_window:
|
|
94
|
+
j += 1
|
|
95
|
+
if i > k or j < k:
|
|
96
|
+
raise ValueError
|
|
97
|
+
med[k] = np.nanmedian(X[i : j + 1], axis=0)
|
|
98
|
+
|
|
99
|
+
if stride != 1 and fill_gaps:
|
|
100
|
+
ind = list(range(0, n, stride))
|
|
101
|
+
f_interp = scipy.interpolate.interp1d(
|
|
102
|
+
range_[ind], med[ind], axis=0, fill_value="extrapolate"
|
|
103
|
+
)
|
|
104
|
+
med_all = f_interp(range_)
|
|
105
|
+
return np.array(med_all.T.copy(), dtype=np.float64)
|
|
106
|
+
return np.array(med.T.copy(), dtype=np.float64)
|
doppy/product/stare.py
CHANGED
|
@@ -14,20 +14,35 @@ from sklearn.cluster import KMeans
|
|
|
14
14
|
|
|
15
15
|
import doppy
|
|
16
16
|
from doppy import defaults, options
|
|
17
|
+
from doppy.product.noise_utils import detect_wind_noise
|
|
17
18
|
|
|
18
19
|
SelectionGroupKeyType: TypeAlias = tuple[int,]
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
@dataclass
|
|
22
|
+
@dataclass(slots=True)
|
|
23
|
+
class RayAccumulationTime:
|
|
24
|
+
# in seconds
|
|
25
|
+
value: float
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(slots=True)
|
|
29
|
+
class PulsesPerRay:
|
|
30
|
+
value: int
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True)
|
|
22
34
|
class Stare:
|
|
23
35
|
time: npt.NDArray[np.datetime64]
|
|
24
36
|
radial_distance: npt.NDArray[np.float64]
|
|
25
37
|
elevation: npt.NDArray[np.float64]
|
|
26
38
|
beta: npt.NDArray[np.float64]
|
|
39
|
+
snr: npt.NDArray[np.float64]
|
|
27
40
|
radial_velocity: npt.NDArray[np.float64]
|
|
28
|
-
|
|
41
|
+
mask_beta: npt.NDArray[np.bool_]
|
|
42
|
+
mask_radial_velocity: npt.NDArray[np.bool_]
|
|
29
43
|
wavelength: float
|
|
30
44
|
system_id: str
|
|
45
|
+
ray_info: RayAccumulationTime | PulsesPerRay
|
|
31
46
|
|
|
32
47
|
def __getitem__(
|
|
33
48
|
self,
|
|
@@ -44,10 +59,13 @@ class Stare:
|
|
|
44
59
|
radial_distance=self.radial_distance,
|
|
45
60
|
elevation=self.elevation[index],
|
|
46
61
|
beta=self.beta[index],
|
|
62
|
+
snr=self.snr[index],
|
|
47
63
|
radial_velocity=self.radial_velocity[index],
|
|
48
|
-
|
|
64
|
+
mask_beta=self.mask_beta[index],
|
|
65
|
+
mask_radial_velocity=self.mask_radial_velocity[index],
|
|
49
66
|
wavelength=self.wavelength,
|
|
50
67
|
system_id=self.system_id,
|
|
68
|
+
ray_info=self.ray_info,
|
|
51
69
|
)
|
|
52
70
|
raise TypeError
|
|
53
71
|
|
|
@@ -79,16 +97,23 @@ class Stare:
|
|
|
79
97
|
effective_diameter=defaults.WindCube.effective_diameter,
|
|
80
98
|
)
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
mask_beta = _compute_noise_mask_for_windcube(raw)
|
|
101
|
+
mask_radial_velocity = detect_wind_noise(
|
|
102
|
+
raw.radial_velocity, raw.radial_distance, mask_beta
|
|
103
|
+
)
|
|
104
|
+
|
|
83
105
|
return cls(
|
|
84
106
|
time=raw.time,
|
|
85
107
|
radial_distance=raw.radial_distance,
|
|
86
108
|
elevation=raw.elevation,
|
|
87
109
|
beta=beta,
|
|
110
|
+
snr=raw.cnr,
|
|
88
111
|
radial_velocity=raw.radial_velocity,
|
|
89
|
-
|
|
112
|
+
mask_beta=mask_beta,
|
|
113
|
+
mask_radial_velocity=mask_radial_velocity,
|
|
90
114
|
wavelength=wavelength,
|
|
91
115
|
system_id=raw.system_id,
|
|
116
|
+
ray_info=RayAccumulationTime(raw.ray_accumulation_time),
|
|
92
117
|
)
|
|
93
118
|
|
|
94
119
|
@classmethod
|
|
@@ -146,18 +171,25 @@ class Stare:
|
|
|
146
171
|
effective_diameter=defaults.Halo.effective_diameter,
|
|
147
172
|
)
|
|
148
173
|
|
|
149
|
-
|
|
174
|
+
mask_beta = _compute_noise_mask(
|
|
150
175
|
intensity_noise_bias_corrected, raw.radial_velocity, raw.radial_distance
|
|
151
176
|
)
|
|
177
|
+
mask_radial_velocity = detect_wind_noise(
|
|
178
|
+
raw.radial_velocity, raw.radial_distance, mask_beta
|
|
179
|
+
)
|
|
180
|
+
|
|
152
181
|
return cls(
|
|
153
182
|
time=raw.time,
|
|
154
183
|
radial_distance=raw.radial_distance,
|
|
155
184
|
elevation=raw.elevation,
|
|
156
185
|
beta=beta,
|
|
186
|
+
snr=intensity_noise_bias_corrected - 1,
|
|
157
187
|
radial_velocity=raw.radial_velocity,
|
|
158
|
-
|
|
188
|
+
mask_beta=mask_beta,
|
|
189
|
+
mask_radial_velocity=mask_radial_velocity,
|
|
159
190
|
wavelength=wavelength,
|
|
160
191
|
system_id=raw.header.system_id,
|
|
192
|
+
ray_info=PulsesPerRay(raw.header.pulses_per_ray),
|
|
161
193
|
)
|
|
162
194
|
|
|
163
195
|
def write_to_netcdf(self, filename: str | Path) -> None:
|
|
@@ -200,7 +232,7 @@ class Stare:
|
|
|
200
232
|
units="sr-1 m-1",
|
|
201
233
|
data=self.beta,
|
|
202
234
|
dtype="f4",
|
|
203
|
-
mask=self.
|
|
235
|
+
mask=self.mask_beta,
|
|
204
236
|
)
|
|
205
237
|
nc.add_variable(
|
|
206
238
|
name="v",
|
|
@@ -209,7 +241,7 @@ class Stare:
|
|
|
209
241
|
long_name="Doppler velocity",
|
|
210
242
|
data=self.radial_velocity,
|
|
211
243
|
dtype="f4",
|
|
212
|
-
mask=self.
|
|
244
|
+
mask=self.mask_radial_velocity,
|
|
213
245
|
)
|
|
214
246
|
nc.add_scalar_variable(
|
|
215
247
|
name="wavelength",
|
|
@@ -218,6 +250,24 @@ class Stare:
|
|
|
218
250
|
data=self.wavelength,
|
|
219
251
|
dtype="f4",
|
|
220
252
|
)
|
|
253
|
+
match self.ray_info:
|
|
254
|
+
case RayAccumulationTime(value):
|
|
255
|
+
nc.add_scalar_variable(
|
|
256
|
+
name="ray_accumulation_time",
|
|
257
|
+
units="s",
|
|
258
|
+
long_name="ray accumulation time",
|
|
259
|
+
data=value,
|
|
260
|
+
dtype="f4",
|
|
261
|
+
)
|
|
262
|
+
case PulsesPerRay(value):
|
|
263
|
+
nc.add_scalar_variable(
|
|
264
|
+
name="pulses_per_ray",
|
|
265
|
+
units="1",
|
|
266
|
+
long_name="pulses per ray",
|
|
267
|
+
data=value,
|
|
268
|
+
dtype="u4",
|
|
269
|
+
)
|
|
270
|
+
|
|
221
271
|
nc.add_attribute("serial_number", self.system_id)
|
|
222
272
|
nc.add_attribute("doppy_version", doppy.__version__)
|
|
223
273
|
|
|
@@ -232,7 +282,7 @@ def _compute_noise_mask_for_windcube(
|
|
|
232
282
|
|
|
233
283
|
cnr = raw.cnr.copy()
|
|
234
284
|
cnr[mask] = np.finfo(float).eps
|
|
235
|
-
cnr_filt = median_filter(cnr, size=(3, 3))
|
|
285
|
+
cnr_filt = np.array(median_filter(cnr, size=(3, 3)), dtype=np.float64)
|
|
236
286
|
rel_diff = np.abs(cnr - cnr_filt) / np.abs(cnr)
|
|
237
287
|
diff_mask = rel_diff > 0.25
|
|
238
288
|
|
doppy/product/stare_depol.py
CHANGED
|
@@ -10,7 +10,7 @@ import numpy.typing as npt
|
|
|
10
10
|
|
|
11
11
|
import doppy
|
|
12
12
|
from doppy import options
|
|
13
|
-
from doppy.product.stare import Stare
|
|
13
|
+
from doppy.product.stare import PulsesPerRay, RayAccumulationTime, Stare
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass
|
|
@@ -36,7 +36,9 @@ class StareDepol:
|
|
|
36
36
|
sr-1 m-1.
|
|
37
37
|
radial_velocity
|
|
38
38
|
An array of radial velocities of the co-polarised signal, in m s-1.
|
|
39
|
-
|
|
39
|
+
mask_beta
|
|
40
|
+
A boolean array indicating signal (True) or noise (False) data points.
|
|
41
|
+
mask_radial_velocity
|
|
40
42
|
A boolean array indicating signal (True) or noise (False) data points.
|
|
41
43
|
depolarisation
|
|
42
44
|
An array of depolarisation ratios calculated as the ratio of
|
|
@@ -69,11 +71,13 @@ class StareDepol:
|
|
|
69
71
|
beta: npt.NDArray[np.float64]
|
|
70
72
|
beta_cross: npt.NDArray[np.float64]
|
|
71
73
|
radial_velocity: npt.NDArray[np.float64]
|
|
72
|
-
|
|
74
|
+
mask_beta: npt.NDArray[np.bool_]
|
|
75
|
+
mask_radial_velocity: npt.NDArray[np.bool_]
|
|
73
76
|
depolarisation: npt.NDArray[np.float64]
|
|
74
77
|
polariser_bleed_through: float
|
|
75
78
|
wavelength: float
|
|
76
79
|
system_id: str
|
|
80
|
+
ray_info: RayAccumulationTime | PulsesPerRay
|
|
77
81
|
|
|
78
82
|
def __init__(
|
|
79
83
|
self,
|
|
@@ -138,11 +142,13 @@ class StareDepol:
|
|
|
138
142
|
self.beta = co.beta
|
|
139
143
|
self.beta_cross = cross_beta
|
|
140
144
|
self.radial_velocity = co.radial_velocity
|
|
141
|
-
self.
|
|
145
|
+
self.mask_beta = co.mask_beta
|
|
146
|
+
self.mask_radial_velocity = co.mask_radial_velocity
|
|
142
147
|
self.depolarisation = depolarisation
|
|
143
148
|
self.polariser_bleed_through = polariser_bleed_through
|
|
144
149
|
self.wavelength = co.wavelength
|
|
145
150
|
self.system_id = co.system_id
|
|
151
|
+
self.ray_info = co.ray_info
|
|
146
152
|
|
|
147
153
|
@property
|
|
148
154
|
def mask_depolarisation(self) -> npt.NDArray[np.bool_]:
|
|
@@ -224,7 +230,7 @@ class StareDepol:
|
|
|
224
230
|
units="sr-1 m-1",
|
|
225
231
|
data=self.beta,
|
|
226
232
|
dtype="f4",
|
|
227
|
-
mask=self.
|
|
233
|
+
mask=self.mask_beta,
|
|
228
234
|
)
|
|
229
235
|
nc.add_variable(
|
|
230
236
|
name="v",
|
|
@@ -233,7 +239,7 @@ class StareDepol:
|
|
|
233
239
|
long_name="Doppler velocity",
|
|
234
240
|
data=self.radial_velocity,
|
|
235
241
|
dtype="f4",
|
|
236
|
-
mask=self.
|
|
242
|
+
mask=self.mask_radial_velocity,
|
|
237
243
|
)
|
|
238
244
|
nc.add_scalar_variable(
|
|
239
245
|
name="wavelength",
|
|
@@ -256,7 +262,7 @@ class StareDepol:
|
|
|
256
262
|
units="1",
|
|
257
263
|
data=self.depolarisation,
|
|
258
264
|
dtype="f4",
|
|
259
|
-
mask=self.
|
|
265
|
+
mask=self.mask_beta | self.mask_depolarisation,
|
|
260
266
|
)
|
|
261
267
|
nc.add_variable(
|
|
262
268
|
name="beta_cross_raw",
|
|
@@ -271,7 +277,7 @@ class StareDepol:
|
|
|
271
277
|
dimensions=("time", "range"),
|
|
272
278
|
units="sr-1 m-1",
|
|
273
279
|
data=self.beta_cross,
|
|
274
|
-
mask=self.
|
|
280
|
+
mask=self.mask_beta | self.mask_beta_cross,
|
|
275
281
|
dtype="f4",
|
|
276
282
|
)
|
|
277
283
|
nc.add_scalar_variable(
|
|
@@ -281,5 +287,22 @@ class StareDepol:
|
|
|
281
287
|
data=self.polariser_bleed_through,
|
|
282
288
|
dtype="f4",
|
|
283
289
|
)
|
|
290
|
+
match self.ray_info:
|
|
291
|
+
case RayAccumulationTime(value):
|
|
292
|
+
nc.add_scalar_variable(
|
|
293
|
+
name="ray_accumulation_time",
|
|
294
|
+
units="s",
|
|
295
|
+
long_name="ray accumulation time",
|
|
296
|
+
data=value,
|
|
297
|
+
dtype="f4",
|
|
298
|
+
)
|
|
299
|
+
case PulsesPerRay(value):
|
|
300
|
+
nc.add_scalar_variable(
|
|
301
|
+
name="pulses_per_ray",
|
|
302
|
+
units="1",
|
|
303
|
+
long_name="pulses per ray",
|
|
304
|
+
data=value,
|
|
305
|
+
dtype="u4",
|
|
306
|
+
)
|
|
284
307
|
nc.add_attribute("serial_number", self.system_id)
|
|
285
308
|
nc.add_attribute("doppy_version", doppy.__version__)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class HorizontalWind:
|
|
13
|
+
time: npt.NDArray[np.datetime64]
|
|
14
|
+
height: npt.NDArray[np.float64] # Height in meters from reference
|
|
15
|
+
V: npt.NDArray[np.float64] # Horizontal wind speed in m/s
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class VerticalWind:
|
|
20
|
+
time: npt.NDArray[np.datetime64]
|
|
21
|
+
height: npt.NDArray[np.float64] # Height in meters from reference
|
|
22
|
+
w: npt.NDArray[np.float64] # Vertical wind speed in m/s
|
|
23
|
+
mask: npt.NDArray[np.bool_] # mask[t,h] = True iff w[t,h] should be masked
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Options:
|
|
28
|
+
ray_accumulation_time: float # in seconds
|
|
29
|
+
period: float = 600 # period for computing the variance in seconds
|
|
30
|
+
beam_divergence: float = 33e-6 # radians
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Turbulence:
|
|
35
|
+
time: npt.NDArray[np.datetime64]
|
|
36
|
+
height: npt.NDArray[np.float64]
|
|
37
|
+
turbulent_kinetic_energy_dissipation_rate: npt.NDArray[np.float64]
|
|
38
|
+
mask: npt.NDArray[np.bool_]
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_winds(
|
|
42
|
+
cls, vert: VerticalWind, hori: HorizontalWind, options: Options
|
|
43
|
+
) -> Turbulence:
|
|
44
|
+
V = _preprocess_horiontal_wind(vert, hori, options)
|
|
45
|
+
ls_low = _length_scale_low(V, vert.height, options)
|
|
46
|
+
res = _compute_variance(vert, options.period)
|
|
47
|
+
sampling_time = _sampling_time_in_seconds(res)
|
|
48
|
+
ls_up = V * sampling_time
|
|
49
|
+
dissipation_rate = _compute_dissipation_rate(res.variance, ls_low, ls_up)
|
|
50
|
+
mask = np.isnan(dissipation_rate) | vert.mask
|
|
51
|
+
return cls(
|
|
52
|
+
time=vert.time.copy(),
|
|
53
|
+
height=vert.height.copy(),
|
|
54
|
+
turbulent_kinetic_energy_dissipation_rate=dissipation_rate,
|
|
55
|
+
mask=mask,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _sampling_time_in_seconds(r: VarResult) -> npt.NDArray[np.float64]:
|
|
60
|
+
if not all(
|
|
61
|
+
(
|
|
62
|
+
t == np.dtype("datetime64[us]")
|
|
63
|
+
for t in (r.period_start.dtype, r.period_stop.dtype)
|
|
64
|
+
)
|
|
65
|
+
):
|
|
66
|
+
raise ValueError("period times must be on datetime64[us]")
|
|
67
|
+
td = r.period_stop - r.period_start
|
|
68
|
+
td_in_seconds = td / np.timedelta64(1, "s")
|
|
69
|
+
return np.array(td_in_seconds, dtype=np.float64)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class VarResult:
|
|
74
|
+
variance: npt.NDArray[np.float64]
|
|
75
|
+
period_start: npt.NDArray[np.datetime64]
|
|
76
|
+
period_stop: npt.NDArray[np.datetime64]
|
|
77
|
+
nsamples: npt.NDArray[np.int64]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _compute_variance(vert: VerticalWind, period: float) -> VarResult:
|
|
81
|
+
# NOTE: numerically unstable
|
|
82
|
+
|
|
83
|
+
# To compute actual time window
|
|
84
|
+
next_valid = _next_valid_from_mask(vert.mask)
|
|
85
|
+
prev_valid = _prev_valid_from_mask(vert.mask)
|
|
86
|
+
|
|
87
|
+
X = vert.w.copy()
|
|
88
|
+
X[vert.mask] = 0
|
|
89
|
+
X2 = X**2
|
|
90
|
+
X_cumsum = X.cumsum(axis=0)
|
|
91
|
+
X2_cumsum = X2.cumsum(axis=0)
|
|
92
|
+
|
|
93
|
+
N_i = (~vert.mask).astype(int)
|
|
94
|
+
N_cumsum = N_i.cumsum(axis=0)
|
|
95
|
+
|
|
96
|
+
def N_func(i: int, j: int) -> npt.NDArray[np.float64]:
|
|
97
|
+
return N_cumsum[j] - N_cumsum[i] + N_i[i]
|
|
98
|
+
|
|
99
|
+
def S(i: int, j: int) -> npt.NDArray[np.float64]:
|
|
100
|
+
return X_cumsum[j] - X_cumsum[i] + X[i]
|
|
101
|
+
|
|
102
|
+
def S2(i: int, j: int) -> npt.NDArray[np.float64]:
|
|
103
|
+
return X2_cumsum[j] - X2_cumsum[i] + X2[i]
|
|
104
|
+
|
|
105
|
+
def var_ij(i: int, j: int) -> npt.NDArray[np.float64]:
|
|
106
|
+
N = N_func(i, j)
|
|
107
|
+
with np.errstate(invalid="ignore"):
|
|
108
|
+
return (S2(i, j) - S(i, j) ** 2 / N) / N
|
|
109
|
+
|
|
110
|
+
half_period = np.timedelta64(int(1e6 * period / 2), "us")
|
|
111
|
+
period_start = np.full(vert.w.shape, np.datetime64("NaT", "us"))
|
|
112
|
+
period_stop = np.full(vert.w.shape, np.datetime64("NaT", "us"))
|
|
113
|
+
var = np.full(vert.w.shape, np.nan, dtype=np.float64)
|
|
114
|
+
nsamples = np.zeros_like(vert.w, dtype=np.int64)
|
|
115
|
+
i = 0
|
|
116
|
+
j = 0
|
|
117
|
+
n = len(vert.time)
|
|
118
|
+
for k, t in enumerate(vert.time):
|
|
119
|
+
while i + 1 < n and t - vert.time[i + 1] >= half_period:
|
|
120
|
+
i += 1
|
|
121
|
+
while j + 1 < n and vert.time[j] - t < half_period:
|
|
122
|
+
j += 1
|
|
123
|
+
i_valid = next_valid[i]
|
|
124
|
+
i_inbound = (0 <= i_valid) & (i_valid < n)
|
|
125
|
+
j_valid = prev_valid[j]
|
|
126
|
+
j_inbound = (0 <= j_valid) & (j_valid < n)
|
|
127
|
+
period_start[k][i_inbound] = vert.time[i_valid[i_inbound]]
|
|
128
|
+
period_stop[k][j_inbound] = vert.time[j_valid[j_inbound]]
|
|
129
|
+
var[k] = var_ij(i, j)
|
|
130
|
+
nsamples[k] = N_func(i, j)
|
|
131
|
+
return VarResult(
|
|
132
|
+
variance=var,
|
|
133
|
+
period_start=period_start,
|
|
134
|
+
period_stop=period_stop,
|
|
135
|
+
nsamples=nsamples,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _length_scale_low(
|
|
140
|
+
V: npt.NDArray[np.float64], height: npt.NDArray[np.float64], opts: Options
|
|
141
|
+
) -> npt.NDArray[np.float64]:
|
|
142
|
+
integration_time = opts.ray_accumulation_time
|
|
143
|
+
from_beam = 2 * height * np.sin(opts.beam_divergence / 2)
|
|
144
|
+
from_wind = V * integration_time
|
|
145
|
+
return np.array(from_wind + from_beam[np.newaxis, :], dtype=np.float64)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _preprocess_horiontal_wind(
|
|
149
|
+
vert: VerticalWind, hori: HorizontalWind, options: Options
|
|
150
|
+
):
|
|
151
|
+
if np.isnan(hori.V).any():
|
|
152
|
+
raise ValueError("horizontal wind speed cannot contains NaNs")
|
|
153
|
+
trg_points = np.meshgrid(vert.time, vert.height, indexing="ij")
|
|
154
|
+
src_points = (hori.time, hori.height)
|
|
155
|
+
src_vals = hori.V
|
|
156
|
+
|
|
157
|
+
interp_nearest = RegularGridInterpolator(
|
|
158
|
+
src_points,
|
|
159
|
+
src_vals,
|
|
160
|
+
method="nearest",
|
|
161
|
+
bounds_error=False,
|
|
162
|
+
fill_value=None,
|
|
163
|
+
)
|
|
164
|
+
interp_linear = RegularGridInterpolator(
|
|
165
|
+
src_points, src_vals, method="linear", bounds_error=False
|
|
166
|
+
)
|
|
167
|
+
V_nearest = interp_nearest(trg_points)
|
|
168
|
+
V_linear = interp_linear(trg_points)
|
|
169
|
+
V = V_linear
|
|
170
|
+
V[np.isnan(V)] = V_nearest[np.isnan(V)]
|
|
171
|
+
if np.isnan(V).any():
|
|
172
|
+
raise ValueError("Unexpected NaNs")
|
|
173
|
+
V_rmean = _rolling_mean_over_time(vert.time, V, options.period)
|
|
174
|
+
return V_rmean
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _rolling_mean_over_time(
|
|
178
|
+
time: npt.NDArray[np.datetime64], arr: npt.NDArray[np.float64], period: float
|
|
179
|
+
) -> npt.NDArray[np.float64]:
|
|
180
|
+
if arr.ndim != 2:
|
|
181
|
+
raise ValueError("number of dims on arr should be 2")
|
|
182
|
+
if time.ndim != 1 or time.shape[0] != arr.shape[0]:
|
|
183
|
+
raise ValueError("time and arr dimensions do not match")
|
|
184
|
+
if time.dtype != np.dtype("datetime64[us]"):
|
|
185
|
+
raise TypeError(f"Invalid time type: {time.dtype}")
|
|
186
|
+
|
|
187
|
+
S = arr.cumsum(axis=0)
|
|
188
|
+
|
|
189
|
+
def rolling_mean(i: int, j: int) -> npt.NDArray[np.flaat64]:
|
|
190
|
+
return (S[j] - S[i] + arr[i]) / (j - i + 1)
|
|
191
|
+
|
|
192
|
+
half_period = np.timedelta64(int(period * 0.5e6), "us")
|
|
193
|
+
rol_mean = np.full(arr.shape, np.nan, dtype=np.float64)
|
|
194
|
+
|
|
195
|
+
i = 0
|
|
196
|
+
j = 0
|
|
197
|
+
n = len(time)
|
|
198
|
+
for k, t in enumerate(time):
|
|
199
|
+
while i + 1 < n and t - time[i + 1] >= half_period:
|
|
200
|
+
i += 1
|
|
201
|
+
while j + 1 < n and time[j] - t < half_period:
|
|
202
|
+
j += 1
|
|
203
|
+
rol_mean[k] = rolling_mean(i, j)
|
|
204
|
+
return rol_mean
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _compute_dissipation_rate(
|
|
208
|
+
variance: npt.NDArray[np.float64],
|
|
209
|
+
length_scale_lower: npt.NDArray[np.float64],
|
|
210
|
+
length_scale_upper: npt.NDArray[np.float64],
|
|
211
|
+
):
|
|
212
|
+
"""
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
variance, length_scale_lower, and length_scale_upper
|
|
216
|
+
dimensions: (time,range)
|
|
217
|
+
"""
|
|
218
|
+
kolmogorov_constant = 0.55
|
|
219
|
+
with np.errstate(invalid="ignore"):
|
|
220
|
+
dr = (
|
|
221
|
+
2
|
|
222
|
+
* np.pi
|
|
223
|
+
* (2 / (3 * kolmogorov_constant)) ** (3 / 2)
|
|
224
|
+
* variance ** (3 / 2)
|
|
225
|
+
* (length_scale_upper ** (2 / 3) - length_scale_lower ** (2 / 3))
|
|
226
|
+
** (-3 / 2)
|
|
227
|
+
)
|
|
228
|
+
return dr
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _next_valid_from_mask(mask):
|
|
232
|
+
"""
|
|
233
|
+
mask[t,v] (time,value)
|
|
234
|
+
|
|
235
|
+
returns N[t,v] = i where i = min { j | j >= t and mask[j,v] == False}
|
|
236
|
+
if the set is non empty and N[t,v] = len(mask) otherwise
|
|
237
|
+
"""
|
|
238
|
+
n = len(mask)
|
|
239
|
+
N = np.full(mask.shape, n)
|
|
240
|
+
if mask.size == 0:
|
|
241
|
+
return N
|
|
242
|
+
N[-1][~mask[-1]] = n - 1
|
|
243
|
+
|
|
244
|
+
for t in reversed(range(n - 1)):
|
|
245
|
+
N[t][~mask[t]] = t
|
|
246
|
+
N[t][mask[t]] = N[t + 1][mask[t]]
|
|
247
|
+
return N
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _prev_valid_from_mask(mask):
|
|
251
|
+
"""
|
|
252
|
+
mask[t,v] (time,value)
|
|
253
|
+
|
|
254
|
+
returns N[t,v] = i where i = max { j | j <= t and mask[j,v] == False}
|
|
255
|
+
if the set is non empty and N[t,v] = -1 otherwise
|
|
256
|
+
"""
|
|
257
|
+
n = len(mask)
|
|
258
|
+
N = np.full(mask.shape, -1)
|
|
259
|
+
if mask.size == 0:
|
|
260
|
+
return N
|
|
261
|
+
N[0][~mask[0]] = 0
|
|
262
|
+
for t in range(1, n):
|
|
263
|
+
N[t][~mask[t]] = t
|
|
264
|
+
N[t][mask[t]] = N[t - 1][mask[t]]
|
|
265
|
+
return N
|
doppy/product/wind.py
CHANGED
|
@@ -326,6 +326,13 @@ def _compute_wind(
|
|
|
326
326
|
|
|
327
327
|
rmse (range,):
|
|
328
328
|
Root-mean-square error of radial velocity fit for each range gate.
|
|
329
|
+
|
|
330
|
+
References
|
|
331
|
+
----------
|
|
332
|
+
An assessment of the performance of a 1.5 µm Doppler lidar for
|
|
333
|
+
operational vertical wind profiling based on a 1-year trial
|
|
334
|
+
authors: E. Päschke, R. Leinweber, and V. Lehmann
|
|
335
|
+
doi: 10.5194/amt-8-2251-2015
|
|
329
336
|
"""
|
|
330
337
|
elevation = np.deg2rad(raw.elevation)
|
|
331
338
|
azimuth = np.deg2rad(raw.azimuth)
|
|
@@ -364,9 +371,9 @@ def _compute_mask(
|
|
|
364
371
|
rmse (time,range)
|
|
365
372
|
"""
|
|
366
373
|
|
|
367
|
-
def neighbour_diff(X: npt.NDArray[np.float64]) ->
|
|
374
|
+
def neighbour_diff(X: npt.NDArray[np.float64]) -> np.float64:
|
|
368
375
|
mdiff = np.max(np.abs(X - X[len(X) // 2]))
|
|
369
|
-
return np.
|
|
376
|
+
return np.float64(mdiff)
|
|
370
377
|
|
|
371
378
|
WIND_NEIGHBOUR_DIFFERENCE = 20
|
|
372
379
|
neighbour_mask = np.any(
|
doppy/raw/halo_hpl.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
|
-
import io
|
|
5
4
|
import re
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from datetime import datetime, timedelta, timezone
|
|
@@ -16,6 +15,7 @@ from numpy import datetime64, timedelta64
|
|
|
16
15
|
|
|
17
16
|
import doppy
|
|
18
17
|
from doppy import exceptions
|
|
18
|
+
from doppy.raw.utils import bytes_from_src
|
|
19
19
|
from doppy.utils import merge_all_equal
|
|
20
20
|
|
|
21
21
|
|
|
@@ -35,30 +35,9 @@ class HaloHpl:
|
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
37
|
def from_srcs(
|
|
38
|
-
cls,
|
|
39
|
-
data: Sequence[str]
|
|
40
|
-
| Sequence[Path]
|
|
41
|
-
| Sequence[bytes]
|
|
42
|
-
| Sequence[BufferedIOBase],
|
|
38
|
+
cls, data: Sequence[str | bytes | Path | BufferedIOBase]
|
|
43
39
|
) -> list[HaloHpl]:
|
|
44
|
-
|
|
45
|
-
raise TypeError("data should be list or tuple")
|
|
46
|
-
if all(isinstance(src, bytes) for src in data):
|
|
47
|
-
data_bytes = data
|
|
48
|
-
elif all(isinstance(src, str) for src in data):
|
|
49
|
-
data_bytes = []
|
|
50
|
-
for src in data:
|
|
51
|
-
with Path(src).open("rb") as f:
|
|
52
|
-
data_bytes.append(f.read())
|
|
53
|
-
elif all(isinstance(src, Path) for src in data):
|
|
54
|
-
data_bytes = []
|
|
55
|
-
for src in data:
|
|
56
|
-
with src.open("rb") as f:
|
|
57
|
-
data_bytes.append(f.read())
|
|
58
|
-
elif all(isinstance(src, BufferedIOBase) for src in data):
|
|
59
|
-
data_bytes = [src.read() for src in data]
|
|
60
|
-
else:
|
|
61
|
-
raise TypeError("Unexpected types in data")
|
|
40
|
+
data_bytes = [bytes_from_src(src) for src in data]
|
|
62
41
|
raw_dicts = doppy.rs.raw.halo_hpl.from_bytes_srcs(data_bytes)
|
|
63
42
|
try:
|
|
64
43
|
return [_raw_tuple2halo_hpl(r) for r in raw_dicts]
|
|
@@ -67,40 +46,12 @@ class HaloHpl:
|
|
|
67
46
|
|
|
68
47
|
@classmethod
|
|
69
48
|
def from_src(cls, data: str | Path | bytes | BufferedIOBase) -> HaloHpl:
|
|
70
|
-
|
|
71
|
-
path = Path(data)
|
|
72
|
-
with path.open("rb") as f:
|
|
73
|
-
data_bytes = f.read()
|
|
74
|
-
elif isinstance(data, Path):
|
|
75
|
-
with data.open("rb") as f:
|
|
76
|
-
data_bytes = f.read()
|
|
77
|
-
elif isinstance(data, bytes):
|
|
78
|
-
data_bytes = data
|
|
79
|
-
elif isinstance(data, BufferedIOBase):
|
|
80
|
-
data_bytes = data.read()
|
|
81
|
-
else:
|
|
82
|
-
raise TypeError("Unsupported data type")
|
|
49
|
+
data_bytes = bytes_from_src(data)
|
|
83
50
|
try:
|
|
84
51
|
return _raw_tuple2halo_hpl(doppy.rs.raw.halo_hpl.from_bytes_src(data_bytes))
|
|
85
52
|
except RuntimeError as err:
|
|
86
53
|
raise exceptions.RawParsingError(err) from err
|
|
87
54
|
|
|
88
|
-
@classmethod
|
|
89
|
-
def _py_from_src(cls, data: str | Path | bytes | BufferedIOBase) -> HaloHpl:
|
|
90
|
-
if isinstance(data, str):
|
|
91
|
-
path = Path(data)
|
|
92
|
-
with path.open("rb") as f:
|
|
93
|
-
return _from_src(f)
|
|
94
|
-
elif isinstance(data, Path):
|
|
95
|
-
with data.open("rb") as f:
|
|
96
|
-
return _from_src(f)
|
|
97
|
-
elif isinstance(data, bytes):
|
|
98
|
-
return _from_src(io.BytesIO(data))
|
|
99
|
-
elif isinstance(data, BufferedIOBase):
|
|
100
|
-
return _from_src(data)
|
|
101
|
-
else:
|
|
102
|
-
raise TypeError("Unsupported data type")
|
|
103
|
-
|
|
104
55
|
def __getitem__(
|
|
105
56
|
self,
|
|
106
57
|
index: int
|
|
@@ -187,7 +138,7 @@ class HaloHpl:
|
|
|
187
138
|
return self[mask]
|
|
188
139
|
|
|
189
140
|
def nans_removed(self) -> HaloHpl:
|
|
190
|
-
is_ok = ~np.isnan(self.intensity).any(axis=1)
|
|
141
|
+
is_ok = np.array(~np.isnan(self.intensity).any(axis=1), dtype=np.bool_)
|
|
191
142
|
return self[is_ok]
|
|
192
143
|
|
|
193
144
|
|
|
@@ -381,7 +332,7 @@ def _convert_time(
|
|
|
381
332
|
decimal_time: hours since beginning of the day of start_time
|
|
382
333
|
"""
|
|
383
334
|
HOURS_TO_MICROSECONDS = 3600000000.0
|
|
384
|
-
start_of_day =
|
|
335
|
+
start_of_day = start_time.astype("datetime64[D]").astype("datetime64[us]")
|
|
385
336
|
delta_hours = (decimal_time * HOURS_TO_MICROSECONDS).astype("timedelta64[us]")
|
|
386
337
|
return np.array(start_of_day + delta_hours, dtype=datetime64)
|
|
387
338
|
|
doppy/raw/halo_sys_params.py
CHANGED
|
@@ -20,8 +20,8 @@ class HaloSysParams:
|
|
|
20
20
|
internal_relative_humidity: npt.NDArray[np.float64] # dim: (time, )
|
|
21
21
|
supply_voltage: npt.NDArray[np.float64] # dim: (time, )
|
|
22
22
|
acquisition_card_temperature: npt.NDArray[np.float64] # dim: (time, )
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
platform_pitch_angle: npt.NDArray[np.float64] # dim: (time, ), unit: degrees
|
|
24
|
+
platform_roll_angle: npt.NDArray[np.float64] # dim: (time, ), unit: degrees
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def from_src(cls, data: str | Path | bytes | BufferedIOBase) -> HaloSysParams:
|
|
@@ -47,8 +47,8 @@ class HaloSysParams:
|
|
|
47
47
|
np.concatenate(tuple(r.internal_relative_humidity for r in raws)),
|
|
48
48
|
np.concatenate(tuple(r.supply_voltage for r in raws)),
|
|
49
49
|
np.concatenate(tuple(r.acquisition_card_temperature for r in raws)),
|
|
50
|
-
np.concatenate(tuple(r.
|
|
51
|
-
np.concatenate(tuple(r.
|
|
50
|
+
np.concatenate(tuple(r.platform_pitch_angle for r in raws)),
|
|
51
|
+
np.concatenate(tuple(r.platform_roll_angle for r in raws)),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
def __getitem__(
|
|
@@ -62,8 +62,8 @@ class HaloSysParams:
|
|
|
62
62
|
self.internal_relative_humidity[index],
|
|
63
63
|
self.supply_voltage[index],
|
|
64
64
|
self.acquisition_card_temperature[index],
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
65
|
+
self.platform_pitch_angle[index],
|
|
66
|
+
self.platform_roll_angle[index],
|
|
67
67
|
)
|
|
68
68
|
raise TypeError
|
|
69
69
|
|
doppy/raw/utils.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from io import BufferedIOBase
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def bytes_from_src(src: str | bytes | Path | BufferedIOBase) -> bytes:
|
|
6
|
+
if isinstance(src, (str, Path)):
|
|
7
|
+
with open(src, "rb") as f:
|
|
8
|
+
return f.read()
|
|
9
|
+
elif isinstance(src, bytes):
|
|
10
|
+
return src
|
|
11
|
+
elif isinstance(src, BufferedIOBase):
|
|
12
|
+
return src.read()
|
|
13
|
+
else:
|
|
14
|
+
raise TypeError(f"Unexpected type {type(src)} for src")
|
doppy/raw/windcube.py
CHANGED
|
@@ -24,7 +24,7 @@ class WindCubeFixed:
|
|
|
24
24
|
radial_velocity: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
25
25
|
doppler_spectrum_width: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
26
26
|
radial_velocity_confidence: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
27
|
-
ray_accumulation_time: np.float64 # dim: ()
|
|
27
|
+
ray_accumulation_time: np.float64 # dim: (), unit: seconds
|
|
28
28
|
system_id: str
|
|
29
29
|
|
|
30
30
|
@classmethod
|
|
@@ -97,7 +97,7 @@ class WindCubeFixed:
|
|
|
97
97
|
return self[sort_indices]
|
|
98
98
|
|
|
99
99
|
def nan_profiles_removed(self) -> WindCubeFixed:
|
|
100
|
-
return self[~np.all(np.isnan(self.cnr), axis=1)]
|
|
100
|
+
return self[np.array(~np.all(np.isnan(self.cnr), axis=1), dtype=np.bool)]
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
@dataclass
|
|
@@ -300,6 +300,7 @@ def _from_fixed_src(nc: Dataset) -> WindCubeFixed:
|
|
|
300
300
|
_extract_float64_or_raise(
|
|
301
301
|
group["ray_accumulation_time"], expected_dimensions
|
|
302
302
|
)
|
|
303
|
+
* 1e-3 # convert ms to s
|
|
303
304
|
)
|
|
304
305
|
|
|
305
306
|
return WindCubeFixed(
|
|
@@ -314,7 +315,7 @@ def _from_fixed_src(nc: Dataset) -> WindCubeFixed:
|
|
|
314
315
|
doppler_spectrum_width=np.concatenate(doppler_spectrum_width_list),
|
|
315
316
|
ray_accumulation_time=merge_all_equal(
|
|
316
317
|
"ray_accumulation_time",
|
|
317
|
-
np.array(ray_accumulation_time_list, dtype=np.float64)
|
|
318
|
+
list(np.array(ray_accumulation_time_list, dtype=np.float64)),
|
|
318
319
|
),
|
|
319
320
|
system_id=nc.instrument_name,
|
|
320
321
|
)
|
doppy/raw/wls70.py
CHANGED
|
@@ -12,6 +12,7 @@ from numpy import datetime64
|
|
|
12
12
|
|
|
13
13
|
import doppy
|
|
14
14
|
from doppy import exceptions
|
|
15
|
+
from doppy.raw.utils import bytes_from_src
|
|
15
16
|
from doppy.utils import merge_all_equal
|
|
16
17
|
|
|
17
18
|
|
|
@@ -37,30 +38,9 @@ class Wls70:
|
|
|
37
38
|
|
|
38
39
|
@classmethod
|
|
39
40
|
def from_srcs(
|
|
40
|
-
cls,
|
|
41
|
-
data: Sequence[str]
|
|
42
|
-
| Sequence[Path]
|
|
43
|
-
| Sequence[bytes]
|
|
44
|
-
| Sequence[BufferedIOBase],
|
|
41
|
+
cls, data: Sequence[str | bytes | Path | BufferedIOBase]
|
|
45
42
|
) -> list[Wls70]:
|
|
46
|
-
|
|
47
|
-
raise TypeError("data should be list or tuple")
|
|
48
|
-
if all(isinstance(src, bytes) for src in data):
|
|
49
|
-
data_bytes = data
|
|
50
|
-
elif all(isinstance(src, str) for src in data):
|
|
51
|
-
data_bytes = []
|
|
52
|
-
for src in data:
|
|
53
|
-
with Path(src).open("rb") as f:
|
|
54
|
-
data_bytes.append(f.read())
|
|
55
|
-
elif all(isinstance(src, Path) for src in data):
|
|
56
|
-
data_bytes = []
|
|
57
|
-
for src in data:
|
|
58
|
-
with src.open("rb") as f:
|
|
59
|
-
data_bytes.append(f.read())
|
|
60
|
-
elif all(isinstance(src, BufferedIOBase) for src in data):
|
|
61
|
-
data_bytes = [src.read() for src in data]
|
|
62
|
-
else:
|
|
63
|
-
raise TypeError("Unexpected types in data")
|
|
43
|
+
data_bytes = [bytes_from_src(src) for src in data]
|
|
64
44
|
raws = doppy.rs.raw.wls70.from_bytes_srcs(data_bytes)
|
|
65
45
|
try:
|
|
66
46
|
return [_raw_rs_to_wls70(r) for r in raws]
|
|
@@ -69,19 +49,7 @@ class Wls70:
|
|
|
69
49
|
|
|
70
50
|
@classmethod
|
|
71
51
|
def from_src(cls, data: str | Path | bytes | BufferedIOBase) -> Wls70:
|
|
72
|
-
|
|
73
|
-
path = Path(data)
|
|
74
|
-
with path.open("rb") as f:
|
|
75
|
-
data_bytes = f.read()
|
|
76
|
-
elif isinstance(data, Path):
|
|
77
|
-
with data.open("rb") as f:
|
|
78
|
-
data_bytes = f.read()
|
|
79
|
-
elif isinstance(data, bytes):
|
|
80
|
-
data_bytes = data
|
|
81
|
-
elif isinstance(data, BufferedIOBase):
|
|
82
|
-
data_bytes = data.read()
|
|
83
|
-
else:
|
|
84
|
-
raise TypeError("Unsupported data type")
|
|
52
|
+
data_bytes = bytes_from_src(data)
|
|
85
53
|
try:
|
|
86
54
|
return _raw_rs_to_wls70(doppy.rs.raw.wls70.from_bytes_src(data_bytes))
|
|
87
55
|
except RuntimeError as err:
|
doppy/rs.pyd
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: doppy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -19,11 +19,12 @@ Requires-Dist: matplotlib
|
|
|
19
19
|
Requires-Dist: scikit-learn
|
|
20
20
|
Requires-Dist: scipy
|
|
21
21
|
Requires-Dist: mypy ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pyright ; extra == 'dev'
|
|
22
23
|
Requires-Dist: ruff ; extra == 'dev'
|
|
23
24
|
Requires-Dist: pytest ; extra == 'dev'
|
|
24
25
|
Requires-Dist: types-requests ; extra == 'dev'
|
|
25
26
|
Requires-Dist: py-spy ; extra == 'dev'
|
|
26
|
-
Requires-Dist: maturin
|
|
27
|
+
Requires-Dist: maturin==1.8 ; extra == 'dev'
|
|
27
28
|
Requires-Dist: release-version ; extra == 'dev'
|
|
28
29
|
Requires-Dist: pre-commit ; extra == 'dev'
|
|
29
30
|
Requires-Dist: xarray[io] ; extra == 'dev'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
doppy-0.5.0.dist-info/METADATA,sha256=WBN2SRmIlip8lZCFIOTt2XB3lQThw406jgHb8i7uGS0,4414
|
|
2
|
+
doppy-0.5.0.dist-info/WHEEL,sha256=md9qofgGs0CN4-5nDhVd0IzxxzM8cFGCnkeRThUNGbI,95
|
|
3
|
+
doppy-0.5.0.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
|
|
4
|
+
doppy-0.5.0.dist-info/licenses/LICENSE,sha256=RIAxFjJLTw0wQ3_SM73JoTeppoD99DJJ72cjvVuRrW4,1110
|
|
5
|
+
doppy/bench.py,sha256=X4yPXFPAM708ptzXKDYxVBOC0E3cMj_KxXzmD_Q9_RM,321
|
|
6
|
+
doppy/data/api.py,sha256=IoLRodfITNHs4Pb2IvVvOjSirSeK-xNoSO4tsQYjsKc,1933
|
|
7
|
+
doppy/data/cache.py,sha256=ERFzH-KjLLXV8fCePsewTepKZXmLwtXxeazmHiFdA80,1203
|
|
8
|
+
doppy/data/exceptions.py,sha256=JOyekvUO-Ew4ZVezf3_IxZOrPN0IksfUILd8R2YcSts,95
|
|
9
|
+
doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
doppy/defaults.py,sha256=jZvGmHJkvOzFpwy97L6Sj9-bnF0-nvq0-zNNWxM_WpE,490
|
|
11
|
+
doppy/exceptions.py,sha256=IUF8D_d-QOvtemUSEMg1raXDWA3dSXQ2MUBvlwgm1rU,197
|
|
12
|
+
doppy/netcdf.py,sha256=1mydrGnBBb4IxKCeVrXbwsS0PoOCObww3PfJbVciFWA,4188
|
|
13
|
+
doppy/options.py,sha256=uyIKM_G2GtbmV6Gve8o13eIShQqUwsnYZ41mhX2ypGE,218
|
|
14
|
+
doppy/product/noise_utils.py,sha256=Bro3T8uMY9MmJcrXbOl_QKIU7OXVRUs_R6Norl5Tv3Q,2735
|
|
15
|
+
doppy/product/stare.py,sha256=A_Xy6nIScnCUlEoziZ164aIW1HVqE7WogCcL5Ljpc_o,28009
|
|
16
|
+
doppy/product/stare_depol.py,sha256=GkS-60Izk-rF9PnHPtOgAssr--K4ddzQHUlyLZ7JNkU,11026
|
|
17
|
+
doppy/product/turbulence.py,sha256=IpqZ5ayxUrIX4J6C-3AcLTMem4OLHquKtvEC3zSEpkg,8516
|
|
18
|
+
doppy/product/wind.py,sha256=SbVM_AyLJtIggOb3PHLTak6majHaBMFJHNX36wRti1k,17298
|
|
19
|
+
doppy/product/__init__.py,sha256=xoBEXuhid-bvoof5Ogzpt1dKIhJgNMRyrAinCqUOUUI,241
|
|
20
|
+
doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
doppy/raw/halo_bg.py,sha256=TrqJDUXa1atW2w4UDN9PPx2Mzrc7D9Q2Ndph0yB65p0,5988
|
|
22
|
+
doppy/raw/halo_hpl.py,sha256=ap9WGBSHCYtLDZ89TaTXbHJHIwT6y3y-U0wO1kCR0o8,17304
|
|
23
|
+
doppy/raw/halo_sys_params.py,sha256=9aZpAgaLFniADjYv7EO8wKtmBD6McJP2v7YUz94hrTM,5385
|
|
24
|
+
doppy/raw/utils.py,sha256=pBeBk87_LMxop14_RK5Xj64iYBMu8AhH-IqBRoGgUzE,436
|
|
25
|
+
doppy/raw/windcube.py,sha256=FBoiLHXNluN2HISe6OO5TE32BtZYTuvSwyvcYIafLtE,19626
|
|
26
|
+
doppy/raw/wls70.py,sha256=lWSnuZlmdCGqrlYt6SUDFvPRBOOdtt6LiDiu_nhnPiU,6494
|
|
27
|
+
doppy/raw/__init__.py,sha256=ycT-_mCiI19b2TH33L74-Eq_MYUTPe3B8NhwNodSx1E,267
|
|
28
|
+
doppy/utils.py,sha256=2PwDiO8GyMjsHC-EYcTGyN2mEfPaCi3hhEn2twETOl0,814
|
|
29
|
+
doppy/__init__.py,sha256=Af7_8p3oN1nTqS9fo0mVKVuiKf5CAEK69uQa32CSFBA,197
|
|
30
|
+
doppy/__main__.py,sha256=38hIWWfanILuBBGorQiAaleSC4qYJoIxuzVBkxf7Dng,371
|
|
31
|
+
doppy/rs.pyd,sha256=ZYCEafdpDXk_D8aBrwTA4lRvBXqY-4Y9GhAJIJtI0Sc,2164736
|
|
32
|
+
doppy-0.5.0.dist-info/RECORD,,
|
doppy-0.4.1.dist-info/RECORD
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
doppy-0.4.1.dist-info/METADATA,sha256=1GDlAOhuqFL8D874SwK8J86zaMKRaI9FJZnUHy951N4,4375
|
|
2
|
-
doppy-0.4.1.dist-info/WHEEL,sha256=ff_aotEPDeP8au5CC0c1B8Qp-UHc3TEJkBgH6hZZ_jU,95
|
|
3
|
-
doppy-0.4.1.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
|
|
4
|
-
doppy-0.4.1.dist-info/licenses/LICENSE,sha256=RIAxFjJLTw0wQ3_SM73JoTeppoD99DJJ72cjvVuRrW4,1110
|
|
5
|
-
doppy/bench.py,sha256=fLN2iS5mmoYH4qZjD80Vl1h9lp3C-KDfhj9fteWRPtM,260
|
|
6
|
-
doppy/data/api.py,sha256=c3zbZI3IV7HnzhyFzJpUPOFT20eYFuQlJ5K_1ZEe1Pg,1921
|
|
7
|
-
doppy/data/cache.py,sha256=ERFzH-KjLLXV8fCePsewTepKZXmLwtXxeazmHiFdA80,1203
|
|
8
|
-
doppy/data/exceptions.py,sha256=JOyekvUO-Ew4ZVezf3_IxZOrPN0IksfUILd8R2YcSts,95
|
|
9
|
-
doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
doppy/defaults.py,sha256=jZvGmHJkvOzFpwy97L6Sj9-bnF0-nvq0-zNNWxM_WpE,490
|
|
11
|
-
doppy/exceptions.py,sha256=IUF8D_d-QOvtemUSEMg1raXDWA3dSXQ2MUBvlwgm1rU,197
|
|
12
|
-
doppy/netcdf.py,sha256=W5YPbsv6CQ2Ycy4sFaIyjfn9YjvDgygczm8j9E1_uAg,4054
|
|
13
|
-
doppy/options.py,sha256=uyIKM_G2GtbmV6Gve8o13eIShQqUwsnYZ41mhX2ypGE,218
|
|
14
|
-
doppy/product/stare.py,sha256=d5RnRD5tUWkbfKgdUhlpX13tXfY6BSkQ9uiANH_38b0,26122
|
|
15
|
-
doppy/product/stare_depol.py,sha256=D0bVRZT4EP-pYHE84lioUxiuB3a2Cl6LvOgCE-YPJZY,9938
|
|
16
|
-
doppy/product/wind.py,sha256=lGkvGf_QeOXtfT5kQEq5GuxKuSsLxTGVwRzoioF525o,17061
|
|
17
|
-
doppy/product/__init__.py,sha256=xoBEXuhid-bvoof5Ogzpt1dKIhJgNMRyrAinCqUOUUI,241
|
|
18
|
-
doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
doppy/raw/halo_bg.py,sha256=TrqJDUXa1atW2w4UDN9PPx2Mzrc7D9Q2Ndph0yB65p0,5988
|
|
20
|
-
doppy/raw/halo_hpl.py,sha256=l9i_TTJ4BY5eE3qAEAiLKK-mXd-a2BseEyDUMvMEanI,19105
|
|
21
|
-
doppy/raw/halo_sys_params.py,sha256=JBBQykXC9ozut_e4EvuTIAspUmjnB7FShCJoRiNEm54,5265
|
|
22
|
-
doppy/raw/windcube.py,sha256=LB7Do9Bn4ygBy8tMBM9Rc1ZZKU_6WX88UHELKiTPJfQ,19550
|
|
23
|
-
doppy/raw/wls70.py,sha256=J3ABVj_YrjSgmyRt6DGNoZUZIEZEzEoKaY5yiN9hE94,7717
|
|
24
|
-
doppy/raw/__init__.py,sha256=ycT-_mCiI19b2TH33L74-Eq_MYUTPe3B8NhwNodSx1E,267
|
|
25
|
-
doppy/utils.py,sha256=2PwDiO8GyMjsHC-EYcTGyN2mEfPaCi3hhEn2twETOl0,814
|
|
26
|
-
doppy/__init__.py,sha256=Af7_8p3oN1nTqS9fo0mVKVuiKf5CAEK69uQa32CSFBA,197
|
|
27
|
-
doppy/__main__.py,sha256=38hIWWfanILuBBGorQiAaleSC4qYJoIxuzVBkxf7Dng,371
|
|
28
|
-
doppy/rs.pyd,sha256=2BsMpXq8hxnXADpS9lXWiLZN6nxiu5_5iFw1p4GhUQQ,2130944
|
|
29
|
-
doppy-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|