doppy 0.5.3__tar.gz → 0.5.4__tar.gz

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.

Files changed (49) hide show
  1. {doppy-0.5.3 → doppy-0.5.4}/Cargo.lock +29 -3
  2. {doppy-0.5.3 → doppy-0.5.4}/Cargo.toml +6 -1
  3. {doppy-0.5.3 → doppy-0.5.4}/PKG-INFO +2 -1
  4. {doppy-0.5.3 → doppy-0.5.4}/crates/doppy_rs/Cargo.toml +2 -1
  5. doppy-0.5.4/crates/doppy_rs/src/raw/wls77.rs +90 -0
  6. {doppy-0.5.3 → doppy-0.5.4}/crates/doppy_rs/src/raw.rs +2 -0
  7. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/Cargo.toml +1 -0
  8. doppy-0.5.4/crates/doprs/src/bin/parse_wls70.rs +8 -0
  9. doppy-0.5.4/crates/doprs/src/bin/parse_wls77.rs +7 -0
  10. doppy-0.5.4/crates/doprs/src/raw/wls77.rs +290 -0
  11. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/src/raw.rs +1 -0
  12. {doppy-0.5.3 → doppy-0.5.4}/pyproject.toml +1 -0
  13. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/__init__.py +10 -1
  14. doppy-0.5.4/src/doppy/raw/wls77.py +163 -0
  15. {doppy-0.5.3 → doppy-0.5.4}/LICENSE +0 -0
  16. {doppy-0.5.3 → doppy-0.5.4}/README.md +0 -0
  17. {doppy-0.5.3 → doppy-0.5.4}/crates/doppy_rs/src/lib.rs +0 -0
  18. {doppy-0.5.3 → doppy-0.5.4}/crates/doppy_rs/src/raw/halo_hpl.rs +0 -0
  19. {doppy-0.5.3 → doppy-0.5.4}/crates/doppy_rs/src/raw/wls70.rs +0 -0
  20. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/.gitignore +0 -0
  21. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/src/lib.rs +0 -0
  22. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/src/raw/error.rs +0 -0
  23. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/src/raw/halo_hpl.rs +0 -0
  24. {doppy-0.5.3 → doppy-0.5.4}/crates/doprs/src/raw/wls70.rs +0 -0
  25. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/__init__.py +0 -0
  26. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/__main__.py +0 -0
  27. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/bench.py +0 -0
  28. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/data/__init__.py +0 -0
  29. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/data/api.py +0 -0
  30. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/data/cache.py +0 -0
  31. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/data/exceptions.py +0 -0
  32. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/defaults.py +0 -0
  33. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/exceptions.py +0 -0
  34. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/netcdf.py +0 -0
  35. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/options.py +0 -0
  36. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/__init__.py +0 -0
  37. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/noise_utils.py +0 -0
  38. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/stare.py +0 -0
  39. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/stare_depol.py +0 -0
  40. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/turbulence.py +0 -0
  41. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/product/wind.py +0 -0
  42. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/py.typed +0 -0
  43. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/halo_bg.py +0 -0
  44. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/halo_hpl.py +0 -0
  45. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/halo_sys_params.py +0 -0
  46. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/utils.py +0 -0
  47. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/windcube.py +0 -0
  48. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/raw/wls70.py +0 -0
  49. {doppy-0.5.3 → doppy-0.5.4}/src/doppy/utils.py +0 -0
@@ -106,18 +106,20 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
106
106
 
107
107
  [[package]]
108
108
  name = "doppy_rs"
109
- version = "0.5.3"
109
+ version = "0.5.4"
110
110
  dependencies = [
111
111
  "doprs",
112
+ "ndarray 0.16.1",
112
113
  "numpy",
113
114
  "pyo3",
114
115
  ]
115
116
 
116
117
  [[package]]
117
118
  name = "doprs"
118
- version = "0.5.3"
119
+ version = "0.5.4"
119
120
  dependencies = [
120
121
  "chrono",
122
+ "ndarray 0.16.1",
121
123
  "rayon",
122
124
  "regex",
123
125
  ]
@@ -233,6 +235,21 @@ dependencies = [
233
235
  "rawpointer",
234
236
  ]
