doppy 0.1.3__tar.gz → 0.2.0__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 (39) hide show
  1. {doppy-0.1.3 → doppy-0.2.0}/Cargo.lock +2 -2
  2. {doppy-0.1.3 → doppy-0.2.0}/Cargo.toml +1 -1
  3. {doppy-0.1.3 → doppy-0.2.0}/PKG-INFO +2 -2
  4. doppy-0.2.0/crates/doppy_rs/src/raw/wls70.rs +76 -0
  5. {doppy-0.1.3 → doppy-0.2.0}/crates/doppy_rs/src/raw.rs +2 -0
  6. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/src/raw/error.rs +1 -1
  7. doppy-0.2.0/crates/doprs/src/raw/wls70.rs +223 -0
  8. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/src/raw.rs +1 -0
  9. {doppy-0.1.3 → doppy-0.2.0}/pyproject.toml +1 -1
  10. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/data/api.py +7 -1
  11. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/product/stare.py +3 -0
  12. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/product/wind.py +33 -0
  13. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/raw/__init__.py +2 -1
  14. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/raw/halo_hpl.py +4 -0
  15. doppy-0.2.0/src/doppy/raw/wls70.py +188 -0
  16. {doppy-0.1.3 → doppy-0.2.0}/LICENSE +0 -0
  17. {doppy-0.1.3 → doppy-0.2.0}/README.md +0 -0
  18. {doppy-0.1.3 → doppy-0.2.0}/crates/doppy_rs/Cargo.toml +0 -0
  19. {doppy-0.1.3 → doppy-0.2.0}/crates/doppy_rs/src/lib.rs +0 -0
  20. {doppy-0.1.3 → doppy-0.2.0}/crates/doppy_rs/src/raw/halo_hpl.rs +0 -0
  21. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/.gitignore +0 -0
  22. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/Cargo.toml +0 -0
  23. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/src/lib.rs +0 -0
  24. {doppy-0.1.3 → doppy-0.2.0}/crates/doprs/src/raw/halo_hpl.rs +0 -0
  25. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/__init__.py +0 -0
  26. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/__main__.py +0 -0
  27. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/bench.py +0 -0
  28. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/data/__init__.py +0 -0
  29. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/data/cache.py +0 -0
  30. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/data/exceptions.py +0 -0
  31. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/defaults.py +0 -0
  32. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/exceptions.py +0 -0
  33. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/netcdf.py +0 -0
  34. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/options.py +0 -0
  35. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/product/__init__.py +0 -0
  36. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/py.typed +0 -0
  37. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/raw/halo_bg.py +0 -0
  38. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/raw/halo_sys_params.py +0 -0
  39. {doppy-0.1.3 → doppy-0.2.0}/src/doppy/raw/windcube.py +0 -0
@@ -106,7 +106,7 @@ checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
106
106
 
107
107
  [[package]]
108
108
  name = "doppy_rs"
