cloudnetpy 1.50.0__py3-none-any.whl → 1.51.1__py3-none-any.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.
@@ -1,11 +1,12 @@
1
- import logging
1
+ import binascii
2
2
  import re
3
3
  from datetime import datetime
4
+ from typing import NamedTuple
4
5
 
5
6
  import numpy as np
6
7
 
7
8
  from cloudnetpy import utils
8
- from cloudnetpy.exceptions import ValidTimeStampError
9
+ from cloudnetpy.exceptions import InconsistentDataError, ValidTimeStampError
9
10
  from cloudnetpy.instruments import instruments
10
11
  from cloudnetpy.instruments.ceilometer import Ceilometer
11
12
 
@@ -23,39 +24,40 @@ class Cs135(Ceilometer):
23
24
  self.instrument = instruments.CS135
24
25
 
25
26
  def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
26
- with open(self.full_path, mode="r", encoding="utf-8") as f:
27
- lines = f.readlines()
28
- timestamps, profiles, scales, tilt_angles = [], [], [], []
29
- range_resolution, n_gates = 0, 0
30
- for i, line in enumerate(lines):
31
- line_splat = line.strip().split(",")
32
- if is_timestamp(line_splat[0]):
33
- timestamp = datetime.strptime(line_splat[0], "%Y-%m-%dT%H:%M:%S.%f")
34
- try:
35
- self._check_timestamp(timestamp)
36
- except ValidTimeStampError:
37
- continue
38
- timestamps.append(timestamp)
39
- _line1 = line_splat[1]
40
- if len(_line1) != 11:
41
- raise NotImplementedError("Unknown message number")
42
- if (msg_no := _line1[-4:-1]) != "002":
43
- raise NotImplementedError(
44
- f"Message number {msg_no} not implemented"
45
- )
46
- _line3 = lines[i + 2].strip().split(" ")
47
- scale, range_resolution, n_gates, tilt_angle = [
48
- int(_line3[ind]) for ind in [0, 1, 2, 5]
49
- ]
50
- scales.append(scale)
51
- tilt_angles.append(tilt_angle)
52
- _line4 = lines[i + 3].strip()
53
- profiles.append(_hex2backscatter(_line4, n_gates))
27
+ with open(self.full_path, mode="rb") as f:
28
+ content = f.read()
29
+ timestamps = []
30
+ profiles = []
31
+ tilt_angles = []
32
+ range_resolutions = []
33
+
34
+ parts = re.split(rb"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}),", content)
35
+ for i in range(1, len(parts), 2):
36
+ timestamp = datetime.strptime(parts[i].decode(), "%Y-%m-%dT%H:%M:%S.%f")
37
+ try:
38
+ self._check_timestamp(timestamp)
39
+ except ValidTimeStampError:
40
+ continue
41
+ try:
42
+ message = _read_message(parts[i + 1])
43
+ except InvalidMessageError:
44
+ continue
45
+ profile = (message.data[:-2] * 1e-8) * (message.scale / 100)
46
+ timestamps.append(timestamp)
47
+ profiles.append(profile)
48
+ tilt_angles.append(message.tilt_angle)
49
+ range_resolutions.append(message.range_resolution)
54
50
 
55
51
  if len(timestamps) == 0:
56
52
  raise ValidTimeStampError("No valid timestamps found in the file")
57
- array = self._handle_large_values(np.array(profiles))
58
- self.data["beta_raw"] = _scale_backscatter(array, scales)
53
+ range_resolution = range_resolutions[0]
54
+ n_gates = len(profiles[0])
55
+ if any(res != range_resolution for res in range_resolutions):
56
+ raise InconsistentDataError("Inconsistent range resolution")
57
+ if any(len(profile) != n_gates for profile in profiles):
58
+ raise InconsistentDataError("Inconsistent number of gates")
59
+
60
+ self.data["beta_raw"] = np.array(profiles)
59
61
  if calibration_factor is None:
60
62
  calibration_factor = 1.0
61
63
  self.data["beta_raw"] *= calibration_factor
@@ -75,36 +77,59 @@ class Cs135(Ceilometer):
75
77
  self.date = timestamp_components
76
78
  assert timestamp_components == self.date
77
79
 
78
- @staticmethod
79
- def _handle_large_values(array: np.ndarray) -> np.ndarray:
80
- ind = np.where(array > 524287)
81
- if ind[0].size > 0:
82
- array[ind] -= 1048576
83
- return array
84
80
 
