doppy 0.3.7__cp310-abi3-win_amd64.whl → 0.4.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/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
- raw.header.focus_range,
102
- wavelength,
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 Stare(
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
- intensity: npt.NDArray[np.float64],
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 = 1e-5
244
- B = 5e7
322
+ E = beam_energy
323
+ B = receiver_bandwidth
245
324
  nu = c / wavelength
246
- A_e = _compute_effective_receiver_energy(radial_distance, focus, wavelength)
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 = 25e-3 # effective_diameter_of_gaussian_beam
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(datetime.utcfromtimestamp(header_dict["start_time"])),
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.int64] # dim: (time, radial_distance)
20
- height: npt.NDArray[np.int64] # dim: (time,radial_distance)
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,23 +244,98 @@ 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 _from_vad_or_dbs_src(nc: Dataset) -> WindCube:
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
- height_list = []
258
+ doppler_spectrum_width_list = []
259
+ ray_accumulation_time_list = []
156
260
  time_reference = (
157
261
  nc["time_reference"][:] if "time_reference" in nc.variables else None
158
262
  )
159
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
+
334
+ time_reference = (
335
+ nc["time_reference"][:] if "time_reference" in nc.variables else None
336
+ )
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
  ):
@@ -166,16 +345,26 @@ def _from_vad_or_dbs_src(nc: Dataset) -> WindCube:
166
345
 
167
346
  time_list.append(_extract_datetime64_or_raise(group["time"], time_reference_))
168
347
  radial_wind_speed_list.append(
169
- _extract_float64_or_raise(group["radial_wind_speed"])
348
+ _extract_float64_or_raise(group["radial_wind_speed"], expected_dimensions)
170
349
  )
171
- cnr_list.append(_extract_float64_or_raise(group["cnr"]))
350
+ cnr_list.append(_extract_float64_or_raise(group["cnr"], expected_dimensions))
172
351
  radial_wind_speed_confidence_list.append(
173
- _extract_float64_or_raise(group["radial_wind_speed_ci"])
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)
174
367
  )
175
- azimuth_list.append(_extract_float64_or_raise(group["azimuth"]))
176
- elevation_list.append(_extract_float64_or_raise(group["elevation"]))
177
- range_list.append(_extract_int64_or_raise(group["range"]))
178
- height_list.append(_extract_int64_or_raise(group["measurement_height"]))
179
368
  scan_index_list.append(np.full(group["time"][:].shape, i, dtype=np.int64))
180
369
 
181
370
  return WindCube(
@@ -193,7 +382,7 @@ def _from_vad_or_dbs_src(nc: Dataset) -> WindCube:
193
382
 
194
383
 
195
384
  def _extract_datetime64_or_raise(
196
- nc: Dataset, time_reference: str | None
385
+ nc: Variable[npt.NDArray[np.float64]], time_reference: str | None
197
386
  ) -> npt.NDArray[np.datetime64]:
198
387
  match nc.name:
199
388
  case "time":
@@ -211,18 +400,40 @@ def _extract_datetime64_or_raise(
211
400
  raise ValueError(f"Unexpected variable name {nc.name}")
212
401
 
213
402
 
214
- def _extract_float64_or_raise(nc: Dataset) -> npt.NDArray[np.float64]:
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]:
215
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)
216
419
  case "cnr":
217
- if nc.dimensions != ("time", "gate_index"):
420
+ if nc.dimensions != expected_dimensions:
218
421
  raise ValueError(f"Unexpected dimensions for {nc.name}")
219
422
  if nc.units != "dB":
220
423
  raise ValueError(f"Unexpected units for {nc.name}")
221
424
  if nc[:].mask is not np.bool_(False):
222
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
223
434
  return np.array(nc[:].data, dtype=np.float64)
224
435
  case "radial_wind_speed":
225
- if nc.dimensions != ("time", "gate_index"):
436
+ if nc.dimensions != expected_dimensions:
226
437
  raise ValueError(f"Unexpected dimensions for {nc.name}")
227
438
  if nc.units != "m s-1":