109
- version = "0.1.3"
109
+ version = "0.2.0"
110
110
  dependencies = [
111
111
  "doprs",
112
112
  "numpy",
@@ -115,7 +115,7 @@ dependencies = [
115
115
 
116
116
  [[package]]
117
117
  name = "doprs"
118
- version = "0.1.3"
118
+ version = "0.2.0"
119
119
  dependencies = [
120
120
  "chrono",
121
121
  "rayon",
@@ -4,6 +4,6 @@ resolver = "2"
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2021"
7
- version = "0.1.3"
7
+ version = "0.2.0"
8
8
  authors = ["Niko Leskinen <niko.leskinen@fmi.fi>"]
9
9
  license-file = "LICENSE"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: doppy
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -13,7 +13,7 @@ Requires-Dist: requests
13
13
  Requires-Dist: urllib3
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: netcdf4
16
- Requires-Dist: typer[all]
16
+ Requires-Dist: typer
17
17
  Requires-Dist: matplotlib
18
18
  Requires-Dist: scikit-learn
19
19
  Requires-Dist: scipy
@@ -0,0 +1,76 @@
1
+ use numpy::{PyArray1, ToPyArray};
2
+ use pyo3::exceptions::PyRuntimeError;
3
+ use pyo3::prelude::*;
4
+ use pyo3::types::{PyDict, PyList};
5
+
6
+ type PyReturnType<'a> = (&'a PyDict, &'a PyList, &'a PyArray1<f64>);
7
+
8
+ #[pymodule]
9
+ pub fn wls70(_py: Python, m: &PyModule) -> PyResult<()> {
10
+ m.add_function(wrap_pyfunction!(from_filename_srcs, m)?)?;
11
+ m.add_function(wrap_pyfunction!(from_filename_src, m)?)?;
12
+ m.add_function(wrap_pyfunction!(from_bytes_srcs, m)?)?;
13
+ m.add_function(wrap_pyfunction!(from_bytes_src, m)?)?;
14
+ Ok(())
15
+ }
16
+
17
+ #[pyfunction]
18
+ pub fn from_bytes_srcs<'a>(
19
+ py: Python<'a>,
20
+ contents: Vec<&'a [u8]>,
21
+ ) -> PyResult<Vec<PyReturnType<'a>>> {
22
+ let raws = doprs::raw::wls70::from_bytes_srcs(contents);
23
+ let mut result = Vec::new();
24
+ for raw in raws {
25
+ result.push(convert_to_python(py, raw)?);
26
+ }
27
+ Ok(result)
28
+ }
29
+
30
+ #[pyfunction]
31
+ pub fn from_bytes_src<'a>(py: Python<'a>, content: &'a [u8]) -> PyResult<PyReturnType<'a>> {
32
+ let raw = match doprs::raw::wls70::from_bytes_src(content) {
33
+ Ok(raw) => raw,
34
+ Err(e) => {
35
+ return Err(PyRuntimeError::new_err(format!(
36
+ "Failed to read files: {}",
37
+ e
38
+ )))
39
+ }
40
+ };
41
+ convert_to_python(py, raw)
42
+ }
43
+
44
+ #[pyfunction]
45
+ pub fn from_filename_srcs(py: Python, filenames: Vec<String>) -> PyResult<Vec<PyReturnType>> {
46
+ let raws = doprs::raw::wls70::from_filename_srcs(filenames);
47
+ let mut result = Vec::new();
48
+ for raw in raws {
49
+ result.push(convert_to_python(py, raw)?);
50
+ }
51
+ Ok(result)
52
+ }
53
+
54
+ #[pyfunction]
55
+ pub fn from_filename_src(py: Python, filename: String) -> PyResult<PyReturnType> {
56
+ let raw = match doprs::raw::wls70::from_filename_src(filename) {
57
+ Ok(raw) => raw,
58
+ Err(e) => {
59
+ return Err(PyRuntimeError::new_err(format!(
60
+ "Failed to read files: {}",
61
+ e
62
+ )))
63
+ }
64
+ };
65
+ convert_to_python(py, raw)
66
+ }
67
+
68
+ fn convert_to_python(py: Python, raw: doprs::raw::wls70::Wls70) -> PyResult<PyReturnType> {
69
+ let info_dict = PyDict::new(py);
70
+ info_dict.set_item("altitude", raw.info.altitude.as_slice().to_pyarray(py))?;
71
+ Ok((
72
+ info_dict,
73
+ PyList::new(py, raw.data_columns),
74
+ raw.data.as_slice().to_pyarray(py),
75
+ ))
76
+ }
@@ -2,9 +2,11 @@ use pyo3::prelude::*;
2
2
  use pyo3::wrap_pymodule;
3
3
 
4
4
  pub mod halo_hpl;
5
+ pub mod wls70;
5
6
 
6
7
  #[pymodule]
7
8
  pub fn raw(_py: Python, m: &PyModule) -> PyResult<()> {
8
9
  m.add_wrapped(wrap_pymodule!(halo_hpl::halo_hpl))?;
10
+ m.add_wrapped(wrap_pymodule!(wls70::wls70))?;
9
11
  Ok(())
10
12
  }
@@ -2,7 +2,7 @@ use std::fmt;
2
2
 
3
3
  #[derive(Debug, Clone)]
4
4
  pub struct RawParseError {
5
- message: String,
5
+ pub message: String,
6
6
  }
7
7
 
