cloudnetpy 1.47.0__py3-none-any.whl → 1.47.2__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,6 +1,6 @@
1
1
  import datetime
2
2
  import logging
3
- from collections.abc import Callable, Iterator
3
+ from collections.abc import Callable, Iterator, Sequence
4
4
  from pathlib import Path
5
5
  from typing import Any, Literal, TypeVar
6
6
 
@@ -21,7 +21,7 @@ def parsivel2nc(
21
21
  site_meta: dict,
22
22
  uuid: str | None = None,
23
23
  date: str | datetime.date | None = None,
24
- telegram: list[int] | None = None,
24
+ telegram: Sequence[int | None] | None = None,
25
25
  ) -> str:
26
26
  """Converts OTT Parsivel-2 disdrometer data into Cloudnet Level 1b netCDF
27
27
  file.
@@ -34,8 +34,9 @@ def parsivel2nc(
34
34
  uuid: Set specific UUID for the file.
35
35
  date: Expected date of the measurements as YYYY-MM-DD.
36
36
  telegram: List of measured value numbers as specified in section 11.2 of
37
- the instrument's operating instructions. Must be defined if the
38
- input file doesn't contain a header.
37
+ the instrument's operating instructions. Unknown values are indicated
38
+ with None. Telegram is required if the input file doesn't contain a
39
+ header.
39
40
 
40
41
  Returns:
41
42
  UUID of the generated file.
@@ -69,7 +70,7 @@ class Parsivel(CloudnetInstrument):
69
70
  self,
70
71
  filename: Path | str | bytes,
71
72
  site_meta: dict,
72
- telegram: list[int] | None = None,
73
+ telegram: Sequence[int | None] | None = None,
73
74
  expected_date: datetime.date | None = None,
74
75
  ):
75
76
  super().__init__()
@@ -204,6 +205,9 @@ TELEGRAM = {
204
205
  16: "I_heating",
205
206
  17: "V_power_supply",
206
207
  18: "state_sensor",
208
+ 19: "_datetime",
209
+ 20: "_time",
210
+ 21: "_date",
207
211
  22: "_station_name",
208
212
  24: "_rain_amount_absolute",
209
213
  25: "error_code",
@@ -231,12 +235,19 @@ def _parse_int(tokens: Iterator[str]) -> int:
231
235
 
232
236
 
233
237
  def _parse_float(tokens: Iterator[str]) -> float:
234
- return float(next(tokens))
238
+ token = next(tokens)
239
+ token = token.replace(",", ".")
240
+ return float(token)
235
241
 
236
242
 
237
243
  def _parse_date(tokens: Iterator[str]) -> datetime.date:
238
244
  token = next(tokens)
239
- year, month, day = token.split("/")
245
+ if "/" in token:
246
+ year, month, day = token.split("/")
247
+ elif "." in token:
248
+ day, month, year = token.split(".")
249
+ else:
250
+ raise ValueError(f"Unsupported date: '{input}'")
240
251
  if len(year) != 4:
241
252
  raise ValueError(f"Unsupported date: '{input}'")
242
253
  return datetime.date(int(year), int(month), int(day))
@@ -260,8 +271,7 @@ def _parse_datetime(tokens: Iterator[str]) -> datetime.datetime:
260
271
 
261
272
 
262
273
  def _parse_vector(tokens: Iterator[str]) -> np.ndarray:
263
- values = [float(x) for x in _take(tokens, 32)]
264
- return np.array(values)
274
+ return np.array([_parse_float(tokens) for _i in range(32)])
265
275
 
266
276
 
267
277
  def _parse_spectrum(tokens: Iterator[str]) -> np.ndarray:
@@ -285,14 +295,8 @@ PARSERS: dict[str, Callable[[Iterator[str]], Any]] = {
285
295
  "T_sensor": _parse_int,
286
296
  "V_power_supply": _parse_float,
287
297
  "_date": _parse_date,
288
- "_dsp_firmware_version": next,
289
- "_iop_firmware_version": next,
290
- "_metar": next,
291
- "_nws": next,
292
298
  "_rain_accum": _parse_float,
293
299
  "_rain_amount_absolute": _parse_float,
294
- "_sensor_id": next,
295
- "_station_name": next,
296
300
  "_time": _parse_time,
297
301
  "error_code": _parse_int,
298
302
  "fall_velocity": _parse_vector,
@@ -300,7 +304,7 @@ PARSERS: dict[str, Callable[[Iterator[str]], Any]] = {
300
304
  "kinetic_energy": _parse_float,
301
305
  "n_particles": _parse_int,
302
306
  "number_concentration": _parse_vector,
303
- "time": _parse_datetime,
307
+ "_datetime": _parse_datetime,
304
308
  "radar_reflectivity": _parse_float,
305
309
  "rainfall_rate": _parse_float,
306
310
  "sig_laser": _parse_int,
@@ -316,8 +320,11 @@ def _parse_headers(line: str) -> list[str]:
316
320
  return [HEADERS[header.strip()] for header in line.split(";")]
317
321
 
318
322
 
319
- def _parse_telegram(telegram: list[int]) -> list[str]:
320
- return ["time"] + [TELEGRAM[header] for header in telegram]
323
+ def _parse_telegram(telegram: Sequence[int | None]) -> list[str]:
324
+ return [
325
+ TELEGRAM[num] if num is not None else f"_unknown_{i}"
326
+ for i, num in enumerate(telegram)
327
+ ]
321
328
 
322
329
 
323
330
  def _read_rows(headers: list[str], rows: list[str]) -> dict[str, list]:
@@ -328,7 +335,7 @@ def _read_rows(headers: list[str], rows: list[str]) -> dict[str, list]:
328
335
  continue
329
336
  try:
330
337
  tokens = iter(row.removesuffix(";").split(";"))
331
- parsed = [PARSERS[header](tokens) for header in headers]
338
+ parsed = [PARSERS.get(header, next)(tokens) for header in headers]
332
339
  unread_tokens = list(tokens)
333
340
  if unread_tokens:
334
341
  raise ValueError("More values than expected")
@@ -345,7 +352,7 @@ def _read_rows(headers: list[str], rows: list[str]) -> dict[str, list]:
345
352
 
346
353
 
347
354
  def _read_parsivel(
348
- filename: Path | str | bytes, telegram: list[int] | None = None
355
+ filename: Path | str | bytes, telegram: Sequence[int | None] | None = None
349
356
  ) -> dict[str, np.ndarray]:
350
357
  with open(filename, encoding="latin1", errors="ignore") as file:
351
358
  lines = file.read().splitlines()
@@ -354,17 +361,16 @@ def _read_parsivel(
354
361
  if "Date" in lines[0]:
355
362
  headers = _parse_headers(lines[0])
356
363
  data = _read_rows(headers, lines[1:])
357
- data["time"] = [
358
- datetime.datetime.combine(date, time)
359
- for date, time in zip(data["_date"], data["_time"])
360
- ]
361
- del data["_date"]
362
- del data["_time"]
363
364
  elif telegram is not None:
364
365
  headers = _parse_telegram(telegram)
365
366
  data = _read_rows(headers, lines)
366
367
  else:
367
368
  raise ValueError("telegram must be specified for files without header")
369
+ if "_datetime" not in data:
370
+ data["_datetime"] = [
371
+ datetime.datetime.combine(date, time)
372
+ for date, time in zip(data["_date"], data["_time"])
373
+ ]
368
374
  result = {key: np.array(value) for key, value in data.items()}
369
- result["time"] = result["time"].astype("datetime64[s]")
375
+ result["time"] = result["_datetime"].astype("datetime64[s]")
370
376
  return result
@@ -12,6 +12,8 @@ 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
+
15
17
  def __init__(
16
18
  self, file_name: str, site_meta: dict, expected_date: str | None = None
17
19
  ):
@@ -19,12 +21,13 @@ class LufftCeilo(NcLidar):
19
21
  self.file_name = file_name
20
22
  self.site_meta = site_meta
21
23
  self.expected_date = expected_date
22
- self.instrument = self._get_chm_model()
24
+ self.serial_number = None
23
25
 
24
26
  def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
25
27
  """Reads data and metadata from Jenoptik netCDF file."""
26
28
  with netCDF4.Dataset(self.file_name) as dataset:
27
29
  self.dataset = dataset
30
+ self._fetch_attributes()
28
31
  self._fetch_range(reference="upper")
29
32
  self._fetch_beta_raw(calibration_factor)
30
33
  self._fetch_time_and_date()
@@ -77,9 +80,12 @@ class LufftCeilo(NcLidar):
77
80
  return var[0] if utils.isscalar(var) else var[:]
78
81
  raise ValueError("Unknown variable")
79
82
 
80
- def _get_chm_model(self):
81
- with netCDF4.Dataset(self.file_name) as nc:
82
- source = getattr(nc, "source", "")[:3].lower()
83
- if source == "chx":
84
- return instruments.CHM15KX
85
- return instruments.CHM15K
83
+ def _fetch_attributes(self):
84
+ self.serial_number = getattr(self.dataset, "device_name", None)
85
+ if self.serial_number is None:
86
+ self.serial_number = getattr(self.dataset, "source")
87
+ self.instrument = (
88
+ instruments.CHM15KX
89
+ if self.serial_number.startswith("CHX")
90
+ else instruments.CHM15K
91
+ )
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 47
3
- PATCH = 0
3
+ PATCH = 2
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.47.0
3
+ Version: 1.47.2
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=4stvQT5oxNdxu6Bh7NbT5LH65CIpzhfGA_Cey5Ai6m4,5022
8
8
  cloudnetpy/output.py,sha256=Oaj5w_C8rIFQrt2i9WZtvOjliBZgHo9Rm-HO1n6wazQ,13071
9
9
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  cloudnetpy/utils.py,sha256=Rsh_ojLNMH5ps7fWLKvF74Xd9B1yC9JqL507eRljyDQ,26973
11
- cloudnetpy/version.py,sha256=67sm74BYbuFFYHeq-EL-buEgbFD_IQ9vSQKZtLPNhC4,72
11
+ cloudnetpy/version.py,sha256=a1VwkoLwSdAt59hSaL7yzIODKVJxnFcS6tSnZws2z64,72
12
12
  cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
13
13
  cloudnetpy/categorize/atmos.py,sha256=-Z1y1Eaju7pG2yOIgUeIf-xH21HRxzWfJ9AOIrVL4A8,12389
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=6WdfGqzOvnaDW7vlMMrZBJIxW_eHQdjH-Xl_iPv1TTI,3716
@@ -34,7 +34,7 @@ cloudnetpy/instruments/copernicus.py,sha256=FDS7Rsunp4ieTPFh_T_LXvreNi5_HTv4ZzR3
34
34
  cloudnetpy/instruments/galileo.py,sha256=F_XyoAb9PR-ifGhqhXziKnv0KfyOh-yEBaE1NgRMzNg,4318
35
35
  cloudnetpy/instruments/hatpro.py,sha256=ftXVbYx2xtQcRZ2zVF82jUP7R96YV5Wdgcz_PUNZTXM,4824
36
36
  cloudnetpy/instruments/instruments.py,sha256=FGiN0369--1z8uYzgDP9_1A7DFLL_Cl4-Ca-OMsjBdI,2824
37
- cloudnetpy/instruments/lufft.py,sha256=o6sY1L82EgfaBXBwFpomHmIz37cP8WHu0WDoiUROvyQ,3226
37
+ cloudnetpy/instruments/lufft.py,sha256=E2qzvya206gNiML7BSM6vm1lzeOMBePrIuPT81NHPvw,3397
38
38
  cloudnetpy/instruments/mira.py,sha256=wtcgTR_nqL_BPZ5yqvBYXpI9Qtwdvc_1v6N_r3hCEPc,4888
39
39
  cloudnetpy/instruments/nc_lidar.py,sha256=9FSsInEM8fdyyhgNXb2INBjb5OEGFE5ZaVSowHjzoCA,1386
40
40
  cloudnetpy/instruments/nc_radar.py,sha256=hjmtgLuBPnfsyRInOZeKzAw3oZ82WSumrlPpycqBbjk,5530
@@ -46,7 +46,7 @@ cloudnetpy/instruments/vaisala.py,sha256=wOziQs_NuPTcZm8fg0UH9HCslhBCWromyxulQgS
46
46
  cloudnetpy/instruments/weather_station.py,sha256=gwPh4opv8SDf_vi4yBq5oHe8IFxGUF7ktB5yOBerd1g,5829
47
47
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
48
48
  cloudnetpy/instruments/disdrometer/common.py,sha256=_IWuhRtFIiYdIUjnS9R715C4eyr0dGd96ZDvQWJYetc,15006
49
- cloudnetpy/instruments/disdrometer/parsivel.py,sha256=EIq61eZIU4RyTGIN4caQieGfVhbsFE77fbGk9JL0u7I,12552
49
+ cloudnetpy/instruments/disdrometer/parsivel.py,sha256=_AGqOl2_vC927MJ2vn9xArHZDw0kWhUKxxxusCyv4CY,12821
50
50
  cloudnetpy/instruments/disdrometer/thies.py,sha256=YB328052yqlkOW7sGZSfUUCwLS3GPzYrWDiSg8C3C-s,5022
51
51
  cloudnetpy/model_evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  cloudnetpy/model_evaluation/file_handler.py,sha256=xYEkCsgwpF1kQNkG3ydxGM6DjFd-tryZ9ULlA9HUIAE,6321
@@ -103,8 +103,8 @@ cloudnetpy/products/lwc.py,sha256=CqvV7iqxUbk2YekaocBActmZuhF1BhWkfc-y6MDej-0,18
103
103
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
104
104
  cloudnetpy/products/product_tools.py,sha256=9eKvr-zGEK2ARN2DmJN--KbynKdT08DhfT1GB8Rfoe4,9616
105
105
  docs/source/conf.py,sha256=baQlgkkUGJi4952W6NRhLkIBbRtwFgqrIOBuEeSCLfk,1488
106
- cloudnetpy-1.47.0.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
107
- cloudnetpy-1.47.0.dist-info/METADATA,sha256=Z2SzijgI_f7RfaPHEty3pJmqnZ1VZdNv7Cc6fHEzOq8,5638
108
- cloudnetpy-1.47.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
109
- cloudnetpy-1.47.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
110
- cloudnetpy-1.47.0.dist-info/RECORD,,
106
+ cloudnetpy-1.47.2.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
107
+ cloudnetpy-1.47.2.dist-info/METADATA,sha256=TdlQti8TZ1wKHHG1coPydiB7fUkj2MrRIDFfSGjXE44,5638
108
+ cloudnetpy-1.47.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
109
+ cloudnetpy-1.47.2.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
110
+ cloudnetpy-1.47.2.dist-info/RECORD,,