228
439
  raise ValueError(f"Unexpected units for {nc.name}")
@@ -230,7 +441,7 @@ def _extract_float64_or_raise(nc: Dataset) -> npt.NDArray[np.float64]:
230
441
  pass # ignore that array contains masked values
231
442
  return np.array(nc[:].data, dtype=np.float64)
232
443
  case "radial_wind_speed_ci":
233
- if nc.dimensions != ("time", "gate_index"):
444
+ if nc.dimensions != expected_dimensions:
234
445
  raise ValueError(f"Unexpected dimensions for {nc.name}")
235
446
  if nc.units != "percent":
236
447
  raise ValueError(f"Unexpected units for {nc.name}")
@@ -238,26 +449,28 @@ def _extract_float64_or_raise(nc: Dataset) -> npt.NDArray[np.float64]:
238
449
  pass # ignore that array contains masked values
239
450
  return np.array(nc[:].data, dtype=np.float64)
240
451
  case "azimuth" | "elevation":
241
- if nc.dimensions != ("time",):
452
+ if nc.dimensions != (expected_dimensions[0],):
242
453
  raise ValueError(f"Unexpected dimensions for {nc.name}")
243
454
  if nc.units != "degrees":
244
455
  raise ValueError(f"Unexpected units for {nc.name}")
245
456
  if nc[:].mask is not np.bool_(False):
246
457
  raise ValueError
247
458
  return np.array(nc[:].data, dtype=np.float64)
248
- case _:
249
- raise ValueError(f"Unexpected variable name {nc.name}")
250
-
251
-
252
- def _extract_int64_or_raise(nc: Dataset) -> npt.NDArray[np.int64]:
253
- match nc.name:
254
- case "range" | "measurement_height":
255
- if nc.dimensions != ("time", "gate_index"):
459
+ case "doppler_spectrum_width":
460
+ if nc.dimensions != expected_dimensions:
256
461
  raise ValueError(f"Unexpected dimensions for {nc.name}")
257
- if nc.units != "m":
462
+ if nc.units != "m s-1":
258
463
  raise ValueError(f"Unexpected units for {nc.name}")
259
464
  if nc[:].mask is not np.bool_(False):
260
- raise ValueError
261
- return np.array(nc[:].data, dtype=np.int64)
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)
262
475
  case _:
263
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([datetime64(datetime.utcfromtimestamp(ts)) for ts in time_ts])
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.pyd 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.3
1
+ Metadata-Version: 2.4
2
2
  Name: doppy
3
- Version: 0.3.7
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.3.7.dist-info/METADATA,sha256=lF1kdOPKMH6ndMWXe5-_xskXTgxNbQPshFkiHEV1L-M,4281
2
- doppy-0.3.7.dist-info/WHEEL,sha256=Tw0ve9H4Xzdrn3r_TkwGVW_TehY5JI5yM2urT-BkFf4,95
3
- doppy-0.3.7.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
4
- doppy-0.3.7.dist-info/licenses/LICENSE,sha256=RIAxFjJLTw0wQ3_SM73JoTeppoD99DJJ72cjvVuRrW4,1110
1
+ doppy-0.4.0.dist-info/METADATA,sha256=YmWpU_2D2vkb1syNvT0rTWbwWIl_szfcfsJlDNquySI,4375
2
+ doppy-0.4.0.dist-info/WHEEL,sha256=KwWTkx8j-9qpJhNDsJHbSlL5MdLAbyW_pNjgwYvZm70,95
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=RIAxFjJLTw0wQ3_SM73JoTeppoD99DJJ72cjvVuRrW4,1110
5
5
  doppy/bench.py,sha256=fLN2iS5mmoYH4qZjD80Vl1h9lp3C-KDfhj9fteWRPtM,260
6
6
  doppy/data/api.py,sha256=c3zbZI3IV7HnzhyFzJpUPOFT20eYFuQlJ5K_1ZEe1Pg,1921
7
7
  doppy/data/cache.py,sha256=ERFzH-KjLLXV8fCePsewTepKZXmLwtXxeazmHiFdA80,1203