8
8
  impl Default for RawParseError {
@@ -0,0 +1,223 @@
1
+ use std::fs::File;
2
+
3
+ use chrono::{DateTime, NaiveDateTime, ParseError, Utc};
4
+ use rayon::prelude::*;
5
+ use std::io::{BufRead, Cursor, Read};
6
+
7
+ use crate::raw::error::RawParseError;
8
+
9
+ #[derive(Debug, Default, Clone)]
10
+ pub struct Wls70 {
11
+ pub info: Info,
12
+ pub data_columns: Vec<String>,
13
+ pub data: Vec<f64>,
14
+ }
15
+
16
+ #[derive(Debug, Default, Clone)]
17
+ pub struct Info {
18
+ pub altitude: Vec<f64>,
19
+ }
20
+
21
+ pub fn from_file_src(mut file: &File) -> Result<Wls70, RawParseError> {
22
+ let mut content = vec![];
23
+ file.read_to_end(&mut content)?;
24
+ from_bytes_src(&content)
25
+ }
26
+
27
+ pub fn from_filename_src(filename: String) -> Result<Wls70, RawParseError> {
28
+ let file = File::open(filename)?;
29
+ from_file_src(&file)
30
+ }
31
+
32
+ pub fn from_filename_srcs(filenames: Vec<String>) -> Vec<Wls70> {
33
+ let results = filenames
34
+ .par_iter()
35
+ .filter_map(|filename| from_filename_src(filename.to_string()).ok())
36
+ .collect();
37
+ results
38
+ }
39
+
40
+ pub fn from_file_srcs(files: Vec<&File>) -> Vec<Wls70> {
41
+ let results = files
42
+ .par_iter()
43
+ .filter_map(|file| from_file_src(file).ok())
44
+ .collect();
45
+ results
46
+ }
47
+
48
+ pub fn from_bytes_srcs(contents: Vec<&[u8]>) -> Vec<Wls70> {
49
+ let results = contents
50
+ .par_iter()
51
+ .filter_map(|content| from_bytes_src(content).ok())
52
+ .collect();
53
+ results
54
+ }
55
+
56
+ enum Phase {
57
+ Info,
58
+ Data,
59
+ }
60
+
61
+ pub fn from_bytes_src(content: &[u8]) -> Result<Wls70, RawParseError> {
62
+ let cur = Cursor::new(content);
63
+
64
+ let mut info_str = Vec::new();
65
+ let mut header = Vec::new();
66
+ let mut data_str = Vec::new();
67
+
68
+ let mut phase = Phase::Info;
69
+
70
+ for line in cur.split(b'\n') {
71
+ let line = line?;
72
+ match phase {
73
+ Phase::Info => {
74
+ if line.starts_with(b"Timestamp\tPosition\tTemperature") {
75
+ header.extend_from_slice(&line);
76
+ header.push(b'\n');
77
+ phase = Phase::Data;
78
+ } else {
79
+ info_str.extend_from_slice(&line);
80
+ info_str.push(b'\n');
81
+ }
82
+ }
83
+ Phase::Data => {
84
+ data_str.extend_from_slice(&line);
85
+ data_str.push(b'\n');
86
+ }
87
+ }
88
+ }
89
+ let info = parse_info(&info_str)?;
90
+ match parse_data(&data_str) {
91
+ Ok((data, ncols)) => {
92
+ let header_str: String = header.iter().map(|&c| c as char).collect();
93
+ let cols: Vec<_> = header_str
94
+ .split(|c| c == '\t')
95
+ .map(|s| s.trim().to_string())
96
+ .filter(|s| !s.is_empty())
97
+ .collect();
98
+ if ncols != (cols.len() as i64) {
99
+ return Err(RawParseError {
100
+ message: "Number of columns on header and number of columns in data mismatch"
101
+ .to_string(),
102
+ });
103
+ }
104
+ Ok(Wls70 {
105
+ info,
106
+ data_columns: cols,
107
+ data,
108
+ })
109
+ }
110
+ Err(e) => Err(e),
111
+ }
112
+ }
113
+
114
+ fn parse_info(info_str: &[u8]) -> Result<Info, RawParseError> {
115
+ let mut info = Info::default();
116
+ for line in info_str.split(|&b| b == b'\n') {
117
+ match line {
118
+ b if b.starts_with(b"Altitudes(m)=") => {
119
+ info.altitude = line
120
+ .split(|&b| b == b'\t')
121
+ .skip(1)
122
+ .map(|part| {
123
+ String::from_utf8(part.to_vec())
124
+ .map_err(|_| RawParseError {
125
+ message: "UTF-8 conversion error".into(),
126
+ })
127
+ .and_then(|s| {
128
+ s.trim().parse::<f64>().map_err(|_| RawParseError {
129
+ message: "Parse float error".into(),
130
+ })
131
+ })
132
+ })
133
+ .collect::<Result<Vec<f64>, _>>()?;
134
+ }
135
+ _ => (),
136
+ }
137
+ }
138
+
139
+ Ok(info)
140
+ }
141
+
142
+ pub fn parse_data(data: &[u8]) -> Result<(Vec<f64>, i64), RawParseError> {
143
+ let mut ncols: i64 = -1;
144
+ let mut data_flat = vec![];
145
+ for line in data.split(|&b| b == b'\n') {
146
+ let parts: Vec<_> = line
147
+ .split(|&b| b == b'\t')
148
+ .filter(|part| !(part.is_empty() || part == b"\r"))
149
+ .collect();
150
+ if parts.is_empty() {
151
+ continue;
152
+ }
153
+ if ncols < 0 {
154
+ ncols = parts.len() as i64;
155
+ }
156
+ if ncols != parts.len() as i64 {
157
+ return Err(RawParseError {
158
+ message: "Unexpected number of columns".to_string(),
159
+ });
160
+ }
161
+ for (i, part) in parts.iter().enumerate() {
162
+ match i {
163
+ 0 => {
164
+ let date = String::from_utf8_lossy(part).trim().to_string();
165
+ match datetime_to_timestamp(&date) {
166
+ Ok(d) => {
167
+ data_flat.push(d);
168
+ }
169
+ Err(_) => println!("Error with datetime"),
170
+ }
171
+ }
172
+ 3 => match String::from_utf8_lossy(part).trim() {
173
+ "On" => {
174
+ data_flat.push(1.);
175
+ }
176
+ "Off" => {
177
+ data_flat.push(0.);
178
+ }
179
+ _ => {
180
+ println!("Failed to read Wiper state");
181
+ return Err(RawParseError {
182
+ message: "Unexpected value for Wiper state".to_string(),
183
+ });
184
+ }
185
+ },
186
+ _ => match String::from_utf8_lossy(part).trim().parse::<f64>() {
187
+ Ok(x) => {
188
+ data_flat.push(x);
189
+ }
190
+ Err(_) => println!("Cannot parse float"),
191
+ },
192
+ }
193
+ }
194
+ }
195
+ if ncols < 1 || (data_flat.len() as i64) % ncols != 0 {
196
+ return Err(RawParseError {
197
+ message: "Unexpected number of columns".to_string(),
198
+ });
199
+ }
200
+ Ok((data_flat, ncols))
201
+ }
202
+
203
+ fn datetime_to_timestamp(s: &str) -> Result<f64, ParseError> {
204
+ let format = "%d/%m/%Y %H:%M:%S%.f";
205
+ let ndt = NaiveDateTime::parse_from_str(s, format)?;
206
+ let dt = DateTime::<Utc>::from_naive_utc_and_offset(ndt, Utc);
207
+ Ok(dt.timestamp() as f64 + dt.timestamp_subsec_millis() as f64 / 1000.0)
208
+ }
209
+
210
+ #[cfg(test)]
211
+ mod tests {
212
+ use super::*;
213
+ use std::fs::File;
214
+
215
+ #[test]
216
+ fn test_from_file_src() -> Result<(), Box<dyn std::error::Error>> {
217
+ let file_path = "../../data/palaiseau/2024-04-01/wlscerea_0a_windLz1Lb87R10s-HR_v01_20240401_000000.rtd";
218
+ let file = File::open(file_path)?;
219
+ assert!(from_file_src(&file).is_ok());
220
+
221
+ Ok(())
222
+ }
223
+ }
@@ -1,2 +1,3 @@
1
1
  pub mod error;
2
2
  pub mod halo_hpl;
3
+ pub mod wls70;
@@ -25,7 +25,7 @@ dependencies = [
25
25
  "urllib3",
26
26
  "numpy",
27
27
  "netCDF4",
28
- "typer[all]",
28
+ "typer",
29
29
  "matplotlib",
30
30
  "scikit-learn",
31
31
  "scipy"
@@ -37,7 +37,13 @@ class Api:
37
37
  return self.get(
38
38
  "raw-files",
39
39
  params={
40
- "instrument": ["halo-doppler-lidar", "wls100s", "wls200s", "wls400s"],
40
+ "instrument": [
41
+ "halo-doppler-lidar",
42
+ "wls100s",
43
+ "wls200s",
44
+ "wls400s",
45
+ "wls70",
46
+ ],
41
47
  "site": site,
42
48
  "date": date,
43
49
  },
@@ -50,6 +50,7 @@ class Stare:
50
50
  doppy.raw.HaloHpl.merge(_select_raws_for_stare(raws))
51
51
  .sorted_by_time()
52
52
  .non_strictly_increasing_timesteps_removed()
53
+ .nans_removed()
53
54
  )
54
55
 
55
56
  bgs = doppy.raw.HaloBg.from_srcs(data_bg)
@@ -65,6 +66,8 @@ class Stare:
65
66
  .non_strictly_increasing_timesteps_removed()
66
67
  )
67
68
  raw, intensity_bg_corrected = _correct_background(raw, bg, bg_correction_method)
69
+ if len(raw.time) == 0:
70
+ raise doppy.exceptions.NoDataError("No matching data and bg files")
68
71
  intensity_noise_bias_corrected = _correct_intensity_noise_bias(
69
72
  raw, intensity_bg_corrected
70
73
  )
@@ -54,6 +54,7 @@ class Wind:
54
54
  doppy.raw.HaloHpl.merge(_select_raws_for_wind(raws))
55
55
  .sorted_by_time()
56
56
  .non_strictly_increasing_timesteps_removed()
57
+ .nans_removed()
57
58
  )