81
+ class Message(NamedTuple):
82
+ scale: int
83
+ range_resolution: int
84
+ laser_pulse_energy: int
85
+ laser_temperature: int
86
+ tilt_angle: int
87
+ background_light: int
88
+ pulse_quantity: int
89
+ sample_rate: int
90
+ data: np.ndarray
91
+
92
+
93
+ class InvalidMessageError(Exception):
94
+ pass
85
95
 
86
- def is_timestamp(timestamp: str) -> bool:
87
- """Tests if the input string is formatted as -yyyy-mm-dd hh:mm:ss"""
88
- reg_exp = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}")
89
- if reg_exp.match(timestamp) is not None:
90
- return True
91
- return False
96
+
97
+ def _read_message(message: bytes) -> Message:
98
+ end_idx = message.index(3)
99
+ content = message[1 : end_idx + 1]
100
+ expected_checksum = int(message[end_idx + 1 : end_idx + 5], 16)
101
+ actual_checksum = _crc16(content)
102
+ if expected_checksum != actual_checksum:
103
+ raise InvalidMessageError(
104
+ "Invalid checksum: "
105
+ f"expected {expected_checksum:04x}, "
106
+ f"got {actual_checksum:04x}"
107
+ )
108
+ lines = message.splitlines()
109
+ if len(lines[0]) != 11:
110
+ raise NotImplementedError("Unknown message format")
111
+ if (msg_no := lines[0][-4:-1]) != b"002":
112
+ raise NotImplementedError(f"Message number {msg_no.decode()} not implemented")
113
+ if len(lines) != 5:
114
+ raise InvalidMessageError("Invalid line count")
115
+ scale, res, n, energy, lt, ti, bl, pulse, rate, _sum = map(int, lines[2].split())
116
+ data = _read_backscatter(lines[3].strip(), n)
117
+ return Message(scale, res, energy, lt, ti, bl, pulse, rate, data)
92
118
 
93
119
 
94
- def _hex2backscatter(data: str, n_gates: int):
95
- """Converts hex string to backscatter values."""
120
+ def _read_backscatter(data: bytes, n_gates: int) -> np.ndarray:
121
+ """Read backscatter values from hex-encoded two's complement values."""
96
122
  n_chars = 5
97
- return [
98
- int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)
99
- ]
100
-
101
-
102
- def _scale_backscatter(data: np.ndarray, scales: list) -> np.ndarray:
103
- """Scales backscatter values."""
104
- unit_conversion_factor = 1e-8
105
- scales_array = np.array(scales)
106
- ind = np.where(scales_array != 100)
107
- if ind[0].size > 0:
108
- logging.info(f"{ind[0].size} profiles have not 100% scaling")
109
- data[ind, :] *= scales_array[ind] / 100
110
- return data * unit_conversion_factor
123
+ n_bits = n_chars * 4
124
+ limit = (1 << (n_bits - 1)) - 1
125
+ offset = 1 << n_bits
126
+ out = np.array(
127
+ [int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)]
128
+ )
129
+ out[out > limit] -= offset
130
+ return out
131
+
132
+
133
+ def _crc16(data: bytes) -> int:
134
+ """Compute checksum similar to CRC-16-CCITT."""
135
+ return binascii.crc_hqx(data, 0xFFFF) ^ 0xFFFF
@@ -21,6 +21,8 @@ class NoiseParam:
21
21
  class Ceilometer:
22
22
  """Base class for all types of ceilometers and pollyxt."""
23
23
 
24
+ serial_number: str | None
25
+
24
26
  def __init__(self, noise_param: NoiseParam = NoiseParam()):
25
27
  self.noise_param = noise_param
26
28
  self.data: dict = {} # Need to contain 'beta_raw', 'range' and 'time'
@@ -29,6 +31,7 @@ class Ceilometer:
29
31
  self.site_meta: dict = {}
30
32
  self.date: list[str] = []
31
33
  self.instrument: Instrument | None = None
34
+ self.serial_number = None
32
35
 