8
8
  doppy/data/exceptions.py,sha256=JOyekvUO-Ew4ZVezf3_IxZOrPN0IksfUILd8R2YcSts,95
9
9
  doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- doppy/defaults.py,sha256=nhxcZcFd2jyDVbq0azwtekEJEjiz8k21MZmXFlSXAjo,40
10
+ doppy/defaults.py,sha256=jZvGmHJkvOzFpwy97L6Sj9-bnF0-nvq0-zNNWxM_WpE,490
11
11
  doppy/exceptions.py,sha256=IUF8D_d-QOvtemUSEMg1raXDWA3dSXQ2MUBvlwgm1rU,197
12
12
  doppy/netcdf.py,sha256=W5YPbsv6CQ2Ycy4sFaIyjfn9YjvDgygczm8j9E1_uAg,4054
13
13
  doppy/options.py,sha256=uyIKM_G2GtbmV6Gve8o13eIShQqUwsnYZ41mhX2ypGE,218
14
- doppy/product/stare.py,sha256=ipZuRO8Bc5jWDlPYAxDZQ91gR8OMmnK-D7mNiKo2E7I,23322
14
+ doppy/product/stare.py,sha256=d5RnRD5tUWkbfKgdUhlpX13tXfY6BSkQ9uiANH_38b0,26122
15
15
  doppy/product/stare_depol.py,sha256=D0bVRZT4EP-pYHE84lioUxiuB3a2Cl6LvOgCE-YPJZY,9938
16
16
  doppy/product/wind.py,sha256=lGkvGf_QeOXtfT5kQEq5GuxKuSsLxTGVwRzoioF525o,17061
17
17
  doppy/product/__init__.py,sha256=xoBEXuhid-bvoof5Ogzpt1dKIhJgNMRyrAinCqUOUUI,241
18
18
  doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  doppy/raw/halo_bg.py,sha256=TrqJDUXa1atW2w4UDN9PPx2Mzrc7D9Q2Ndph0yB65p0,5988
20
- doppy/raw/halo_hpl.py,sha256=VF8JQ_5N8adLsGNJOlhvKwz0LtNd4oCU76hETe5Vuhk,19007
20
+ doppy/raw/halo_hpl.py,sha256=l9i_TTJ4BY5eE3qAEAiLKK-mXd-a2BseEyDUMvMEanI,19105
21
21
  doppy/raw/halo_sys_params.py,sha256=nT9r4F-H0Rf5njee5rYzzLTCm7vm3N_pdJqrW6RtyK4,4041
22
- doppy/raw/windcube.py,sha256=Yi3eE6GUA9aia43_X1qApMs9Bv9QqAVxgH1az-FqDUQ,10390
23
- doppy/raw/wls70.py,sha256=2OhjmQ1SoAmjN4npOKFH0eFLyo9RssO7sr-gOtxjAho,7622
24
- doppy/raw/__init__.py,sha256=J06q7iykSAWIif4XAxI1jOszkARvLFBR1eU-B9yUXMw,235
25
- doppy/utils.py,sha256=lENDTzMVjCOA15Va9WZ6cou-foL5bGbNt4-NbDcnXpc,223
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
26
  doppy/__init__.py,sha256=Af7_8p3oN1nTqS9fo0mVKVuiKf5CAEK69uQa32CSFBA,197
27
27
  doppy/__main__.py,sha256=38hIWWfanILuBBGorQiAaleSC4qYJoIxuzVBkxf7Dng,371
28
- doppy/rs.pyd,sha256=uQ56Lc0WBSQ-V0hhrJpiVkguFvbPwj16hIDb98sgb9w,2128896
29
- doppy-0.3.7.dist-info/RECORD,,
28
+ doppy/rs.pyd,sha256=mapeuiOknSrHqwJqRyKXtd46vkG_NtsnVredpQXN8fo,2135552
29
+ doppy-0.4.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.7.4)
2
+ Generator: maturin (1.7.7)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-abi3-win_amd64