235
237
 
238
+ [[package]]
239
+ name = "ndarray"
240
+ version = "0.16.1"
241
+ source = "registry+https://github.com/rust-lang/crates.io-index"
242
+ checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
243
+ dependencies = [
244
+ "matrixmultiply",
245
+ "num-complex",
246
+ "num-integer",
247
+ "num-traits",
248
+ "portable-atomic",
249
+ "portable-atomic-util",
250
+ "rawpointer",
251
+ ]
252
+
236
253
  [[package]]
237
254
  name = "num-complex"
238
255
  version = "0.4.6"
@@ -267,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
267
284
  checksum = "bef41cbb417ea83b30525259e30ccef6af39b31c240bda578889494c5392d331"
268
285
  dependencies = [
269
286
  "libc",
270
- "ndarray",
287
+ "ndarray 0.15.6",
271
288
  "num-complex",
272
289
  "num-integer",
273
290
  "num-traits",
@@ -310,6 +327,15 @@ version = "1.11.0"
310
327
  source = "registry+https://github.com/rust-lang/crates.io-index"
311
328
  checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
312
329
 
330
+ [[package]]
331
+ name = "portable-atomic-util"
332
+ version = "0.2.4"
333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
334
+ checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
335
+ dependencies = [
336
+ "portable-atomic",
337
+ ]
338
+
313
339
  [[package]]
314
340
  name = "proc-macro2"
315
341
  version = "1.0.94"
@@ -4,6 +4,11 @@ resolver = "2"
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2021"
7
- version = "0.5.3"
7
+ version = "0.5.4"
8
8
  authors = ["Niko Leskinen <niko.leskinen@fmi.fi>"]
9
9
  license-file = "LICENSE"
10
+
11
+
12
+ [workspace.dependencies]
13
+ ndarray = "0.16"
14
+ numpy = {version = "0.20", fatures = ["ndarray"]}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doppy
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -28,6 +28,7 @@ Requires-Dist: maturin==1.8 ; extra == 'dev'
28
28
  Requires-Dist: release-version ; extra == 'dev'
29
29
  Requires-Dist: pre-commit ; extra == 'dev'
30
30
  Requires-Dist: xarray[io] ; extra == 'dev'
31
+ Requires-Dist: seaborn ; extra == 'dev'
31
32
  Provides-Extra: dev
32
33
  License-File: LICENSE
33
34
  License-File: LICENSE
@@ -1,6 +1,7 @@
1
1
  [dependencies]
2
- numpy = "0.20"
3
2
  doprs = {path = "../doprs"}
3
+ ndarray = {workspace=true}
4
+ numpy = {workspace=true}
4
5
 
5
6
  [dependencies.pyo3]
6
7
  version = "0.20.0"
@@ -0,0 +1,90 @@
1
+ use numpy::PyArray1;
2
+ use pyo3::exceptions::PyRuntimeError;
3
+ use pyo3::prelude::*;
4
+ use pyo3::types::PyDict;
5
+
6
+ #[pymodule]
7
+ pub fn wls77(_py: Python, m: &PyModule) -> PyResult<()> {
8
+ m.add_function(wrap_pyfunction!(from_bytes_src, m)?)?;
9
+ Ok(())
10
+ }
11
+
12
+ #[pyfunction]
13
+ pub fn from_bytes_srcs<'a>(py: Python<'a>, contents: Vec<&'a [u8]>) -> PyResult<Vec<&'a PyDict>> {
14
+ let raws = doprs::raw::wls77::from_bytes_srcs(contents);
15
+ let mut result = Vec::new();
16
+ for raw in raws {
17
+ result.push(convert_to_python(py, raw)?);
18
+ }
19
+ Ok(result)
20
+ }
21
+
22
+ #[pyfunction]
23
+ pub fn from_bytes_src<'a>(py: Python<'a>, content: &'a [u8]) -> PyResult<&'a PyDict> {
24
+ let raw = match doprs::raw::wls77::from_bytes_src(content) {
25
+ Ok(raw) => raw,
26
+ Err(e) => {
27
+ return Err(PyRuntimeError::new_err(format!(
28
+ "Failed to read files: {}",
29
+ e
30
+ )))
31
+ }
32
+ };
33
+ convert_to_python(py, raw)
34
+ }
35
+
36
+ #[pyfunction]
37
+ pub fn from_filename_srcs(py: Python, filenames: Vec<String>) -> PyResult<Vec<&PyDict>> {
38
+ let raws = doprs::raw::wls77::from_filename_srcs(filenames);
39
+ let mut result = Vec::new();
40
+ for raw in raws {
41
+ result.push(convert_to_python(py, raw)?);
42
+ }
43
+ Ok(result)
44
+ }
45
+
46
+ #[pyfunction]
47
+ pub fn from_filename_src(py: Python, filename: String) -> PyResult<&PyDict> {
48
+ let raw = match doprs::raw::wls77::from_filename_src(filename) {
49
+ Ok(raw) => raw,
50
+ Err(e) => {
51
+ return Err(PyRuntimeError::new_err(format!(
52
+ "Failed to read files: {}",
53
+ e
54
+ )))
55
+ }
56
+ };
57
+ convert_to_python(py, raw)
58
+ }
59
+
60
+ fn convert_to_python(py: Python, raw: doprs::raw::wls77::Wls77) -> PyResult<&PyDict> {
61
+ let d = PyDict::new(py);
62
+
63
+ let fields = [
64
+ ("time", raw.time.as_slice()),
65
+ ("altitude", raw.altitude.as_slice()),
66
+ ("position", raw.position.as_slice()),
67
+ ("temperature", raw.temperature.as_slice()),
68
+ ("wiper_count", raw.wiper_count.as_slice()),
69
+ ("cnr", raw.cnr.as_slice()),
70
+ ("radial_velocity", raw.radial_velocity.as_slice()),
71
+ (
72
+ "radial_velocity_deviation",
73
+ raw.radial_velocity_deviation.as_slice(),
74
+ ),
75
+ ("wind_speed", raw.wind_speed.as_slice()),
76
+ ("wind_direction", raw.wind_direction.as_slice()),
77
+ ("zonal_wind", raw.zonal_wind.as_slice()),
78
+ ("meridional_wind", raw.meridional_wind.as_slice()),
79
+ ("vertical_wind", raw.vertical_wind.as_slice()),
80
+ ];
81
+
82
+ for (key, value) in fields {
83
+ d.set_item(key, PyArray1::from_slice(py, value.unwrap()))?;
84
+ }
85
+
86
+ d.set_item("cnr_threshold", raw.cnr_threshold)?;
87
+ d.set_item("system_id", raw.system_id)?;
88
+
89
+ Ok(d)
90
+ }
@@ -3,10 +3,12 @@ use pyo3::wrap_pymodule;
3
3
 
4
4
  pub mod halo_hpl;
5
5
  pub mod wls70;
6
+ pub mod wls77;
6
7
 
7
8
  #[pymodule]
8
9
  pub fn raw(_py: Python, m: &PyModule) -> PyResult<()> {
9
10
  m.add_wrapped(wrap_pymodule!(halo_hpl::halo_hpl))?;
10
11
  m.add_wrapped(wrap_pymodule!(wls70::wls70))?;
12
+ m.add_wrapped(wrap_pymodule!(wls77::wls77))?;
11
13
  Ok(())
12
14
  }
@@ -2,6 +2,7 @@
2
2
  chrono = "0.4"
3
3
  regex = "1.10"
4
4
  rayon = "1.8"
5
+ ndarray = {workspace=true}
5
6
 
6
7
  [package]
7
8
  name = "doprs"
@@ -0,0 +1,8 @@
1
+ use doprs::raw::wls70;
2
+
3
+ fn main() {
4
+ let path = std::env::args().nth(1).unwrap();
5
+ let file = std::fs::File::open(path).unwrap();
6
+ let a = wls70::from_file_src(&file).unwrap();
7
+ dbg!(a);
8
+ }
@@ -0,0 +1,7 @@
1
+ use doprs::raw::wls77;
2
+
3
+ fn main() {
4
+ let path = std::env::args().nth(1).unwrap();
5
+ let file = std::fs::File::open(path).unwrap();
6
+ let _a = wls77::from_file_src(&file).unwrap();
7
+ }
@@ -0,0 +1,290 @@
1
+ use crate::raw::error::RawParseError;
2
+ use chrono::{DateTime, NaiveDateTime, ParseError, Utc};
3
+ use ndarray::{s, Array, Array1, Array2};
4
+ use rayon::prelude::*;
5
+ use std::fs::File;
6
+ use std::io::{BufRead, Cursor, Read};
7
+
8
+ #[derive(Debug, Default, Clone)]
9
+ pub struct Wls77Old {
10
+ pub info: Info,
11
+ pub data_columns: Vec<String>,
12
+ pub data: Vec<f64>,
13
+ }
14
+
15
+ #[derive(Debug, Default, Clone)]
16
+ pub struct Wls77 {
17
+ pub time: Array1<f64>,
18
+ pub altitude: Array1<f64>,
19
+ pub position: Array1<f64>,
20
+ pub temperature: Array1<f64>,
21
+ pub wiper_count: Array1<f64>,
22
+ pub cnr: Array2<f64>,
23
+ pub radial_velocity: Array2<f64>,
24
+ pub radial_velocity_deviation: Array2<f64>,
25
+ pub wind_speed: Array2<f64>,
26
+ pub wind_direction: Array2<f64>,
27
+ pub zonal_wind: Array2<f64>,
28
+ pub meridional_wind: Array2<f64>,
29
+ pub vertical_wind: Array2<f64>,
30
+ pub cnr_threshold: f64,
31
+ pub system_id: String,
32
+ }
33
+
34
+ #[derive(Debug, Default, Clone)]
35
+ pub struct Info {
36
+ pub altitude: Vec<f64>,
37
+ pub system_id: String,
38
+ pub cnr_threshold: f64,
39
+ }
40
+
41
+ pub fn from_file_src(mut file: &File) -> Result<Wls77, RawParseError> {
42
+ let mut content = vec![];
43
+ file.read_to_end(&mut content)?;
44
+ from_bytes_src(&content)
45
+ }
46
+
47
+ pub fn from_filename_src(filename: String) -> Result<Wls77, RawParseError> {
48
+ let file = File::open(filename)?;
49
+ from_file_src(&file)
50
+ }
51
+
52
+ pub fn from_filename_srcs(filenames: Vec<String>) -> Vec<Wls77> {
53
+ let results = filenames
54
+ .par_iter()
55
+ .filter_map(|filename| from_filename_src(filename.to_string()).ok())
56
+ .collect();
57
+ results
58
+ }
59
+
60
+ pub fn from_file_srcs(files: Vec<&File>) -> Vec<Wls77> {
61
+ let results = files
62
+ .par_iter()
63
+ .filter_map(|file| from_file_src(file).ok())
64
+ .collect();
65
+ results
66
+ }
67
+
68
+ pub fn from_bytes_srcs(contents: Vec<&[u8]>) -> Vec<Wls77> {
69
+ let results = contents
70
+ .par_iter()
71
+ .filter_map(|content| from_bytes_src(content).ok())
72
+ .collect();
73
+ results
74
+ }
75
+
76
+ enum Phase {
77
+ Info,
78
+ Data,
79
+ }
80
+
81
+ pub fn from_bytes_src(content: &[u8]) -> Result<Wls77, RawParseError> {
82
+ let cur = Cursor::new(content);
83
+ let mut info_str = Vec::new();
84
+ let mut header = Vec::new();
85
+ let mut data_str = Vec::new();
86
+
87
+ let mut phase = Phase::Info;
88
+
89
+ for line in cur.split(b'\n') {
90
+ let line = line.unwrap();
91
+ match phase {
92
+ Phase::Info => {
93
+ if line.starts_with(b"Timestamp\tPosition\tTemperature")
94
+ || line.starts_with(b"Date\tPosition\tTemperature")
95
+ {
96
+ header.extend_from_slice(&line);
97
+ header.push(b'\n');
98
+ phase = Phase::Data;
99
+ } else {
100
+ info_str.extend_from_slice(&line);
101
+ info_str.push(b'\n');
102
+ }
103
+ }
104
+ Phase::Data => {
105
+ data_str.extend_from_slice(&line);
106
+ data_str.push(b'\n');
107
+ }
108
+ }
109
+ }
110
+ let info = parse_info(&info_str)?;
111
+
112
+ match parse_data(&data_str) {
113
+ Ok((data, ncols)) => {
114
+ let header_str: String = header.iter().map(|&c| c as char).collect();
115
+ let cols: Vec<_> = header_str
116
+ .split('\t')
117
+ .map(|s| s.trim().to_string())
118
+ .filter(|s| !s.is_empty())
119
+ .collect();
120
+ if ncols != (cols.len() as i64) {
121
+ return Err(RawParseError {
122
+ message: "Number of columns on header and number of columns in data mismatch"
123
+ .to_string(),
124
+ });
125
+ }
126
+
127
+ let data: Array1<_> = Array::from_vec(data.clone());
128
+ let n = (data.len() as i64 / ncols)
129
+ .try_into()
130
+ .map_err(|e| RawParseError {
131
+ message: format!("Failed to convert rows count: {}", e),
132
+ })?;
133
+ let m = ncols.try_into().map_err(|e| RawParseError {
134
+ message: format!("Failed to convert columns count: {}", e),
135
+ })?;
136
+ let shape: [usize; 2] = [n, m];
137
+
138
+ let data = data
139
+ .into_shape_with_order(shape)
140
+ .map_err(|e| RawParseError {
141
+ message: format!("Cannot reshape data array: {}", e),
142
+ })?;
143
+ let altitude = Array::from_vec(info.altitude);
144
+
145
+ let time = data.slice(s![.., 0]).to_owned();
146
+ let position = data.slice(s![.., 1]).to_owned();
147
+ let temperature = data.slice(s![.., 2]).to_owned();
148
+ let wiper_count = data.slice(s![.., 2]).to_owned();
149
+ let cnr = data.slice(s![..,4..;8]).to_owned();
150
+ let radial_velocity = data.slice(s![..,5..;8]).to_owned();
151
+ let radial_velocity_deviation = data.slice(s![..,6..;8]).to_owned();
152
+ let wind_speed = data.slice(s![..,7..;8]).to_owned();
153
+ let wind_direction = data.slice(s![..,8..;8]).to_owned();
154
+ let zonal_wind = data.slice(s![..,9..;8]).to_owned();
155
+ let meridional_wind = data.slice(s![..,10..;8]).to_owned();
156
+ let vertical_wind = data.slice(s![..,11..;8]).to_owned();
157
+
158
+ Ok(Wls77 {
159
+ time,
160
+ altitude,
161
+ position,
162
+ temperature,
163
+ wiper_count,
164
+ cnr,
165
+ radial_velocity,
166
+ radial_velocity_deviation,
167
+ wind_speed,
168
+ wind_direction,
169
+ zonal_wind,
170
+ meridional_wind,
171
+ vertical_wind,
172
+ system_id: info.system_id,
173
+ cnr_threshold: info.cnr_threshold,
174
+ })
175
+ }
176
+ Err(e) => Err(e),
177
+ }
178
+ }
179
+
180
+ fn parse_info(info_str: &[u8]) -> Result<Info, RawParseError> {
181
+ let mut info = Info::default();
182
+ for line in info_str.split(|&b| b == b'\n') {
183
+ match line {
184
+ b if b.starts_with(b"Altitudes AGL (m)=") => {
185
+ info.altitude = line
186
+ .split(|&b| b == b'\t')
187
+ .skip(1)
188
+ .map(|part| {
189
+ String::from_utf8(part.to_vec())
190
+ .map_err(|_| RawParseError {
191
+ message: "UTF-8 conversion error".into(),
192
+ })
193
+ .and_then(|s| {
194
+ s.trim().parse::<f64>().map_err(|_| RawParseError {
195
+ message: "Parse float error".into(),
196
+ })
197
+ })
198
+ })
199
+ .collect::<Result<Vec<f64>, _>>()?;
200
+ }
201
+ b if b.starts_with(b"ID System=") => {
202
+ info.system_id = std::str::from_utf8(&line[10..])
203
+ .map(|s| s.trim())
204
+ .map_err(|_| RawParseError {
205
+ message: "UTF-8 conversion error".into(),
206
+ })?
207
+ .to_string();
208
+ }
209
+ b if b.starts_with(b"CNRThreshold=") => {
210
+ info.cnr_threshold = std::str::from_utf8(&line[13..])
211
+ .map_err(|_| RawParseError {
212
+ message: "UTF-8 conversion error".into(),
213
+ })?
214
+ .trim()
215
+ .parse::<f64>()
216
+ .map_err(|_| RawParseError {
217
+ message: "Parse float error".into(),
218
+ })?;
219
+ }
220
+ _ => (),
221
+ }
222
+ }
223
+
224
+ Ok(info)
225
+ }
226
+
227
+ pub fn parse_data(data: &[u8]) -> Result<(Vec<f64>, i64), RawParseError> {
228
+ let mut ncols: i64 = -1;
229
+ let mut data_flat = vec![];
230
+ for line in data.split(|&b| b == b'\n') {
231
+ let parts: Vec<_> = line
232
+ .split(|&b| b == b'\t')
233
+ .filter(|part| !(part.is_empty() || part == b"\r"))
234
+ .collect();
235
+ if parts.is_empty() {
236
+ continue;
237
+ }
238
+ if ncols < 0 {
239
+ ncols = parts.len() as i64;
240
+ }
241
+ if ncols != parts.len() as i64 {
242
+ return Err(RawParseError {
243
+ message: "Unexpected number of columns".to_string(),
244
+ });
245
+ }
246
+ for (i, part) in parts.iter().enumerate() {
247
+ match i {
248
+ 0 => {
249
+ let date = String::from_utf8_lossy(part).trim().to_string();
250
+ match datetime_to_timestamp(&date) {
251
+ Ok(d) => {
252
+ data_flat.push(d);
253
+ }
254
+ Err(_) => println!("Error with datetime"),
255
+ }
256
+ }
257
+ 1 => {
258
+ let s = String::from_utf8_lossy(part).trim().to_string();
259
+ if let Ok(val) = s.parse::<f64>() {
260
+ data_flat.push(val);
261
+ } else {
262
+ match s.as_ref() {
263
+ "V" => data_flat.push(-1.0),
264
+ _ => data_flat.push(-2.0),
265
+ }
266
+ }
267
+ }
268
+
269
+ _ => match String::from_utf8_lossy(part).trim().parse::<f64>() {
270
+ Ok(x) => {
271
+ data_flat.push(x);
272
+ }
273
+ Err(_) => println!("Cannot parse float"),
274
+ },
275
+ }
276
+ }
277
+ }
278
+ if ncols < 1 || (data_flat.len() as i64) % ncols != 0 {
279
+ return Err(RawParseError {
280
+ message: "Unexpected number of columns".to_string(),
281
+ });
282
+ }
283
+ Ok((data_flat, ncols))
284
+ }
285
+ fn datetime_to_timestamp(s: &str) -> Result<f64, ParseError> {
286
+ let format = "%Y/%m/%d %H:%M:%S%.f";
287
+ let ndt = NaiveDateTime::parse_from_str(s, format)?;
288
+ let dt = DateTime::<Utc>::from_naive_utc_and_offset(ndt, Utc);
289
+ Ok(dt.timestamp() as f64 + dt.timestamp_subsec_millis() as f64 / 1000.0)
290
+ }
@@ -1,3 +1,4 @@
1
1
  pub mod error;
2
2
  pub mod halo_hpl;
3
3
  pub mod wls70;
4
+ pub mod wls77;
@@ -44,6 +44,7 @@ dev = [
44
44
  "release-version",
45
45
  "pre-commit",
46
46
  "xarray[io]",
47
+ "seaborn",
47
48
  ]
48
49
 
49
50
  [project.scripts]
@@ -3,5 +3,14 @@ from .halo_hpl import HaloHpl
3
3
  from .halo_sys_params import HaloSysParams
4
4
  from .windcube import WindCube, WindCubeFixed
5
5
  from .wls70 import Wls70
6
+ from .wls77 import Wls77
6
7
 
7
- __all__ = ["HaloHpl", "HaloBg", "HaloSysParams", "WindCube", "WindCubeFixed", "Wls70"]
8
+ __all__ = [
9
+ "HaloHpl",
10
+ "HaloBg",
11
+ "HaloSysParams",
12
+ "WindCube",
13
+ "WindCubeFixed",
14
+ "Wls70",
15
+ "Wls77",
16
+ ]
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timezone
5
+ from io import BufferedIOBase
6
+ from pathlib import Path
7
+ from typing import Any, Sequence
8
+
9
+ import numpy as np
10
+ import numpy.typing as npt
11
+ from numpy import datetime64
12
+
13
+ import doppy
14
+ from doppy import exceptions
15
+ from doppy.raw.utils import bytes_from_src
16
+ from doppy.utils import merge_all_equal
17
+
18
+
19
+ @dataclass
20
+ class Wls77:
21
+ time: npt.NDArray[datetime64] # dim: (time, )
22
+ altitude: npt.NDArray[np.float64] # dim: (altitude, )
23
+ position: npt.NDArray[np.float64] # dim: (time, )
24
+ temperature: npt.NDArray[np.float64] # dim: (time, )
25
+ wiper_count: npt.NDArray[np.float64] # dim: (time, )
26
+ cnr: npt.NDArray[np.float64] # dim: (time, altitude)
27
+ radial_velocity: npt.NDArray[np.float64] # dim: (time, altitude)
28
+ radial_velocity_deviation: npt.NDArray[np.float64] # dim: (time, altitude)
29
+ wind_speed: npt.NDArray[np.float64] # dim: (time, altitude)
30
+ wind_direction: npt.NDArray[np.float64] # dim: (time, altitude)
31
+ zonal_wind: npt.NDArray[np.float64] # u := zonal wind?, dim: (time, altitude)
32
+ meridional_wind: npt.NDArray[
33
+ np.float64
34
+ ] # v := meridional wind?, dim: (time, altitude)
35
+ vertical_wind: npt.NDArray[np.float64] # w := vertical wind?, dim: (time, altitude)
36
+ cnr_threshold: float
37
+ system_id: str
38
+
39
+ @classmethod
40
+ def from_srcs(
41
+ cls, data: Sequence[str | bytes | Path | BufferedIOBase]
42
+ ) -> list[Wls77]:
43
+ data_bytes = [bytes_from_src(src) for src in data]
44
+ raws = doppy.rs.raw.wls77.from_bytes_srcs(data_bytes)
45
+ try:
46
+ return [_raw_rs_to_wls77(r) for r in raws]
47
+ except RuntimeError as err:
48
+ raise exceptions.RawParsingError(err) from err
49
+
50
+ @classmethod
51
+ def from_src(cls, data: str | Path | bytes | BufferedIOBase) -> Wls77:
52
+ data_bytes = bytes_from_src(data)
53
+ try:
54
+ return _raw_rs_to_wls77(doppy.rs.raw.wls77.from_bytes_src(data_bytes))
55
+ except RuntimeError as err:
56
+ raise exceptions.RawParsingError(err) from err
57
+
58
+ def __getitem__(
59
+ self,
60
+ index: int
61
+ | slice
62
+ | list[int]
63
+ | npt.NDArray[np.int64]
64
+ | npt.NDArray[np.bool_]
65
+ | tuple[slice, slice],
66
+ ) -> Wls77:
67
+ if isinstance(index, (int, slice, list, np.ndarray)):
68
+ return Wls77(
69
+ time=self.time[index],
70
+ altitude=self.altitude,
71
+ position=self.position[index],
72
+ temperature=self.temperature[index],
73
+ wiper_count=self.wiper_count[index],
74
+ cnr=self.cnr[index],
75
+ radial_velocity=self.radial_velocity[index],
76
+ radial_velocity_deviation=self.radial_velocity_deviation[index],
77
+ wind_speed=self.wind_speed[index],
78
+ wind_direction=self.wind_direction[index],
79
+ zonal_wind=self.zonal_wind[index],
80
+ meridional_wind=self.meridional_wind[index],
81
+ vertical_wind=self.vertical_wind[index],
82
+ system_id=self.system_id,
83
+ cnr_threshold=self.cnr_threshold,
84
+ )
85
+ raise TypeError
86
+
87
+ def sorted_by_time(self) -> Wls77:
88
+ sort_indices = np.argsort(self.time)
89
+ return self[sort_indices]
90
+
91
+ @classmethod
92
+ def merge(cls, raws: Sequence[Wls77]) -> Wls77:
93
+ return cls(
94
+ time=np.concatenate(tuple(r.time for r in raws)),
95
+ altitude=raws[0].altitude,
96
+ position=np.concatenate(tuple(r.position for r in raws)),
97
+ temperature=np.concatenate(tuple(r.temperature for r in raws)),
98
+ wiper_count=np.concatenate(tuple(r.wiper_count for r in raws)),
99
+ cnr=np.concatenate(tuple(r.cnr for r in raws)),
100
+ radial_velocity=np.concatenate(tuple(r.radial_velocity for r in raws)),
101
+ radial_velocity_deviation=np.concatenate(
102
+ tuple(r.radial_velocity_deviation for r in raws)
103
+ ),
104
+ wind_speed=np.concatenate(tuple(r.wind_speed for r in raws)),
105
+ wind_direction=np.concatenate(tuple(r.wind_direction for r in raws)),
106
+ zonal_wind=np.concatenate(tuple(r.zonal_wind for r in raws)),
107
+ meridional_wind=np.concatenate(tuple(r.meridional_wind for r in raws)),
108
+ vertical_wind=np.concatenate(tuple(r.vertical_wind for r in raws)),
109
+ system_id=merge_all_equal("system_id", [r.system_id for r in raws]),
110
+ cnr_threshold=merge_all_equal(
111
+ "cnr_threshold", [r.cnr_threshold for r in raws]
112
+ ),
113
+ )
114
+
115
+ def non_strictly_increasing_timesteps_removed(self) -> Wls77:
116
+ if len(self.time) == 0:
117
+ return self
118
+ mask = np.ones_like(self.time, dtype=np.bool_)
119
+ latest_time = self.time[0]
120
+ for i, t in enumerate(self.time[1:], start=1):
121
+ if t <= latest_time:
122
+ mask[i] = False
123
+ else:
124
+ latest_time = t
125
+ return self[mask]
126
+
127
+
128
+ def _raw_rs_to_wls77(
129
+ raw: dict[str, Any],
130
+ ) -> Wls77:
131
+ time_ts = raw["time"]
132
+ time = np.array(
133
+ [
134
+ datetime64(datetime.fromtimestamp(ts, timezone.utc).replace(tzinfo=None))
135
+ for ts in time_ts
136
+ ]
137
+ )
138
+
139
+ n = time.size
140
+
141
+ return Wls77(
142
+ time=time,
143
+ altitude=np.array(raw["altitude"], dtype=np.float64),
144
+ position=np.array(raw["position"], dtype=np.float64),
145
+ temperature=np.array(raw["temperature"], dtype=np.float64),
146
+ wiper_count=np.array(raw["wiper_count"], dtype=np.float64),
147
+ cnr=np.array(raw["cnr"], dtype=np.float64).reshape(n, -1),
148
+ radial_velocity=np.array(raw["radial_velocity"], dtype=np.float64).reshape(
149
+ n, -1
150
+ ),
151
+ radial_velocity_deviation=np.array(
152
+ raw["radial_velocity_deviation"], dtype=np.float64
153
+ ).reshape(n, -1),
154
+ wind_speed=np.array(raw["wind_speed"], dtype=np.float64).reshape(n, -1),
155
+ wind_direction=np.array(raw["wind_direction"], dtype=np.float64).reshape(n, -1),
156
+ zonal_wind=np.array(raw["zonal_wind"], dtype=np.float64).reshape(n, -1),
157
+ meridional_wind=np.array(raw["meridional_wind"], dtype=np.float64).reshape(
158
+ n, -1
159
+ ),
160
+ vertical_wind=np.array(raw["vertical_wind"], dtype=np.float64).reshape(n, -1),
161
+ cnr_threshold=raw["cnr_threshold"],
162
+ system_id=raw["system_id"],
163
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes