doppy 0.3.6__cp310-abi3-macosx_10_12_x86_64.whl → 0.4.0__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/defaults.py +17 -1
- doppy/product/stare.py +96 -14
- doppy/raw/__init__.py +2 -2
- doppy/raw/halo_hpl.py +6 -2
- doppy/raw/windcube.py +249 -32
- doppy/raw/wls70.py +7 -2
- doppy/rs.abi3.so +0 -0
- doppy/utils.py +16 -1
- {doppy-0.3.6.dist-info → doppy-0.4.0.dist-info}/METADATA +4 -2
- {doppy-0.3.6.dist-info → doppy-0.4.0.dist-info}/RECORD +13 -13
- {doppy-0.3.6.dist-info → doppy-0.4.0.dist-info}/WHEEL +1 -1
- {doppy-0.3.6.dist-info → doppy-0.4.0.dist-info}/entry_points.txt +0 -0
- {doppy-0.3.6.dist-info → doppy-0.4.0.dist-info}/licenses/LICENSE +0 -0
doppy/defaults.py
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
+
DEFAULT_BEAM_ENERGY = 1e-5
|
|
2
|
+
DEFAULT_EFFECTIVE_DIAMETER = 25e-3
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
class Halo:
|
|
2
|
-
wavelength = 1.565e-6
|
|
6
|
+
wavelength = 1.565e-6 # [m]
|
|
7
|
+
receiver_bandwidth = 50e6 # [Hz]
|
|
8
|
+
beam_energy = DEFAULT_BEAM_ENERGY
|
|
9
|
+
effective_diameter = DEFAULT_EFFECTIVE_DIAMETER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WindCube:
|
|
13
|
+
# https://doi.org/10.5194/essd-13-3539-2021
|
|
14
|
+
wavelength = 1.54e-6 # [m]
|
|
15
|
+
receiver_bandwidth = 55e6 # [Hz]
|
|
16
|
+
beam_energy = DEFAULT_BEAM_ENERGY
|
|
17
|
+
effective_diameter = 50e-3 # [m]
|
|
18
|
+
focus = 1e3 # [m]
|
doppy/product/stare.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Sequence, Tuple, TypeAlias
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import numpy.typing as npt
|
|
11
11
|
import scipy
|
|
12
|
-
from scipy.ndimage import uniform_filter
|
|
12
|
+
from scipy.ndimage import median_filter, uniform_filter
|
|
13
13
|
from sklearn.cluster import KMeans
|
|
14
14
|
|
|
15
15
|
import doppy
|
|
@@ -51,6 +51,46 @@ class Stare:
|
|
|
51
51
|
)
|
|
52
52
|
raise TypeError
|
|
53
53
|
|
|
54
|
+
@classmethod
|
|
55
|
+
def mask_nan(cls, x: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]:
|
|
56
|
+
return np.isnan(x)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_windcube_data(
|
|
60
|
+
cls,
|
|
61
|
+
data: Sequence[str]
|
|
62
|
+
| Sequence[Path]
|
|
63
|
+
| Sequence[bytes]
|
|
64
|
+
| Sequence[BufferedIOBase],
|
|
65
|
+
) -> Stare:
|
|
66
|
+
raws = doppy.raw.WindCubeFixed.from_srcs(data)
|
|
67
|
+
raw = (
|
|
68
|
+
doppy.raw.WindCubeFixed.merge(raws).sorted_by_time().nan_profiles_removed()
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
wavelength = defaults.WindCube.wavelength
|
|
72
|
+
beta = _compute_beta(
|
|
73
|
+
snr=raw.cnr,
|
|
74
|
+
radial_distance=raw.radial_distance,
|
|
75
|
+
wavelength=wavelength,
|
|
76
|
+
beam_energy=defaults.WindCube.beam_energy,
|
|
77
|
+
receiver_bandwidth=defaults.WindCube.receiver_bandwidth,
|
|
78
|
+
focus=defaults.WindCube.focus,
|
|
79
|
+
effective_diameter=defaults.WindCube.effective_diameter,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
mask = _compute_noise_mask_for_windcube(raw)
|
|
83
|
+
return cls(
|
|
84
|
+
time=raw.time,
|
|
85
|
+
radial_distance=raw.radial_distance,
|
|
86
|
+
elevation=raw.elevation,
|
|
87
|
+
beta=beta,
|
|
88
|
+
radial_velocity=raw.radial_velocity,
|
|
89
|
+
mask=mask,
|
|
90
|
+
wavelength=wavelength,
|
|
91
|
+
system_id=raw.system_id,
|
|
92
|
+
)
|
|
93
|
+
|
|
54
94
|
@classmethod
|
|
55
95
|
def from_halo_data(
|
|
56
96
|
cls,
|
|
@@ -95,16 +135,21 @@ class Stare:
|
|
|
95
135
|
raw, intensity_bg_corrected
|
|
96
136
|
)
|
|
97
137
|
wavelength = defaults.Halo.wavelength
|
|
138
|
+
|
|
98
139
|
beta = _compute_beta(
|
|
99
|
-
intensity_noise_bias_corrected,
|
|
100
|
-
raw.radial_distance,
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
snr=intensity_noise_bias_corrected - 1,
|
|
141
|
+
radial_distance=raw.radial_distance,
|
|
142
|
+
wavelength=wavelength,
|
|
143
|
+
beam_energy=defaults.Halo.beam_energy,
|
|
144
|
+
receiver_bandwidth=defaults.Halo.receiver_bandwidth,
|
|
145
|
+
focus=raw.header.focus_range,
|
|
146
|
+
effective_diameter=defaults.Halo.effective_diameter,
|
|
103
147
|
)
|
|
148
|
+
|
|
104
149
|
mask = _compute_noise_mask(
|
|
105
150
|
intensity_noise_bias_corrected, raw.radial_velocity, raw.radial_distance
|
|
106
151
|
)
|
|
107
|
-
return
|
|
152
|
+
return cls(
|
|
108
153
|
time=raw.time,
|
|
109
154
|
radial_distance=raw.radial_distance,
|
|
110
155
|
elevation=raw.elevation,
|
|
@@ -177,6 +222,36 @@ class Stare:
|
|
|
177
222
|
nc.add_attribute("doppy_version", doppy.__version__)
|
|
178
223
|
|
|
179
224
|
|
|
225
|
+
def _compute_noise_mask_for_windcube(
|
|
226
|
+
raw: doppy.raw.WindCubeFixed,
|
|
227
|
+
) -> npt.NDArray[np.bool_]:
|
|
228
|
+
if np.any(np.isnan(raw.cnr)) or np.any(np.isnan(raw.radial_velocity)):
|
|
229
|
+
raise ValueError("Unexpected nans in crn or radial_velocity")
|
|
230
|
+
|
|
231
|
+
mask = _mask_with_cnr_norm_dist(raw.cnr) | (np.abs(raw.radial_velocity) > 30)
|
|
232
|
+
|
|
233
|
+
cnr = raw.cnr.copy()
|
|
234
|
+
cnr[mask] = np.finfo(float).eps
|
|
235
|
+
cnr_filt = median_filter(cnr, size=(3, 3))
|
|
236
|
+
rel_diff = np.abs(cnr - cnr_filt) / np.abs(cnr)
|
|
237
|
+
diff_mask = rel_diff > 0.25
|
|
238
|
+
|
|
239
|
+
mask = mask | diff_mask
|
|
240
|
+
|
|
241
|
+
return np.array(mask, dtype=np.bool_)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _mask_with_cnr_norm_dist(cnr: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]:
|
|
245
|
+
th_trunc = -5.5
|
|
246
|
+
std_factor = 2
|
|
247
|
+
log_cnr = np.log(cnr)
|
|
248
|
+
log_cnr_trunc = log_cnr[log_cnr < th_trunc]
|
|
249
|
+
th_trunc_fit = np.percentile(log_cnr_trunc, 90)
|
|
250
|
+
log_cnr_for_fit = log_cnr_trunc[log_cnr_trunc < th_trunc_fit]
|
|
251
|
+
mean, std = scipy.stats.norm.fit(log_cnr_for_fit)
|
|
252
|
+
return np.array(np.log(cnr) < (mean + std_factor * std), dtype=np.bool_)
|
|
253
|
+
|
|
254
|
+
|
|
180
255
|
def _compute_noise_mask(
|
|
181
256
|
intensity: npt.NDArray[np.float64],
|
|
182
257
|
radial_velocity: npt.NDArray[np.float64],
|
|
@@ -197,14 +272,19 @@ def _compute_noise_mask(
|
|
|
197
272
|
|
|
198
273
|
|
|
199
274
|
def _compute_beta(
|
|
200
|
-
|
|
275
|
+
snr: npt.NDArray[np.float64],
|
|
201
276
|
radial_distance: npt.NDArray[np.float64],
|
|
202
|
-
focus: float,
|
|
203
277
|
wavelength: float,
|
|
278
|
+
beam_energy: float,
|
|
279
|
+
receiver_bandwidth: float,
|
|
280
|
+
focus: float,
|
|
281
|
+
effective_diameter: float,
|
|
204
282
|
) -> npt.NDArray[np.float64]:
|
|
205
283
|
"""
|
|
206
284
|
Parameters
|
|
207
285
|
----------
|
|
286
|
+
snr
|
|
287
|
+
for halo: intensity - 1
|
|
208
288
|
radial_distance
|
|
209
289
|
distance from the instrument
|
|
210
290
|
focus
|
|
@@ -236,22 +316,24 @@ def _compute_beta(
|
|
|
236
316
|
doi: https://doi.org/10.5194/amt-13-2849-2020
|
|
237
317
|
"""
|
|
238
318
|
|
|
239
|
-
snr = intensity - 1
|
|
240
319
|
h = scipy.constants.Planck
|
|
241
320
|
c = scipy.constants.speed_of_light
|
|
242
321
|
eta = 1
|
|
243
|
-
E =
|
|
244
|
-
B =
|
|
322
|
+
E = beam_energy
|
|
323
|
+
B = receiver_bandwidth
|
|
245
324
|
nu = c / wavelength
|
|
246
|
-
A_e = _compute_effective_receiver_energy(
|
|
325
|
+
A_e = _compute_effective_receiver_energy(
|
|
326
|
+
radial_distance, wavelength, focus, effective_diameter
|
|
327
|
+
)
|
|
247
328
|
beta = 2 * h * nu * B * radial_distance**2 * snr / (eta * c * E * A_e)
|
|
248
329
|
return np.array(beta, dtype=np.float64)
|
|
249
330
|
|
|
250
331
|
|
|
251
332
|
def _compute_effective_receiver_energy(
|
|
252
333
|
radial_distance: npt.NDArray[np.float64],
|
|
253
|
-
focus: float,
|
|
254
334
|
wavelength: float,
|
|
335
|
+
focus: float,
|
|
336
|
+
effective_diameter: float,
|
|
255
337
|
) -> npt.NDArray[np.float64]:
|
|
256
338
|
"""
|
|
257
339
|
NOTE
|
|
@@ -268,7 +350,7 @@ def _compute_effective_receiver_energy(
|
|
|
268
350
|
wavelength
|
|
269
351
|
laser wavelength
|
|
270
352
|
"""
|
|
271
|
-
D =
|
|
353
|
+
D = effective_diameter
|
|
272
354
|
return np.array(
|
|
273
355
|
np.pi
|
|
274
356
|
* D**2
|
doppy/raw/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .halo_bg import HaloBg
|
|
2
2
|
from .halo_hpl import HaloHpl
|
|
3
3
|
from .halo_sys_params import HaloSysParams
|
|
4
|
-
from .windcube import WindCube
|
|
4
|
+
from .windcube import WindCube, WindCubeFixed
|
|
5
5
|
from .wls70 import Wls70
|
|
6
6
|
|
|
7
|
-
__all__ = ["HaloHpl", "HaloBg", "HaloSysParams", "WindCube", "Wls70"]
|
|
7
|
+
__all__ = ["HaloHpl", "HaloBg", "HaloSysParams", "WindCube", "WindCubeFixed", "Wls70"]
|
doppy/raw/halo_hpl.py
CHANGED
|
@@ -4,7 +4,7 @@ import functools
|
|
|
4
4
|
import io
|
|
5
5
|
import re
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from datetime import datetime, timedelta
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
8
|
from io import BufferedIOBase
|
|
9
9
|
from os.path import commonprefix
|
|
10
10
|
from pathlib import Path
|
|
@@ -309,7 +309,11 @@ def _raw_tuple2halo_hpl(
|
|
|
309
309
|
resolution=float(header_dict["resolution"]),
|
|
310
310
|
scan_type=str(header_dict["scan_type"]),
|
|
311
311
|
focus_range=int(header_dict["focus_range"]),
|
|
312
|
-
start_time=datetime64(
|
|
312
|
+
start_time=datetime64(
|
|
313
|
+
datetime.fromtimestamp(header_dict["start_time"], timezone.utc).replace(
|
|
314
|
+
tzinfo=None
|
|
315
|
+
)
|
|
316
|
+
),
|
|
313
317
|
system_id=str(header_dict["system_id"]),
|
|
314
318
|
instrument_spectral_width=float(header_dict["instrument_spectral_width"])
|
|
315
319
|
if header_dict["instrument_spectral_width"] is not None
|
doppy/raw/windcube.py
CHANGED
|
@@ -7,17 +7,104 @@ from typing import Sequence
|
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import numpy.typing as npt
|
|
10
|
-
from netCDF4 import Dataset, num2date
|
|
10
|
+
from netCDF4 import Dataset, Variable, num2date
|
|
11
11
|
from numpy import datetime64
|
|
12
12
|
|
|
13
13
|
from doppy.utils import merge_all_equal
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
@dataclass
|
|
17
|
+
class WindCubeFixed:
|
|
18
|
+
time: npt.NDArray[datetime64] # dim: (time, )
|
|
19
|
+
radial_distance: npt.NDArray[np.float64] # dim: (radial_distance,)
|
|
20
|
+
azimuth: npt.NDArray[np.float64] # dim: (time, )
|
|
21
|
+
elevation: npt.NDArray[np.float64] # dim: (time, )
|
|
22
|
+
cnr: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
23
|
+
relative_beta: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
24
|
+
radial_velocity: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
25
|
+
doppler_spectrum_width: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
26
|
+
radial_velocity_confidence: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
27
|
+
ray_accumulation_time: np.float64 # dim: ()
|
|
28
|
+
system_id: str
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_srcs(
|
|
32
|
+
cls,
|
|
33
|
+
data: Sequence[str]
|
|
34
|
+
| Sequence[Path]
|
|
35
|
+
| Sequence[bytes]
|
|
36
|
+
| Sequence[BufferedIOBase],
|
|
37
|
+
) -> list[WindCubeFixed]:
|
|
38
|
+
return [WindCubeFixed.from_fixed_src(src) for src in data]
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_fixed_src(cls, data: str | Path | bytes | BufferedIOBase) -> WindCubeFixed:
|
|
42
|
+
data_bytes = _src_to_bytes(data)
|
|
43
|
+
nc = Dataset("inmemory.nc", "r", memory=data_bytes)
|
|
44
|
+
return _from_fixed_src(nc)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def merge(cls, raws: list[WindCubeFixed]) -> WindCubeFixed:
|
|
48
|
+
return WindCubeFixed(
|
|
49
|
+
time=np.concatenate([r.time for r in raws]),
|
|
50
|
+
radial_distance=_merge_radial_distance_for_fixed(
|
|
51
|
+
[r.radial_distance for r in raws]
|
|
52
|
+
),
|
|
53
|
+
azimuth=np.concatenate([r.azimuth for r in raws]),
|
|
54
|
+
elevation=np.concatenate([r.elevation for r in raws]),
|
|
55
|
+
radial_velocity=np.concatenate([r.radial_velocity for r in raws]),
|
|
56
|
+
radial_velocity_confidence=np.concatenate(
|
|
57
|
+
[r.radial_velocity_confidence for r in raws]
|
|
58
|
+
),
|
|
59
|
+
cnr=np.concatenate([r.cnr for r in raws]),
|
|
60
|
+
relative_beta=np.concatenate([r.relative_beta for r in raws]),
|
|
61
|
+
doppler_spectrum_width=np.concatenate(
|
|
62
|
+
[r.doppler_spectrum_width for r in raws]
|
|
63
|
+
),
|
|
64
|
+
ray_accumulation_time=merge_all_equal(
|
|
65
|
+
"ray_accumulation_time", [r.ray_accumulation_time for r in raws]
|
|
66
|
+
),
|
|
67
|
+
system_id=merge_all_equal("system_id", [r.system_id for r in raws]),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def __getitem__(
|
|
71
|
+
self,
|
|
72
|
+
index: int
|
|
73
|
+
| slice
|
|
74
|
+
| list[int]
|
|
75
|
+
| npt.NDArray[np.int64]
|
|
76
|
+
| npt.NDArray[np.bool_]
|
|
77
|
+
| tuple[slice, slice],
|
|
78
|
+
) -> WindCubeFixed:
|
|
79
|
+
if isinstance(index, (int, slice, list, np.ndarray)):
|
|
80
|
+
return WindCubeFixed(
|
|
81
|
+
time=self.time[index],
|
|
82
|
+
radial_distance=self.radial_distance,
|
|
83
|
+
azimuth=self.azimuth[index],
|
|
84
|
+
elevation=self.elevation[index],
|
|
85
|
+
radial_velocity=self.radial_velocity[index],
|
|
86
|
+
radial_velocity_confidence=self.radial_velocity_confidence[index],
|
|
87
|
+
cnr=self.cnr[index],
|
|
88
|
+
relative_beta=self.relative_beta[index],
|
|
89
|
+
doppler_spectrum_width=self.doppler_spectrum_width[index],
|
|
90
|
+
ray_accumulation_time=self.ray_accumulation_time,
|
|
91
|
+
system_id=self.system_id,
|
|
92
|
+
)
|
|
93
|
+
raise TypeError
|
|
94
|
+
|
|
95
|
+
def sorted_by_time(self) -> WindCubeFixed:
|
|
96
|
+
sort_indices = np.argsort(self.time)
|
|
97
|
+
return self[sort_indices]
|
|
98
|
+
|
|
99
|
+
def nan_profiles_removed(self) -> WindCubeFixed:
|
|
100
|
+
return self[~np.all(np.isnan(self.cnr), axis=1)]
|
|
101
|
+
|
|
102
|
+
|
|
16
103
|
@dataclass
|
|
17
104
|
class WindCube:
|
|
18
105
|
time: npt.NDArray[datetime64] # dim: (time, )
|
|
19
|
-
radial_distance: npt.NDArray[np.
|
|
20
|
-
height: npt.NDArray[np.
|
|
106
|
+
radial_distance: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
107
|
+
height: npt.NDArray[np.float64] # dim: (time,radial_distance)
|
|
21
108
|
azimuth: npt.NDArray[np.float64] # dim: (time, )
|
|
22
109
|
elevation: npt.NDArray[np.float64] # dim: (time, )
|
|
23
110
|
cnr: npt.NDArray[np.float64] # dim: (time, radial_distance)
|
|
@@ -128,6 +215,23 @@ def _merge_scan_index(index_list: list[npt.NDArray[np.int64]]) -> npt.NDArray[np
|
|
|
128
215
|
return np.concatenate(new_index_list)
|
|
129
216
|
|
|
130
217
|
|
|
218
|
+
def _merge_radial_distance_for_fixed(
|
|
219
|
+
radial_distance_list: list[npt.NDArray[np.float64]],
|
|
220
|
+
) -> npt.NDArray[np.float64]:
|
|
221
|
+
if len(radial_distance_list) == 0:
|
|
222
|
+
raise ValueError("cannot merge empty list")
|
|
223
|
+
if not all(
|
|
224
|
+
np.allclose(arr.shape, radial_distance_list[0].shape)
|
|
225
|
+
for arr in radial_distance_list
|
|
226
|
+
):
|
|
227
|
+
raise ValueError("Cannot merge radial distances with different shapes")
|
|
228
|
+
if not all(
|
|
229
|
+
np.allclose(arr, radial_distance_list[0]) for arr in radial_distance_list
|
|
230
|
+
):
|
|
231
|
+
raise ValueError("Cannot merge radial distances")
|
|
232
|
+
return radial_distance_list[0]
|
|
233
|
+
|
|
234
|
+
|
|
131
235
|
def _src_to_bytes(data: str | Path | bytes | BufferedIOBase) -> bytes:
|
|
132
236
|
if isinstance(data, str):
|
|
133
237
|
path = Path(data)
|
|
@@ -140,38 +244,127 @@ def _src_to_bytes(data: str | Path | bytes | BufferedIOBase) -> bytes:
|
|
|
140
244
|
return data
|
|
141
245
|
elif isinstance(data, BufferedIOBase):
|
|
142
246
|
return data.read()
|
|
143
|
-
raise TypeError("Unsupported data type")
|
|
144
247
|
|
|
145
248
|
|
|
146
|
-
def
|
|
147
|
-
scan_index_list = []
|
|
249
|
+
def _from_fixed_src(nc: Dataset) -> WindCubeFixed:
|
|
148
250
|
time_list = []
|
|
149
251
|
cnr_list = []
|
|
252
|
+
relative_beta_list = []
|
|
150
253
|
radial_wind_speed_list = []
|
|
151
254
|
radial_wind_speed_confidence_list = []
|
|
152
255
|
azimuth_list = []
|
|
153
256
|
elevation_list = []
|
|
154
257
|
range_list = []
|
|
155
|
-
|
|
258
|
+
doppler_spectrum_width_list = []
|
|
259
|
+
ray_accumulation_time_list = []
|
|
260
|
+
time_reference = (
|
|
261
|
+
nc["time_reference"][:] if "time_reference" in nc.variables else None
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expected_dimensions = ("time", "range")
|
|
265
|
+
for _, group in enumerate(
|
|
266
|
+
nc[group] for group in (nc.variables["sweep_group_name"][:])
|
|
267
|
+
):
|
|
268
|
+
time_reference_ = time_reference
|
|
269
|
+
if time_reference is None and "time_reference" in group.variables:
|
|
270
|
+
time_reference_ = group["time_reference"][:]
|
|
271
|
+
|
|
272
|
+
time_list.append(_extract_datetime64_or_raise(group["time"], time_reference_))
|
|
273
|
+
radial_wind_speed_list.append(
|
|
274
|
+
_extract_float64_or_raise(group["radial_wind_speed"], expected_dimensions)
|
|
275
|
+
)
|
|
276
|
+
cnr_list.append(_extract_float64_or_raise(group["cnr"], expected_dimensions))
|
|
277
|
+
relative_beta_list.append(
|
|
278
|
+
_extract_float64_or_raise(group["relative_beta"], expected_dimensions)
|
|
279
|
+
)
|
|
280
|
+
radial_wind_speed_confidence_list.append(
|
|
281
|
+
_extract_float64_or_raise(
|
|
282
|
+
group["radial_wind_speed_ci"], expected_dimensions
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
azimuth_list.append(
|
|
286
|
+
_extract_float64_or_raise(group["azimuth"], expected_dimensions)
|
|
287
|
+
)
|
|
288
|
+
elevation_list.append(
|
|
289
|
+
_extract_float64_or_raise(group["elevation"], expected_dimensions)
|
|
290
|
+
)
|
|
291
|
+
range_list.append(
|
|
292
|
+
_extract_float64_or_raise(group["range"], (expected_dimensions[1],))
|
|
293
|
+
)
|
|
294
|
+
doppler_spectrum_width_list.append(
|
|
295
|
+
_extract_float64_or_raise(
|
|
296
|
+
group["doppler_spectrum_width"], expected_dimensions
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
ray_accumulation_time_list.append(
|
|
300
|
+
_extract_float64_or_raise(
|
|
301
|
+
group["ray_accumulation_time"], expected_dimensions
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return WindCubeFixed(
|
|
306
|
+
time=np.concatenate(time_list),
|
|
307
|
+
radial_distance=np.concatenate(range_list),
|
|
308
|
+
azimuth=np.concatenate(azimuth_list),
|
|
309
|
+
elevation=np.concatenate(elevation_list),
|
|
310
|
+
radial_velocity=np.concatenate(radial_wind_speed_list),
|
|
311
|
+
radial_velocity_confidence=np.concatenate(radial_wind_speed_confidence_list),
|
|
312
|
+
cnr=np.concatenate(cnr_list),
|
|
313
|
+
relative_beta=np.concatenate(relative_beta_list),
|
|
314
|
+
doppler_spectrum_width=np.concatenate(doppler_spectrum_width_list),
|
|
315
|
+
ray_accumulation_time=merge_all_equal(
|
|
316
|
+
"ray_accumulation_time",
|
|
317
|
+
np.array(ray_accumulation_time_list, dtype=np.float64).tolist(),
|
|
318
|
+
),
|
|
319
|
+
system_id=nc.instrument_name,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _from_vad_or_dbs_src(nc: Dataset) -> WindCube:
|
|
324
|
+
scan_index_list: list[npt.NDArray[np.int64]] = []
|
|
325
|
+
time_list: list[npt.NDArray[np.datetime64]] = []
|
|
326
|
+
cnr_list: list[npt.NDArray[np.float64]] = []
|
|
327
|
+
radial_wind_speed_list: list[npt.NDArray[np.float64]] = []
|
|
328
|
+
radial_wind_speed_confidence_list: list[npt.NDArray[np.float64]] = []
|
|
329
|
+
azimuth_list: list[npt.NDArray[np.float64]] = []
|
|
330
|
+
elevation_list: list[npt.NDArray[np.float64]] = []
|
|
331
|
+
range_list: list[npt.NDArray[np.float64]] = []
|
|
332
|
+
height_list: list[npt.NDArray[np.float64]] = []
|
|
333
|
+
|
|
156
334
|
time_reference = (
|
|
157
335
|
nc["time_reference"][:] if "time_reference" in nc.variables else None
|
|
158
336
|
)
|
|
159
337
|
|
|
338
|
+
expected_dimensions = ("time", "gate_index")
|
|
160
339
|
for i, group in enumerate(
|
|
161
340
|
nc[group] for group in (nc.variables["sweep_group_name"][:])
|
|
162
341
|
):
|
|
163
|
-
|
|
342
|
+
time_reference_ = time_reference
|
|
343
|
+
if time_reference is None and "time_reference" in group.variables:
|
|
344
|
+
time_reference_ = group["time_reference"][:]
|
|
345
|
+
|
|
346
|
+
time_list.append(_extract_datetime64_or_raise(group["time"], time_reference_))
|
|
164
347
|
radial_wind_speed_list.append(
|
|
165
|
-
_extract_float64_or_raise(group["radial_wind_speed"])
|
|
348
|
+
_extract_float64_or_raise(group["radial_wind_speed"], expected_dimensions)
|
|
166
349
|
)
|
|
167
|
-
cnr_list.append(_extract_float64_or_raise(group["cnr"]))
|
|
350
|
+
cnr_list.append(_extract_float64_or_raise(group["cnr"], expected_dimensions))
|
|
168
351
|
radial_wind_speed_confidence_list.append(
|
|
169
|
-
_extract_float64_or_raise(
|
|
352
|
+
_extract_float64_or_raise(
|
|
353
|
+
group["radial_wind_speed_ci"], expected_dimensions
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
azimuth_list.append(
|
|
357
|
+
_extract_float64_or_raise(group["azimuth"], expected_dimensions)
|
|
358
|
+
)
|
|
359
|
+
elevation_list.append(
|
|
360
|
+
_extract_float64_or_raise(group["elevation"], expected_dimensions)
|
|
361
|
+
)
|
|
362
|
+
range_list.append(
|
|
363
|
+
_extract_float64_or_raise(group["range"], expected_dimensions)
|
|
364
|
+
)
|
|
365
|
+
height_list.append(
|
|
366
|
+
_extract_float64_or_raise(group["measurement_height"], expected_dimensions)
|
|
170
367
|
)
|
|
171
|
-
azimuth_list.append(_extract_float64_or_raise(group["azimuth"]))
|
|
172
|
-
elevation_list.append(_extract_float64_or_raise(group["elevation"]))
|
|
173
|
-
range_list.append(_extract_int64_or_raise(group["range"]))
|
|
174
|
-
height_list.append(_extract_int64_or_raise(group["measurement_height"]))
|
|
175
368
|
scan_index_list.append(np.full(group["time"][:].shape, i, dtype=np.int64))
|
|
176
369
|
|
|
177
370
|
return WindCube(
|
|
@@ -189,7 +382,7 @@ def _from_vad_or_dbs_src(nc: Dataset) -> WindCube:
|
|
|
189
382
|
|
|
190
383
|
|
|
191
384
|
def _extract_datetime64_or_raise(
|
|
192
|
-
nc:
|
|
385
|
+
nc: Variable[npt.NDArray[np.float64]], time_reference: str | None
|
|
193
386
|
) -> npt.NDArray[np.datetime64]:
|
|
194
387
|
match nc.name:
|
|
195
388
|
case "time":
|
|
@@ -207,18 +400,40 @@ def _extract_datetime64_or_raise(
|
|
|
207
400
|
raise ValueError(f"Unexpected variable name {nc.name}")
|
|
208
401
|
|
|
209
402
|
|
|
210
|
-
def
|
|
403
|
+
def _dB_to_ratio(decibels: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
404
|
+
return 10 ** (0.1 * decibels)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _extract_float64_or_raise(
|
|
408
|
+
nc: Variable[npt.NDArray[np.float64]], expected_dimensions: tuple[str, ...]
|
|
409
|
+
) -> npt.NDArray[np.float64]:
|
|
211
410
|
match nc.name:
|
|
411
|
+
case "range" | "measurement_height":
|
|
412
|
+
if nc.dimensions != expected_dimensions:
|
|
413
|
+
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
414
|
+
if nc.units != "m":
|
|
415
|
+
raise ValueError(f"Unexpected units for {nc.name}")
|
|
416
|
+
if nc[:].mask is not np.bool_(False):
|
|
417
|
+
raise ValueError
|
|
418
|
+
return np.array(nc[:].data, dtype=np.float64)
|
|
212
419
|
case "cnr":
|
|
213
|
-
if nc.dimensions !=
|
|
420
|
+
if nc.dimensions != expected_dimensions:
|
|
214
421
|
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
215
422
|
if nc.units != "dB":
|
|
216
423
|
raise ValueError(f"Unexpected units for {nc.name}")
|
|
217
424
|
if nc[:].mask is not np.bool_(False):
|
|
218
425
|
pass # ignore that array contains masked values
|
|
426
|
+
return _dB_to_ratio(np.array(nc[:].data, dtype=np.float64))
|
|
427
|
+
case "relative_beta":
|
|
428
|
+
if nc.dimensions != expected_dimensions:
|
|
429
|
+
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
430
|
+
if nc.units != "m-1 sr-1":
|
|
431
|
+
raise ValueError(f"Unexpected units for {nc.name}")
|
|
432
|
+
if nc[:].mask is not np.bool_(False):
|
|
433
|
+
pass # ignore that array contains masked values
|
|
219
434
|
return np.array(nc[:].data, dtype=np.float64)
|
|
220
435
|
case "radial_wind_speed":
|
|
221
|
-
if nc.dimensions !=
|
|
436
|
+
if nc.dimensions != expected_dimensions:
|
|
222
437
|
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
223
438
|
if nc.units != "m s-1":
|
|
224
439
|
raise ValueError(f"Unexpected units for {nc.name}")
|
|
@@ -226,7 +441,7 @@ def _extract_float64_or_raise(nc: Dataset) -> npt.NDArray[np.float64]:
|
|
|
226
441
|
pass # ignore that array contains masked values
|
|
227
442
|
return np.array(nc[:].data, dtype=np.float64)
|
|
228
443
|
case "radial_wind_speed_ci":
|
|
229
|
-
if nc.dimensions !=
|
|
444
|
+
if nc.dimensions != expected_dimensions:
|
|
230
445
|
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
231
446
|
if nc.units != "percent":
|
|
232
447
|
raise ValueError(f"Unexpected units for {nc.name}")
|
|
@@ -234,26 +449,28 @@ def _extract_float64_or_raise(nc: Dataset) -> npt.NDArray[np.float64]:
|
|
|
234
449
|
pass # ignore that array contains masked values
|
|
235
450
|
return np.array(nc[:].data, dtype=np.float64)
|
|
236
451
|
case "azimuth" | "elevation":
|
|
237
|
-
if nc.dimensions != (
|
|
452
|
+
if nc.dimensions != (expected_dimensions[0],):
|
|
238
453
|
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
239
454
|
if nc.units != "degrees":
|
|
240
455
|
raise ValueError(f"Unexpected units for {nc.name}")
|
|
241
456
|
if nc[:].mask is not np.bool_(False):
|
|
242
457
|
raise ValueError
|
|
243
458
|
return np.array(nc[:].data, dtype=np.float64)
|
|
244
|
-
case
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _extract_int64_or_raise(nc: Dataset) -> npt.NDArray[np.int64]:
|
|
249
|
-
match nc.name:
|
|
250
|
-
case "range" | "measurement_height":
|
|
251
|
-
if nc.dimensions != ("time", "gate_index"):
|
|
459
|
+
case "doppler_spectrum_width":
|
|
460
|
+
if nc.dimensions != expected_dimensions:
|
|
252
461
|
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
253
|
-
if nc.units != "m":
|
|
462
|
+
if nc.units != "m s-1":
|
|
254
463
|
raise ValueError(f"Unexpected units for {nc.name}")
|
|
255
464
|
if nc[:].mask is not np.bool_(False):
|
|
256
|
-
|
|
257
|
-
return np.array(nc[:].data, dtype=np.
|
|
465
|
+
pass # ignore that array contains masked values
|
|
466
|
+
return np.array(nc[:].data, dtype=np.float64)
|
|
467
|
+
case "ray_accumulation_time":
|
|
468
|
+
if nc.dimensions != ():
|
|
469
|
+
raise ValueError(f"Unexpected dimensions for {nc.name}")
|
|
470
|
+
if nc.units != "ms":
|
|
471
|
+
raise ValueError(f"Unexpected units for {nc.name}")
|
|
472
|
+
if nc[:].mask is not np.bool_(False):
|
|
473
|
+
raise ValueError(f"Variable {nc.name} contains masked values")
|
|
474
|
+
return np.array(nc[:].data, dtype=np.float64)
|
|
258
475
|
case _:
|
|
259
476
|
raise ValueError(f"Unexpected variable name {nc.name}")
|
doppy/raw/wls70.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
5
|
from io import BufferedIOBase
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Sequence
|
|
@@ -166,7 +166,12 @@ def _raw_rs_to_wls70(
|
|
|
166
166
|
cnr_threshold = float(info["cnr_threshold"])
|
|
167
167
|
data = data.reshape(-1, len(cols))
|
|
168
168
|
time_ts = data[:, 0]
|
|
169
|
-
time = np.array(
|
|
169
|
+
time = np.array(
|
|
170
|
+
[
|
|
171
|
+
datetime64(datetime.fromtimestamp(ts, timezone.utc).replace(tzinfo=None))
|
|
172
|
+
for ts in time_ts
|
|
173
|
+
]
|
|
174
|
+
)
|
|
170
175
|
|
|
171
176
|
position = data[:, 1]
|
|
172
177
|
temperature = data[:, 2]
|
doppy/rs.abi3.so
CHANGED
|
Binary file
|
doppy/utils.py
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
from typing import TypeVar
|
|
1
|
+
from typing import TypeVar, cast
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.typing import NDArray
|
|
2
5
|
|
|
3
6
|
T = TypeVar("T")
|
|
7
|
+
NT = TypeVar("NT", bound=np.generic)
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
def merge_all_equal(key: str, lst: list[T]) -> T:
|
|
7
11
|
if len(set(lst)) != 1:
|
|
8
12
|
raise ValueError(f"Cannot merge header key {key} values {lst}")
|
|
9
13
|
return lst[0]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def merge_all_close(key: str, lst: list[NDArray[NT]]) -> NT:
|
|
17
|
+
if len(lst) == 0:
|
|
18
|
+
raise ValueError(f"Cannot merge empty list for key {key}")
|
|
19
|
+
if any(arr.size == 0 for arr in lst):
|
|
20
|
+
raise ValueError(f"Cannot merge key {key}, one or more arrays are empty.")
|
|
21
|
+
arr = np.concatenate([arr.flatten() for arr in lst])
|
|
22
|
+
if not np.allclose(arr, arr[0]):
|
|
23
|
+
raise ValueError(f"Cannot merge key {key}, values are not close enough")
|
|
24
|
+
return cast(NT, arr[0])
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: doppy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
7
7
|
Classifier: Programming Language :: Python :: 3.11
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
11
|
Classifier: Intended Audience :: Science/Research
|
|
11
12
|
Classifier: Operating System :: OS Independent
|
|
@@ -25,6 +26,7 @@ Requires-Dist: py-spy ; extra == 'dev'
|
|
|
25
26
|
Requires-Dist: maturin ==1.4 ; extra == 'dev'
|
|
26
27
|
Requires-Dist: release-version ; extra == 'dev'
|
|
27
28
|
Requires-Dist: pre-commit ; extra == 'dev'
|
|
29
|
+
Requires-Dist: xarray[io] ; extra == 'dev'
|
|
28
30
|
Provides-Extra: dev
|
|
29
31
|
License-File: LICENSE
|
|
30
32
|
License-File: LICENSE
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
doppy-0.
|
|
2
|
-
doppy-0.
|
|
3
|
-
doppy-0.
|
|
4
|
-
doppy-0.
|
|
1
|
+
doppy-0.4.0.dist-info/METADATA,sha256=bnTR2j3JN6ZfhwrYNbhIj0i6HdM-zpergm-CCBQa_5I,4276
|
|
2
|
+
doppy-0.4.0.dist-info/WHEEL,sha256=J9wdJ6DlOtsCLbXTCN9C4tF9HRUjkxgtI13Jme_gk0Y,105
|
|
3
|
+
doppy-0.4.0.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
|
|
4
|
+
doppy-0.4.0.dist-info/licenses/LICENSE,sha256=V-0iroMNMI8ctnLgUau1kdFvwhkYhr9vi-5kWKxw2wc,1089
|
|
5
5
|
doppy/options.py,sha256=73BDODO4OYHn2qOshhwz6u6G3J1kNd3uj6P0a3V4HBE,205
|
|
6
6
|
doppy/__init__.py,sha256=Z9aEUlbPRWRUAoB8_-djkgrJuS4-6pjem4-mVSB6Z9I,191
|
|
7
7
|
doppy/product/wind.py,sha256=hkSzAyi4tfPoGwUx8iE3l65oCDyzl_tRgtF2loxaEpY,16557
|
|
8
8
|
doppy/product/__init__.py,sha256=C6s9cX20m9UwRsKo1lZH6TnYFfM5KmsX5MPUyShbgl4,235
|
|
9
9
|
doppy/product/stare_depol.py,sha256=thrWzCpvdH3AiA6gmR37vrH_pDACNY2QTtqPteJ2s8Y,9653
|
|
10
|
-
doppy/product/stare.py,sha256=
|
|
10
|
+
doppy/product/stare.py,sha256=TE6BrrXFuwv9-zLTYCCn3HNpNgjwdldbyU6qk1__FzA,25343
|
|
11
11
|
doppy/bench.py,sha256=iVNYveMVGGRES2oe3Orsn31jQFCKTXOmxRFuFiJ8_OA,248
|
|
12
12
|
doppy/netcdf.py,sha256=34TzB9UyypTwDpT2MfrRD14g2zGP3X-NuEU15mQGaUI,3925
|
|
13
|
-
doppy/utils.py,sha256=
|
|
13
|
+
doppy/utils.py,sha256=cY-tWrArrROX7lg_zJ2M4QJ6GUnU_BjhkUYL5aG9kbA,790
|
|
14
14
|
doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
doppy/exceptions.py,sha256=OzdLXmKs3qZrvzwaI0pxjzpM2w9J5Of7dCo_Ekygecc,183
|
|
16
|
-
doppy/defaults.py,sha256=-
|
|
16
|
+
doppy/defaults.py,sha256=-jR_xAVLf_soZNDu3uJ6mhOZa9L1tfKtt0k0tI6Rd9s,472
|
|
17
17
|
doppy/data/cache.py,sha256=cxCZ7HyEQt2EKAGEiMqx3wJ2-5Y3hEAEPQ_XB4gKlkk,1160
|
|
18
18
|
doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
doppy/data/api.py,sha256=QaVKj304OPcu8OF5xgtduQzDis8Srn-I6UgR9qb2u9E,1863
|
|
20
20
|
doppy/data/exceptions.py,sha256=6CS6OHIWq8CqlxiceEvC1j0EfWUYoIfM7dW88apQVn4,89
|
|
21
21
|
doppy/raw/halo_sys_params.py,sha256=DeOIIRtVdO7HnNY3FaI4u035ScI65jfDgUrEccLepEo,3937
|
|
22
|
-
doppy/raw/__init__.py,sha256=
|
|
23
|
-
doppy/raw/wls70.py,sha256=
|
|
22
|
+
doppy/raw/__init__.py,sha256=4pj4xSK9rA0XEfM79NsRBRYId7GaJRDCyPABxbEm63g,260
|
|
23
|
+
doppy/raw/wls70.py,sha256=TePgyhmlWaUQCw1vkWh7uD7KT5EFMaDhNeVYoF2rspc,7510
|
|
24
24
|
doppy/raw/halo_bg.py,sha256=8t9j-SUF1yJht3vrT6KAYJQyxcg3W-0zr8h0jAEhWes,5815
|
|
25
|
-
doppy/raw/halo_hpl.py,sha256=
|
|
26
|
-
doppy/raw/windcube.py,sha256=
|
|
25
|
+
doppy/raw/halo_hpl.py,sha256=yzVPQEATnDVf1K3N-mGLXR4_RuT-E94OVXathT2bHes,18580
|
|
26
|
+
doppy/raw/windcube.py,sha256=vgkoXVgcbI2Or4BsFCeq2STdjUP49h3mBQLoDXdBQHM,19074
|
|
27
27
|
doppy/__main__.py,sha256=zrKQJVj0k0ypBQCGK65Czt9G9FZ_qx3ussw6Q9VJ14g,346
|
|
28
|
-
doppy/rs.abi3.so,sha256=
|
|
29
|
-
doppy-0.
|
|
28
|
+
doppy/rs.abi3.so,sha256=qEol5mznGJTeLODcK2DUcCuEulCBdgy959ejQeIO2Tc,2695088
|
|
29
|
+
doppy-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|