58
59
  if len(raw.time) == 0:
59
60
  raise doppy.exceptions.NoDataError("No suitable data for the wind product")
@@ -148,6 +149,38 @@ class Wind:
148
149
  mask=mask,
149
150
  )
150
151
 
152
+ @classmethod
153
+ def from_wls70_data(
154
+ cls,
155
+ data: Sequence[str]
156
+ | Sequence[Path]
157
+ | Sequence[bytes]
158
+ | Sequence[BufferedIOBase],
159
+ ) -> Wind:
160
+ raws = doppy.raw.Wls70.from_srcs(data)
161
+
162
+ if len(raws) == 0:
163
+ raise doppy.exceptions.NoDataError("Wls70 data missing")
164
+
165
+ raw = (
166
+ doppy.raw.Wls70.merge(raws)
167
+ .sorted_by_time()
168
+ .non_strictly_increasing_timesteps_removed()
169
+ )
170
+ mask = (
171
+ np.isnan(raw.meridional_wind)
172
+ | np.isnan(raw.zonal_wind)
173
+ | np.isnan(raw.vertical_wind)
174
+ )
175
+ return Wind(
176
+ time=raw.time,
177
+ height=raw.altitude,
178
+ zonal_wind=raw.zonal_wind,
179
+ meridional_wind=raw.meridional_wind,
180
+ vertical_wind=raw.vertical_wind,
181
+ mask=mask,
182
+ )
183
+
151
184
 