33
36
  def calc_screened_product(
34
37
  self,
@@ -23,7 +23,8 @@ class Cl61d(NcLidar):
23
23
  """Reads data and metadata from concatenated Vaisala CL61d netCDF file."""
24
24
  with netCDF4.Dataset(self.file_name) as dataset:
25
25
  self.dataset = dataset
26
- self._fetch_zenith_angle("zenith", default=3.0)
26
+ self._fetch_attributes()
27
+ self._fetch_zenith_angle("tilt_angle", default=3.0)
27
28
  self._fetch_range(reference="lower")
28
29
  self._fetch_lidar_variables(calibration_factor)
29
30
  self._fetch_time_and_date()
@@ -41,3 +42,6 @@ class Cl61d(NcLidar):
41
42
  self.data["depolarisation"] = (
42
43
  self.dataset.variables["x_pol"][:] / self.dataset.variables["p_pol"][:]
43
44
  )
45
+
46
+ def _fetch_attributes(self):
47
+ self.serial_number = getattr(self.dataset, "instrument_serial_number", None)
@@ -1,5 +1,7 @@
1
+ import csv
1
2
  import datetime
2
3
  import logging
4
+ import re
3
5
  from collections.abc import Callable, Iterator, Sequence
4
6
  from itertools import islice
5
7
  from pathlib import Path
@@ -169,7 +171,7 @@ class Parsivel(CloudnetInstrument):
169
171
  raise ValueError
170
172
 
171
173
 
172
- HEADERS = {
174
+ CSV_HEADERS = {
173
175
  "Date": "_date",
174
176
  "Time": "_time",
175
177
  "Intensity of precipitation (mm/h)": "rainfall_rate",
@@ -190,6 +192,47 @@ HEADERS = {
190
192
  "Spectrum": "spectrum",
191
193
  }
192
194
 
195
+ TOA5_HEADERS = {
196
+ "TIMESTAMP": "_datetime",
197
+ "rainIntensity": "rainfall_rate",
198
+ "rain_intensity": "rainfall_rate",
199
+ "snowIntensity": "snowfall_rate",
200
+ "snow_intensity": "snowfall_rate",
201
+ "accPrec": "_rain_accum",
202
+ "precipitation": "_rain_accum",
203
+ "weatherCodeWaWa": "synop_WaWa",
204
+ "weather_code_wawa": "synop_WaWa",
205
+ "radarReflectivity": "radar_reflectivity",
206
+ "radar_reflectivity": "radar_reflectivity",
207
+ "morVisibility": "visibility",
208
+ "mor_visibility": "visibility",
209
+ "kineticEnergy": "kinetic_energy",
210
+ "kinetic_energy": "kinetic_energy",
211
+ "signalAmplitude": "sig_laser",
212
+ "signal_amplitude": "sig_laser",
213
+ "sensorTemperature": "T_sensor",
214
+ "sensor_temperature": "T_sensor",
215
+ "pbcTemperature": "_T_pcb",
216
+ "pbc_temperature": "_T_pcb",
217
+ "rightTemperature": "_T_right",
218
+ "right_temperature": "_T_right",
219
+ "leftTemperature": "_T_left",
220
+ "left_temperature": "_T_left",
221
+ "heatingCurrent": "I_heating",
222
+ "heating_current": "I_heating",
223
+ "sensorVoltage": "V_power_supply",
224
+ "sensor_voltage": "V_power_supply",
225
+ "sensorStatus": "state_sensor",
226
+ "sensor_status": "state_sensor",
227
+ "errorCode": "error_code",
228
+ "error_code": "error_code",
229
+ "numberParticles": "n_particles",
230
+ "number_particles": "n_particles",
231
+ "N": "number_concentration",
232
+ "V": "fall_velocity",
233
+ "spectrum": "spectrum",
234
+ }
235
+
193
236
  TELEGRAM = {
194
237
  1: "rainfall_rate",
195
238
  2: "_rain_accum",
@@ -325,7 +368,7 @@ PARSERS: dict[str, Callable[[Iterator[str]], Any]] = {
325
368
 
326
369
 
327
370
  def _parse_headers(line: str) -> list[str]:
328
- return [HEADERS[header.strip()] for header in line.split(";")]
371
+ return [CSV_HEADERS[header.strip()] for header in line.split(";")]
329
372
 
330
373
 
331
374
  def _parse_telegram(telegram: Sequence[int | None]) -> list[str]:
@@ -359,6 +402,71 @@ def _read_rows(headers: list[str], rows: list[str]) -> dict[str, list]:
359
402
  return result
360
403
 
361
404
 
405
+ def _read_toa5(filename: Path | str | bytes) -> dict[str, list]:
406
+ """
407
+ Read ASCII data from Campbell Scientific datalogger such as CR1000.
408
+
409
+ References:
410
+ CR1000 Measurement and Control System.
411
+ https://s.campbellsci.com/documents/us/manuals/cr1000.pdf
412
+ """
413
+ # pylint: disable=too-many-branches,comparison-with-callable
414
+ with open(filename, encoding="latin1", errors="ignore") as file:
415
+ reader = csv.reader(file)
416
+ _origin_line = next(reader)
417
+ header_line = next(reader)
418
+ headers = [
419
+ TOA5_HEADERS.get(re.sub(r"\(.*", "", field)) for field in header_line
420
+ ]
421
+ _units_line = next(reader)
422
+ _process_line = next(reader)
423
+ data: dict[str, list] = {header: [] for header in headers if header is not None}
424
+ n_rows = 0
425
+ n_invalid_rows = 0
426
+ for data_line in reader:
427
+ n_rows += 1
428
+ scalars: dict[str, datetime.datetime | int | float] = {}
429
+ arrays: dict[str, list] = {
430
+ "number_concentration": [],
431
+ "fall_velocity": [],
432
+ "spectrum": [],
433
+ }
434
+ try:
435
+ for header, value in zip(headers, data_line):
436
+ if header is None:
437
+ continue
438
+ if header == "_datetime":
439
+ scalars[header] = datetime.datetime.strptime(
440
+ value, "%Y-%m-%d %H:%M:%S"
441
+ )
442
+ elif header in ("number_concentration", "fall_velocity"):
443
+ arrays[header].append(float(value))
444
+ elif header == "spectrum":
445
+ arrays[header].append(int(value))
446
+ elif PARSERS.get(header) == _parse_int:
447
+ scalars[header] = int(value)
448
+ elif PARSERS.get(header) == _parse_float:
449
+ scalars[header] = float(value)
450
+ except ValueError:
451
+ n_invalid_rows += 1
452
+ continue
453
+ for header, scalar in scalars.items():
454
+ data[header].append(scalar)
455
+ if "spectrum" in headers:
456
+ data["spectrum"].append(
457
+ np.array(arrays["spectrum"], dtype="i2").reshape((32, 32))
458
+ )
459
+ if "number_concentration" in headers:
460
+ data["number_concentration"].append(arrays["number_concentration"])
461
+ if "fall_velocity" in headers:
462
+ data["fall_velocity"].append(arrays["fall_velocity"])
463
+ if n_invalid_rows == n_rows:
464
+ raise DisdrometerDataError("No valid data in file")
465
+ if n_invalid_rows > 0:
466
+ logging.info(f"Skipped {n_invalid_rows} invalid rows")
467
+ return data
468
+
469
+
362
470
  def _read_parsivel(
363
471
  filename: Path | str | bytes, telegram: Sequence[int | None] | None = None
364
472
  ) -> dict[str, np.ndarray]:
@@ -366,7 +474,9 @@ def _read_parsivel(
366
474
  lines = file.read().splitlines()
367
475
  if not lines:
368
476
  raise DisdrometerDataError("File is empty")
369
- if "Date" in lines[0]:
477
+ if "TOA5" in lines[0]:
478
+ data = _read_toa5(filename)
479
+ elif "Date" in lines[0]:
370
480
  headers = _parse_headers(lines[0])
371
481
  data = _read_rows(headers, lines[1:])
372
482
  elif telegram is not None:
@@ -12,8 +12,6 @@ from cloudnetpy.instruments.nc_lidar import NcLidar
12
12
  class LufftCeilo(NcLidar):
13
13
  """Class for Lufft chm15k ceilometer."""
14
14
 
15
- serial_number: str | None
16
-
17
15
  def __init__(
18
16
  self, file_name: str, site_meta: dict, expected_date: str | None = None
19
17
  ):
@@ -21,7 +19,6 @@ class LufftCeilo(NcLidar):
21
19
  self.file_name = file_name
22
20
  self.site_meta = site_meta
23
21
  self.expected_date = expected_date
24
- self.serial_number = None
25
22
 
26
23
  def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
27
24
  """Reads data and metadata from Jenoptik netCDF file."""
@@ -30,7 +30,7 @@ class NcLidar(Ceilometer):
30
30
  def _fetch_zenith_angle(self, key: str, default: float = 3.0) -> None:
31
31
  assert self.dataset is not None
32
32
  if key in self.dataset.variables:
33
- zenith_angle = self.dataset.variables[key][:]
33
+ zenith_angle = np.median(self.dataset.variables[key][:])
34
34
  else:
35
35
  zenith_angle = float(default)
36
36
  logging.warning(f"No zenith angle found, assuming {zenith_angle} degrees")
@@ -103,6 +103,7 @@ class PollyXt(Ceilometer):
103
103
  raise InconsistentDataError(
104
104
  "Inconsistent number of pollyxt bsc / depol files"
105
105
  )
106
+ self._fetch_attributes(bsc_files[0])
106
107
  self.data["range"] = _read_array_from_multiple_files(
107
108
  bsc_files, depol_files, "height"
108
109
  )
@@ -157,6 +158,11 @@ class PollyXt(Ceilometer):
157
158
  return channel
158
159
  raise ValidTimeStampError("No functional pollyXT backscatter channels found")
159
160
 
161
+ def _fetch_attributes(self, file: str) -> None:
162
+ with netCDF4.Dataset(file, "r") as nc:
163
+ if hasattr(nc, "source"):
164
+ self.serial_number = nc.source.lower()
165
+
160
166
 
161
167
  def _read_array_from_multiple_files(files1: list, files2: list, key) -> np.ndarray:
162
168
  array: np.ndarray = np.array([])
@@ -755,7 +755,7 @@ def plot_2d(
755
755
 
756
756
 
757
757
  def compare_files(
758
- nc_files: list,
758
+ nc_files: tuple[str, str],
759
759
  field_name: str,
760
760
  show: bool = True,
761
761
  relative_err: bool = False,
@@ -790,7 +790,7 @@ def compare_files(
790
790
  ax_values = [_read_ax_values(nc_file) for nc_file in nc_files]
791
791
  subtitle = (
792
792
  f" - {os.path.basename(nc_files[0])}",
793
- f" - {os.path.basename(nc_files[0])}",
793
+ f" - {os.path.basename(nc_files[1])}",
794
794
  )
795
795
  n_subs = 3 if relative_err is True else 2
796
796
  fig, axes = _initialize_figure(n_subs, dpi)
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 50
3
- PATCH = 0
2
+ MINOR = 51
3
+ PATCH = 1
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.50.0
3
+ Version: 1.51.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -8,7 +8,7 @@ cloudnetpy/metadata.py,sha256=-oRmr4HWjG_-P8jOjdBYMgRkOYnJKr6jmGIF-38Tno8,5023
8
8
  cloudnetpy/output.py,sha256=6ysoCcrk_pS_fWhyQoJX29V403f7UOloFw0SZjHCwKk,14236
9
9
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  cloudnetpy/utils.py,sha256=lKVPF7VpbX2IPXTztI8eJZm8rLne9IuhMngQXKP12vg,26971
11
- cloudnetpy/version.py,sha256=f1gG8ukA8980EWUjCKF9qA8P9pYzoCtL8gGNb2eCEX0,72
11
+ cloudnetpy/version.py,sha256=Ja1UYLGffo0QdnwJpi9wS24Icrb4a0cV3o2mFuzPACo,72
12
12
  cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
13
13
  cloudnetpy/categorize/atmos.py,sha256=_8VU0UpzKh7ZFh3TbGs-g3SYMRsRIR5mio0PmP66O7o,12372
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=6WdfGqzOvnaDW7vlMMrZBJIxW_eHQdjH-Xl_iPv1TTI,3716
@@ -26,20 +26,20 @@ cloudnetpy/categorize/mwr.py,sha256=PSXf-OukhRLlQIpXtkKhcdgiy-fQy-X-CaVh_G42P9s,
26
26
  cloudnetpy/categorize/radar.py,sha256=3Or3_jWxs9rbwJ3XKzl4hPilg0bFdCRMrbkIAuS3H08,12425
27
27
  cloudnetpy/instruments/__init__.py,sha256=-MOKjKNu8PRciX_PXEBRihGVaKrPIc_2sR-n0D9NEkc,374
28
28
  cloudnetpy/instruments/basta.py,sha256=9KeP65uxOTUH1YavaI8sp35n-VcM-WgtqMfB3bxKSPo,3714
29
- cloudnetpy/instruments/campbell_scientific.py,sha256=HQMPLBMHUojNFnt4WV8cvGrNPfSvv_IUQVeVyC3KJyo,4237
29
+ cloudnetpy/instruments/campbell_scientific.py,sha256=Y7Muf7PCIjXCQvH7wNwB8_v_mgG0jUF3LHjPUalgTDw,4853
30
30
  cloudnetpy/instruments/ceilo.py,sha256=_T_rbvy0kB6hol5iwNLtfFqYDM7g4ce9Mlt_Z1Vg2hU,8013
31
- cloudnetpy/instruments/ceilometer.py,sha256=j3Wb2wJlSGLaZkguPe3nv4751TfGd-hJjithKYNOsO4,10498
32
- cloudnetpy/instruments/cl61d.py,sha256=Qk2YQRrRkivc6nW2gOI7KKLt9rR4JAWF0bfbj8Hd_lY,1653
31
+ cloudnetpy/instruments/ceilometer.py,sha256=xgmDJfwmbWeP6xrMMybktEzgA6tVzTWg3kiKmvpZf6g,10563
32
+ cloudnetpy/instruments/cl61d.py,sha256=jETxUIIjILZwkMx9YKG8pI7cUodNjVoc6dYCqaxW1Uw,1813
33
33
  cloudnetpy/instruments/cloudnet_instrument.py,sha256=nnaOtJJotXzYdoMoYGKeLZ1MciDYBx7tqVBFKBQjKL0,3304
34
34
  cloudnetpy/instruments/copernicus.py,sha256=FDS7Rsunp4ieTPFh_T_LXvreNi5_HTv4ZzR3OnTcAX8,5013
35
35
  cloudnetpy/instruments/galileo.py,sha256=F_XyoAb9PR-ifGhqhXziKnv0KfyOh-yEBaE1NgRMzNg,4318
36
36
  cloudnetpy/instruments/hatpro.py,sha256=H0FgTIxIp3fl59nrTS9z8NyRX7ugkR32B2N1uwEOWtQ,7438
37
37
  cloudnetpy/instruments/instruments.py,sha256=x_YV4UG05lF-39Yr-K3h5OrxSqrsndpF1K-KM0VURI0,2975
38
- cloudnetpy/instruments/lufft.py,sha256=E2qzvya206gNiML7BSM6vm1lzeOMBePrIuPT81NHPvw,3397
38
+ cloudnetpy/instruments/lufft.py,sha256=lZr_fz2vWQdPr4ZiWW7_wT7WlrMmPsQ2ko6MKygZv50,3332
39
39
  cloudnetpy/instruments/mira.py,sha256=5wmmJGYHVglxaCpSuL92WIisADRD-85k_jlsC9mKLgQ,5063
40
- cloudnetpy/instruments/nc_lidar.py,sha256=9FSsInEM8fdyyhgNXb2INBjb5OEGFE5ZaVSowHjzoCA,1386
40
+ cloudnetpy/instruments/nc_lidar.py,sha256=925SMVhbiadCYW_85EdGr_RNjNryZJbBXiffMRnu6Mc,1397
41
41
  cloudnetpy/instruments/nc_radar.py,sha256=hjmtgLuBPnfsyRInOZeKzAw3oZ82WSumrlPpycqBbjk,5530
42
- cloudnetpy/instruments/pollyxt.py,sha256=Ezuq9rJxQREKz5ed1sdMsuqHZwlKMQC_w5CZSpzQQko,7847
42
+ cloudnetpy/instruments/pollyxt.py,sha256=vs-_MwoC_rmTheqErNMaZjSg1drIKe7NXjtH1L73Mso,8085
43
43
  cloudnetpy/instruments/radiometrics.py,sha256=r73gDsB6ZSVRfMPkkf2mnhVSX8MxGTOuTjQyVmaQ5v8,7304
44
44
  cloudnetpy/instruments/rpg.py,sha256=szH59pXNKp6AfiL65_EQ5MRHSXduhO-uJoryfQpT7Mg,15741
45
45
  cloudnetpy/instruments/rpg_reader.py,sha256=8YaEZogUdIfIgVyZtvZ5f_lqm37SGcvmGCWdhaXG9xI,10711
@@ -47,7 +47,7 @@ cloudnetpy/instruments/vaisala.py,sha256=wOziQs_NuPTcZm8fg0UH9HCslhBCWromyxulQgS
47
47
  cloudnetpy/instruments/weather_station.py,sha256=gwPh4opv8SDf_vi4yBq5oHe8IFxGUF7ktB5yOBerd1g,5829
48
48
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
49
49
  cloudnetpy/instruments/disdrometer/common.py,sha256=_IWuhRtFIiYdIUjnS9R715C4eyr0dGd96ZDvQWJYetc,15006
50
- cloudnetpy/instruments/disdrometer/parsivel.py,sha256=q4h7VNkCHq0Qutf3P2BgNcX0ijB8CU9C90bsbI3L1WY,13069
50
+ cloudnetpy/instruments/disdrometer/parsivel.py,sha256=1LDl5FUm0uacVOWVorBC-u37ieStg4SBLCmrg970YnM,17345
51
51
  cloudnetpy/instruments/disdrometer/thies.py,sha256=YB328052yqlkOW7sGZSfUUCwLS3GPzYrWDiSg8C3C-s,5022
52
52
  cloudnetpy/model_evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  cloudnetpy/model_evaluation/file_handler.py,sha256=xYEkCsgwpF1kQNkG3ydxGM6DjFd-tryZ9ULlA9HUIAE,6321
@@ -91,7 +91,7 @@ cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=CPbFwL
91
91
  cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=84b1TxMogC1hyACQol7xthOWNkYXp3oZJF4GFokEkd4,3630
92
92
  cloudnetpy/plotting/__init__.py,sha256=3bhBlLx8o_SjVyuPxgT7mfZ145pd5erwcCoVNuj2z48,62
93
93
  cloudnetpy/plotting/plot_meta.py,sha256=CrRCwh5-YxOrIe2bJXnL9akdUGi-amsUCCHc5VoHAL8,26217
94
- cloudnetpy/plotting/plotting.py,sha256=kmB1pRt38tpDYLLJ38OyeDt-jCCOP-EMQsHzIpi4hco,28163
94
+ cloudnetpy/plotting/plotting.py,sha256=ynjzRm6o5yUO2YI5robhVyiOYaGN88hoxU_FkPfhRaY,28174
95
95
  cloudnetpy/products/__init__.py,sha256=hGkngQT-YAC5cmDiHkSkQw2ZBrg0hN2z40Fizz0QU5Y,210
96
96
  cloudnetpy/products/classification.py,sha256=0Y5dEVDZFbq3UcFnyHomml5Au12SSMVznQTgAMyqh2I,7701
97
97
  cloudnetpy/products/der.py,sha256=zDehcsSCwDTADmxrK4Dmy5VcsrJmDbb-t_SiSU-C3M0,12241
@@ -106,8 +106,8 @@ cloudnetpy/products/mwr_multi.py,sha256=9dw5DqU9uae54SDk0Pjzp4EKtQrjo1DeP-Xx41NE
106
106
  cloudnetpy/products/mwr_single.py,sha256=tfUYvkVf_Hh1GcpBnjjE8T30EYzyYc07UuzGJCBME-8,2931
107
107
  cloudnetpy/products/product_tools.py,sha256=Gk5e4N1m071IIFT9dy9lUvcDICsMYx-pMEtcWTJ54nw,9739
108
108
  docs/source/conf.py,sha256=baQlgkkUGJi4952W6NRhLkIBbRtwFgqrIOBuEeSCLfk,1488
109
- cloudnetpy-1.50.0.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
110
- cloudnetpy-1.50.0.dist-info/METADATA,sha256=mzFxNLsFFEPSD86rJ6RmgYmHdx9OA8zYONk3n_SL3h8,5759
111
- cloudnetpy-1.50.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
112
- cloudnetpy-1.50.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
113
- cloudnetpy-1.50.0.dist-info/RECORD,,
109
+ cloudnetpy-1.51.1.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
110
+ cloudnetpy-1.51.1.dist-info/METADATA,sha256=OChqYA3XkBzKFCvDLrrw90sY85E-OC6Si8_KQJINPN8,5759
111
+ cloudnetpy-1.51.1.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92
112
+ cloudnetpy-1.51.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
113
+ cloudnetpy-1.51.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.40.0)
2
+ Generator: bdist_wheel (0.41.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5