doppy 0.4.2__cp310-abi3-macosx_10_12_x86_64.whl → 0.5.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/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
- print(f"Elapsed time: {time.time() - self.start:.2f}")
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__(self, filename: str | pathlib.Path) -> None:
17
- self.nc = netCDF4.Dataset(filename, mode="w")
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, None)
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
- mask: npt.NDArray[np.bool_]
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
- mask=self.mask[index],
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
- mask = _compute_noise_mask_for_windcube(raw)
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
- mask=mask,
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
- mask = _compute_noise_mask(
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
- mask=mask,
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.mask,
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.mask,
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
 
@@ -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
- mask
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
- mask: npt.NDArray[np.bool_]
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.mask = co.mask
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.mask,
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.mask,
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.mask | self.mask_depolarisation,
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.mask | self.mask_beta_cross,
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]) -> 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.array(mdiff, dtype=np.float64)
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
- if not isinstance(data, (list, tuple)):
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
- if isinstance(data, str):
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 = datetime64(start_time, "D").astype("datetime64[us]")
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/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).tolist(),
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
- if not isinstance(data, (list, tuple)):
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
- if isinstance(data, str):
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.abi3.so CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doppy
3
- Version: 0.4.2
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 ==1.4 ; extra == 'dev'
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=ziOeRuz0u6hFJuK0nrFj0Ud1weDkSLuuGx2PKNrmHIE,4315
2
+ doppy-0.5.0.dist-info/WHEEL,sha256=VOPc4LnjY0yPhQSb3E0q8XFYhkjUnHylSjI09QJpAgM,105
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=V-0iroMNMI8ctnLgUau1kdFvwhkYhr9vi-5kWKxw2wc,1089
5
+ doppy/options.py,sha256=73BDODO4OYHn2qOshhwz6u6G3J1kNd3uj6P0a3V4HBE,205
6
+ doppy/__init__.py,sha256=Z9aEUlbPRWRUAoB8_-djkgrJuS4-6pjem4-mVSB6Z9I,191
7
+ doppy/product/turbulence.py,sha256=AcLHQFeyBla0pP5CQtvJbYKupuuy-dwZK0CFlowBl9k,8251
8
+ doppy/product/wind.py,sha256=efBfYalILN2VvDiGpxP04mob-mCrdQFS9tdhReeyXiU,16787
9
+ doppy/product/__init__.py,sha256=C6s9cX20m9UwRsKo1lZH6TnYFfM5KmsX5MPUyShbgl4,235
10
+ doppy/product/stare_depol.py,sha256=gSlD13jGLRmHyTDADFs_MO6ECos6wwKF-TL7G7NwHAA,10718
11
+ doppy/product/noise_utils.py,sha256=fP2bK2xfCUClQcDc_1FmLUUiU2d0KycrxkXLC5jmJAc,2629
12
+ doppy/product/stare.py,sha256=RVHcW8eep91bH2GVobqDqsqswdRaFZ5SY4K_5fLSF3I,27180
13
+ doppy/bench.py,sha256=xUsmwLCjNLXwO9l-_SCfq62jxXJ4eiwkS-WlCejcqZE,308
14
+ doppy/netcdf.py,sha256=q2Wr9auTo3p_Exkt3_To-Z_drpIguiYkS3-32y1wcUk,4055
15
+ doppy/utils.py,sha256=cY-tWrArrROX7lg_zJ2M4QJ6GUnU_BjhkUYL5aG9kbA,790
16
+ doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ doppy/exceptions.py,sha256=OzdLXmKs3qZrvzwaI0pxjzpM2w9J5Of7dCo_Ekygecc,183
18
+ doppy/defaults.py,sha256=-jR_xAVLf_soZNDu3uJ6mhOZa9L1tfKtt0k0tI6Rd9s,472
19
+ doppy/data/cache.py,sha256=cxCZ7HyEQt2EKAGEiMqx3wJ2-5Y3hEAEPQ_XB4gKlkk,1160
20
+ doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ doppy/data/api.py,sha256=hUIqyCq1wFu89gamK7xK8R58eXAODWvLQHaaS5eu6OY,1875
22
+ doppy/data/exceptions.py,sha256=6CS6OHIWq8CqlxiceEvC1j0EfWUYoIfM7dW88apQVn4,89
23
+ doppy/raw/halo_sys_params.py,sha256=jclE4RbVg63iS3rVNYzvErKYAa3CI1q64xj3oai5tMc,5250
24
+ doppy/raw/__init__.py,sha256=4pj4xSK9rA0XEfM79NsRBRYId7GaJRDCyPABxbEm63g,260
25
+ doppy/raw/wls70.py,sha256=OfPczHZPKX4iqCcyjSv0nmkr1osy4pu6krNUi0-G0vM,6319
26
+ doppy/raw/utils.py,sha256=EOWIaxAZIiRYa9dI1j50cSd4I058Zq7sz1HOeipZFTg,422
27
+ doppy/raw/halo_bg.py,sha256=8t9j-SUF1yJht3vrT6KAYJQyxcg3W-0zr8h0jAEhWes,5815
28
+ doppy/raw/halo_hpl.py,sha256=tK0H6LAOcoWqF4vvNu8VRCYq_spvcSTHRM4ju4gx5Lo,16828
29
+ doppy/raw/windcube.py,sha256=HKAlU-TUVqA1sefRLOY5skM1twQaVe-MExlZoI40rK4,19149
30
+ doppy/__main__.py,sha256=zrKQJVj0k0ypBQCGK65Czt9G9FZ_qx3ussw6Q9VJ14g,346
31
+ doppy/rs.abi3.so,sha256=D5Ttp3gNjpYqW14v63O_7Dv9Nz6Qf9XUVRb6bZrsMyc,2716068
32
+ doppy-0.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.7.8)
2
+ Generator: maturin (1.8.2)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-abi3-macosx_10_12_x86_64
@@ -1,29 +0,0 @@
1
- doppy-0.4.2.dist-info/METADATA,sha256=p8gLRxyFiq40SMjnMrf-iMBTF3jkVMt0mwMPeVVc4q4,4276
2
- doppy-0.4.2.dist-info/WHEEL,sha256=MQ93xHb5jAFUhKISyBo7LLNSt0X2_WQfUUQCsGqgr6E,105
3
- doppy-0.4.2.dist-info/entry_points.txt,sha256=9b_Ca7vJoh6AwL3W8qAPh_UmJ_1Pa6hi-TDfCTDjvSk,43
4
- doppy-0.4.2.dist-info/licenses/LICENSE,sha256=V-0iroMNMI8ctnLgUau1kdFvwhkYhr9vi-5kWKxw2wc,1089
5
- doppy/options.py,sha256=73BDODO4OYHn2qOshhwz6u6G3J1kNd3uj6P0a3V4HBE,205
6
- doppy/__init__.py,sha256=Z9aEUlbPRWRUAoB8_-djkgrJuS4-6pjem4-mVSB6Z9I,191
7
- doppy/product/wind.py,sha256=hkSzAyi4tfPoGwUx8iE3l65oCDyzl_tRgtF2loxaEpY,16557
8
- doppy/product/__init__.py,sha256=C6s9cX20m9UwRsKo1lZH6TnYFfM5KmsX5MPUyShbgl4,235
9
- doppy/product/stare_depol.py,sha256=thrWzCpvdH3AiA6gmR37vrH_pDACNY2QTtqPteJ2s8Y,9653
10
- doppy/product/stare.py,sha256=TE6BrrXFuwv9-zLTYCCn3HNpNgjwdldbyU6qk1__FzA,25343
11
- doppy/bench.py,sha256=iVNYveMVGGRES2oe3Orsn31jQFCKTXOmxRFuFiJ8_OA,248
12
- doppy/netcdf.py,sha256=34TzB9UyypTwDpT2MfrRD14g2zGP3X-NuEU15mQGaUI,3925
13
- doppy/utils.py,sha256=cY-tWrArrROX7lg_zJ2M4QJ6GUnU_BjhkUYL5aG9kbA,790
14
- doppy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- doppy/exceptions.py,sha256=OzdLXmKs3qZrvzwaI0pxjzpM2w9J5Of7dCo_Ekygecc,183
16
- doppy/defaults.py,sha256=-jR_xAVLf_soZNDu3uJ6mhOZa9L1tfKtt0k0tI6Rd9s,472
17
- doppy/data/cache.py,sha256=cxCZ7HyEQt2EKAGEiMqx3wJ2-5Y3hEAEPQ_XB4gKlkk,1160
18
- doppy/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- doppy/data/api.py,sha256=QaVKj304OPcu8OF5xgtduQzDis8Srn-I6UgR9qb2u9E,1863
20
- doppy/data/exceptions.py,sha256=6CS6OHIWq8CqlxiceEvC1j0EfWUYoIfM7dW88apQVn4,89
21
- doppy/raw/halo_sys_params.py,sha256=jclE4RbVg63iS3rVNYzvErKYAa3CI1q64xj3oai5tMc,5250
22
- doppy/raw/__init__.py,sha256=4pj4xSK9rA0XEfM79NsRBRYId7GaJRDCyPABxbEm63g,260
23
- doppy/raw/wls70.py,sha256=TePgyhmlWaUQCw1vkWh7uD7KT5EFMaDhNeVYoF2rspc,7510
24
- doppy/raw/halo_bg.py,sha256=8t9j-SUF1yJht3vrT6KAYJQyxcg3W-0zr8h0jAEhWes,5815
25
- doppy/raw/halo_hpl.py,sha256=yzVPQEATnDVf1K3N-mGLXR4_RuT-E94OVXathT2bHes,18580
26
- doppy/raw/windcube.py,sha256=vgkoXVgcbI2Or4BsFCeq2STdjUP49h3mBQLoDXdBQHM,19074
27
- doppy/__main__.py,sha256=zrKQJVj0k0ypBQCGK65Czt9G9FZ_qx3ussw6Q9VJ14g,346
28
- doppy/rs.abi3.so,sha256=0eshPpHERdBkLZ_d5ZOgu_nKUDRWqmh7EOhpk6BcpSI,2692584
29
- doppy-0.4.2.dist-info/RECORD,,