152
185
  def _compute_wind(
153
186
  raw: doppy.raw.HaloHpl | doppy.raw.WindCube,
@@ -2,5 +2,6 @@ from .halo_bg import HaloBg
2
2
  from .halo_hpl import HaloHpl
3
3
  from .halo_sys_params import HaloSysParams
4
4
  from .windcube import WindCube
5
+ from .wls70 import Wls70
5
6
 
6
- __all__ = ["HaloHpl", "HaloBg", "HaloSysParams", "WindCube"]
7
+ __all__ = ["HaloHpl", "HaloBg", "HaloSysParams", "WindCube", "Wls70"]
@@ -187,6 +187,10 @@ class HaloHpl:
187
187
  latest_time = t
188
188
  return self[mask]
189
189
 
190
+ def nans_removed(self) -> HaloHpl:
191
+ is_ok = ~np.isnan(self.intensity).any(axis=1)
192
+ return self[is_ok]
193
+
190
194
 
191
195
  @dataclass(slots=True)
192
196
  class HaloHplHeader:
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from io import BufferedIOBase
6
+ from pathlib import Path
7
+ from typing import 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
+
16
+
17
+ @dataclass
18
+ class Wls70:
19
+ time: npt.NDArray[datetime64] # dim: (time, )
20
+ altitude: npt.NDArray[np.float64] # dim: (altitude, )
21
+ position: npt.NDArray[np.float64] # dim: (time, )
22
+ temperature: npt.NDArray[np.float64] # dim: (time, )
23
+ wiper: npt.NDArray[np.bool_] # dim: (time, )
24
+ cnr: npt.NDArray[np.float64] # dim: (time, altitude)
25
+ radial_velocity: npt.NDArray[np.float64] # dim: (time, altitude)
26
+ radial_velocity_deviation: npt.NDArray[np.float64] # dim: (time, altitude)
27
+ vh: npt.NDArray[np.float64] # dim: (time, altitude)
28
+ wind_direction: npt.NDArray[np.float64] # dim: (time, altitude)
29
+ zonal_wind: npt.NDArray[np.float64] # u := zonal wind?, dim: (time, altitude)
30
+ meridional_wind: npt.NDArray[
31
+ np.float64
32
+ ] # v := meridional wind?, dim: (time, altitude)
33
+ vertical_wind: npt.NDArray[np.float64] # w := vertical wind?, dim: (time, altitude)
34
+
35
+ @classmethod
36
+ def from_srcs(
37
+ cls,
38
+ data: Sequence[str]
39
+ | Sequence[Path]
40
+ | Sequence[bytes]
41
+ | Sequence[BufferedIOBase],
42
+ ) -> list[Wls70]:
43
+ if not isinstance(data, (list, tuple)):
44
+ raise TypeError("data should be list or tuple")
45
+ if all(isinstance(src, bytes) for src in data):
46
+ data_bytes = data
47
+ elif all(isinstance(src, str) for src in data):
48
+ data_bytes = []
49
+ for src in data:
50
+ with Path(src).open("rb") as f:
51
+ data_bytes.append(f.read())
52
+ elif all(isinstance(src, Path) for src in data):
53
+ data_bytes = []
54
+ for src in data:
55
+ with src.open("rb") as f:
56
+ data_bytes.append(f.read())
57
+ elif all(isinstance(src, BufferedIOBase) for src in data):
58
+ data_bytes = [src.read() for src in data]
59
+ else:
60
+ raise TypeError("Unexpected types in data")
61
+ raws = doppy.rs.raw.wls70.from_bytes_srcs(data_bytes)
62
+ try:
63
+ return [_raw_rs_to_wls70(r) for r in raws]
64
+ except RuntimeError as err:
65
+ raise exceptions.RawParsingError(err) from err
66
+
67
+ @classmethod
68
+ def from_src(cls, data: str | Path | bytes | BufferedIOBase) -> Wls70:
69
+ if isinstance(data, str):
70
+ path = Path(data)
71
+ with path.open("rb") as f:
72
+ data_bytes = f.read()
73
+ elif isinstance(data, Path):
74
+ with data.open("rb") as f:
75
+ data_bytes = f.read()
76
+ elif isinstance(data, bytes):
77
+ data_bytes = data
78
+ elif isinstance(data, BufferedIOBase):
79
+ data_bytes = data.read()
80
+ else:
81
+ raise TypeError("Unsupported data type")
82
+ try:
83
+ return _raw_rs_to_wls70(doppy.rs.raw.wls70.from_bytes_src(data_bytes))
84
+ except RuntimeError as err:
85
+ raise exceptions.RawParsingError(err) from err
86
+
87
+ def __getitem__(
88
+ self,
89
+ index: int
90
+ | slice
91
+ | list[int]
92
+ | npt.NDArray[np.int64]
93
+ | npt.NDArray[np.bool_]
94
+ | tuple[slice, slice],
95
+ ) -> Wls70:
96
+ if isinstance(index, (int, slice, list, np.ndarray)):
97
+ return Wls70(
98
+ time=self.time[index],
99
+ altitude=self.altitude,
100
+ position=self.position[index],
101
+ temperature=self.temperature[index],
102
+ wiper=self.wiper[index],
103
+ cnr=self.cnr[index],
104
+ radial_velocity=self.radial_velocity[index],
105
+ radial_velocity_deviation=self.radial_velocity_deviation[index],
106
+ vh=self.vh[index],
107
+ wind_direction=self.wind_direction[index],
108
+ zonal_wind=self.zonal_wind[index],
109
+ meridional_wind=self.meridional_wind[index],
110
+ vertical_wind=self.vertical_wind[index],
111
+ )
112
+ raise TypeError
113
+
114
+ def sorted_by_time(self) -> Wls70:
115
+ sort_indices = np.argsort(self.time)
116
+ return self[sort_indices]
117
+
118
+ @classmethod
119
+ def merge(cls, raws: Sequence[Wls70]) -> Wls70:
120
+ return cls(
121
+ time=np.concatenate(tuple(r.time for r in raws)),
122
+ altitude=raws[0].altitude,
123
+ position=np.concatenate(tuple(r.position for r in raws)),
124
+ temperature=np.concatenate(tuple(r.temperature for r in raws)),
125
+ wiper=np.concatenate(tuple(r.wiper for r in raws)),
126
+ cnr=np.concatenate(tuple(r.cnr for r in raws)),
127
+ radial_velocity=np.concatenate(tuple(r.radial_velocity for r in raws)),
128
+ radial_velocity_deviation=np.concatenate(
129
+ tuple(r.radial_velocity_deviation for r in raws)
130
+ ),
131
+ vh=np.concatenate(tuple(r.vh for r in raws)),
132
+ wind_direction=np.concatenate(tuple(r.wind_direction for r in raws)),
133
+ zonal_wind=np.concatenate(tuple(r.zonal_wind for r in raws)),
134
+ meridional_wind=np.concatenate(tuple(r.meridional_wind for r in raws)),
135
+ vertical_wind=np.concatenate(tuple(r.vertical_wind for r in raws)),
136
+ )
137
+
138
+ def non_strictly_increasing_timesteps_removed(self) -> Wls70:
139
+ if len(self.time) == 0:
140
+ return self
141
+ mask = np.ones_like(self.time, dtype=np.bool_)
142
+ latest_time = self.time[0]
143
+ for i, t in enumerate(self.time[1:], start=1):
144
+ if t <= latest_time:
145
+ mask[i] = False
146
+ else:
147
+ latest_time = t
148
+ return self[mask]
149
+
150
+
151
+ def _raw_rs_to_wls70(
152
+ raw_rs: tuple[
153
+ dict[str, npt.NDArray[np.float64]], list[str], npt.NDArray[np.float64]
154
+ ],
155
+ ) -> Wls70:
156
+ info, cols, data = raw_rs
157
+ altitude = info["altitude"]
158
+ data = data.reshape(-1, len(cols))
159
+ time_ts = data[:, 0]
160
+ time = np.array([datetime64(datetime.utcfromtimestamp(ts)) for ts in time_ts])
161
+
162
+ position = data[:, 1]
163
+ temperature = data[:, 2]
164
+ wiper = np.array(np.isclose(data[:, 3], 1), dtype=np.bool_)
165
+ cnr = data[:, 4::8]
166
+ rws = data[:, 5::8]
167
+ rwsd = data[:, 6::8]
168
+ vh = data[:, 7::8]
169
+ direction = data[:, 8::8]
170
+ u = data[:, 9::8]
171
+ v = data[:, 10::8]
172
+ w = data[:, 11::8]
173
+
174
+ return Wls70(
175
+ time=time,
176
+ altitude=altitude,
177
+ position=position,
178
+ temperature=temperature,
179
+ wiper=wiper,
180
+ cnr=cnr,
181
+ radial_velocity=rws,
182
+ radial_velocity_deviation=rwsd,
183
+ vh=vh,
184
+ wind_direction=direction,
185
+ zonal_wind=u,
186
+ meridional_wind=v,
187
+ vertical_wind=w,
188
+ )
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