cloudnetpy 1.75.1__py3-none-any.whl → 1.76.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.
@@ -3,9 +3,11 @@
3
3
  import csv
4
4
  import datetime
5
5
  import logging
6
+ import math
6
7
  import os
7
8
  import re
8
9
  from operator import attrgetter
10
+ from pathlib import Path
9
11
  from typing import Any, NamedTuple
10
12
 
11
13
  import numpy as np
@@ -18,8 +20,8 @@ from cloudnetpy.metadata import MetaData
18
20
 
19
21
 
20
22
  def radiometrics2nc(
21
- full_path: str,
22
- output_file: str,
23
+ full_path: str | os.PathLike,
24
+ output_file: str | os.PathLike,
23
25
  site_meta: dict,
24
26
  uuid: str | None = None,
25
27
  date: str | datetime.date | None = None,
@@ -46,19 +48,11 @@ def radiometrics2nc(
46
48
  """
47
49
  if isinstance(date, str):
48
50
  date = datetime.date.fromisoformat(date)
49
-
50
51
  if os.path.isdir(full_path):
51
- valid_filenames = utils.get_sorted_filenames(full_path, ".csv")
52
+ valid_filenames = list(Path(full_path).iterdir())
52
53
  else:
53
- valid_filenames = [full_path]
54
-
55
- objs = []
56
- for filename in valid_filenames:
57
- obj = Radiometrics(filename)
58
- obj.read_raw_data()
59
- obj.read_data()
60
- objs.append(obj)
61
-
54
+ valid_filenames = [Path(full_path)]
55
+ objs = [_read_file(filename) for filename in valid_filenames]
62
56
  radiometrics = RadiometricsCombined(objs, site_meta)
63
57
  radiometrics.screen_time(date)
64
58
  radiometrics.sort_timestamps()
@@ -81,15 +75,16 @@ class Record(NamedTuple):
81
75
  values: dict[str, Any]
82
76
 
83
77
 
84
- class Radiometrics:
85
- """Reader for level 2 files of Radiometrics microwave radiometers.
78
+ class RadiometricsMP:
79
+ """Reader for level 2 files (*.csv) from Radiometrics MP-3000A and similar
80
+ microwave radiometers.
86
81
 
87
82
  References:
88
83
  Radiometrics (2008). Profiler Operator's Manual: MP-3000A, MP-2500A,
89
84
  MP-1500A, MP-183A.
90
85
  """
91
86
 
92
- def __init__(self, filename: str):
87
+ def __init__(self, filename: Path):
93
88
  self.filename = filename
94
89
  self.raw_data: list[Record] = []
95
90
  self.data: dict = {}
@@ -152,12 +147,19 @@ class Radiometrics:
152
147
  ahs = []
153
148
  ah_times = []
154
149
  block_titles = {}
150
+ skip_procs = set()
155
151
  for record in self.raw_data:
156
152
  if record.block_type == 100:
157
153
  block_type = int(record.values["Record Type"]) - 1
158
154
  title = record.values["Title"]
159
155
  block_titles[block_type] = title
160
156
  if title := block_titles.get(record.block_type + record.block_index):
157
+ # "LV2 Processor" values "Zenith" and "0.00:90.00" should be OK
158
+ # but "Angle20(N)" and similar should be skipped.
159
+ proc = record.values["LV2 Processor"]
160
+ if proc.startswith("Angle"):
161
+ skip_procs.add(proc)
162
+ continue
161
163
  if title == "Temperature (K)":
162
164
  temp_times.append(record.timestamp)
163
165
  temps.append(
@@ -194,6 +196,9 @@ class Radiometrics:
194
196
  irt = record.values["Tir(K)"]
195
197
  irts.append([float(irt)])
196
198
  elif record.block_type == 300:
199
+ if skip_procs:
200
+ skip_procs.pop()
201
+ continue
197
202
  lwp = record.values["Int. Liquid(mm)"]
198
203
  iwv = record.values["Int. Vapor(cm)"]
199
204
  times.append(record.timestamp)
@@ -202,26 +207,88 @@ class Radiometrics:
202
207
  self.data["time"] = np.array(times, dtype="datetime64[s]")
203
208
  self.data["lwp"] = np.array(lwps) # mm => kg m-2
204
209
  self.data["iwv"] = np.array(iwvs) * 10 # cm => kg m-2
205
- self.data["irt"] = _find_closest(
206
- np.array(irt_times, dtype="datetime64[s]"),
207
- np.array(irts),
208
- self.data["time"],
209
- )
210
- self.data["temperature"] = _find_closest(
211
- np.array(temp_times, dtype="datetime64[s]"),
212
- np.array(temps),
213
- self.data["time"],
214
- )
215
- self.data["relative_humidity"] = _find_closest(
216
- np.array(rh_times, dtype="datetime64[s]"),
217
- np.array(rhs) / 100, # % => 1
218
- self.data["time"],
219
- )
220
- self.data["absolute_humidity"] = _find_closest(
221
- np.array(ah_times, dtype="datetime64[s]"),
222
- np.array(ahs) / 1000, # g m-3 => kg m-3
223
- self.data["time"],
210
+ if irt_times:
211
+ self.data["irt"] = _find_closest(
212
+ np.array(irt_times, dtype="datetime64[s]"),
213
+ np.array(irts),
214
+ self.data["time"],
215
+ )
216
+ if temp_times:
217
+ self.data["temperature"] = _find_closest(
218
+ np.array(temp_times, dtype="datetime64[s]"),
219
+ np.array(temps),
220
+ self.data["time"],
221
+ )
222
+ if rh_times:
223
+ self.data["relative_humidity"] = _find_closest(
224
+ np.array(rh_times, dtype="datetime64[s]"),
225
+ np.array(rhs) / 100, # % => 1
226
+ self.data["time"],
227
+ )
228
+ if ah_times:
229
+ self.data["absolute_humidity"] = _find_closest(
230
+ np.array(ah_times, dtype="datetime64[s]"),
231
+ np.array(ahs) / 1000, # g m-3 => kg m-3
232
+ self.data["time"],
233
+ )
234
+
235
+
236
+ class RadiometricsWVR:
237
+ """Reader for *.los files from Radiometrics WVR-1100 microwave
238
+ radiometer.
239
+ """
240
+
241
+ def __init__(self, filename: Path):
242
+ self.filename = filename
243
+ self.raw_data: dict = {}
244
+ self.data: dict = {}
245
+ self.instrument = instruments.RADIOMETRICS
246
+ self.ranges: list[str] = []
247
+
248
+ def read_raw_data(self) -> None:
249
+ with open(self.filename, encoding="utf8") as file:
250
+ for line in file:
251
+ columns = line.split()
252
+ if columns[:2] == ["date", "time"]:
253
+ headers = columns
254
+ break
255
+ else:
256
+ msg = "No headers found"
257
+ raise RuntimeError(msg)
258
+ for header in headers:
259
+ self.raw_data[header] = []
260
+ for line in file:
261
+ columns = line.split()
262
+ if len(columns) != len(headers):
263
+ continue
264
+ for header, column in zip(headers, columns, strict=True):
265
+ value: datetime.date | datetime.time | float
266
+ if header == "date":
267
+ value = _parse_date(column)
268
+ elif header == "time":
269
+ value = _parse_time(column)
270
+ else:
271
+ value = _parse_value(column)
272
+ self.raw_data[header].append(value)
273
+
274
+ def read_data(self) -> None:
275
+ self.data["time"] = np.array(
276
+ [
277
+ datetime.datetime.combine(date, time)
278
+ for date, time in zip(
279
+ self.raw_data["date"], self.raw_data["time"], strict=True
280
+ )
281
+ ],
282
+ dtype="datetime64[s]",
224
283
  )
284
+ self.data["lwp"] = np.array(self.raw_data["LiqCM"]) * 10 # cm => kg m-2
285
+ self.data["iwv"] = np.array(self.raw_data["VapCM"]) * 10 # cm => kg m-2
286
+ is_zenith = np.abs(np.array(self.raw_data["ELact"]) - 90.0) < 1.0
287
+ tb23_valid = np.array(self.raw_data["TbSky23"]) > 0
288
+ tb31_valid = np.array(self.raw_data["TbSky31"]) > 0
289
+ is_valid = is_zenith & tb23_valid & tb31_valid
290
+ for key in self.data:
291
+ self.data[key] = self.data[key][is_valid]
225
292
 
226
293
 
227
294
  class RadiometricsCombined:
@@ -230,7 +297,7 @@ class RadiometricsCombined:
230
297
  date: datetime.date | None
231
298
  instrument: instruments.Instrument
232
299
 
233
- def __init__(self, objs: list[Radiometrics], site_meta: dict):
300
+ def __init__(self, objs: list[RadiometricsMP | RadiometricsWVR], site_meta: dict):
234
301
  self.site_meta = site_meta
235
302
  self.data = {}
236
303
  self.date = None
@@ -240,9 +307,10 @@ class RadiometricsCombined:
240
307
  raise InconsistentDataError(msg)
241
308
  for key in obj.data:
242
309
  self.data = utils.append_data(self.data, key, obj.data[key])
243
- ranges = [float(x) for x in objs[0].ranges]
244
- self.data["range"] = np.array(ranges) * 1000 # m => km
245
- self.data["height"] = self.data["range"] + self.site_meta["altitude"]
310
+ if objs[0].ranges:
311
+ ranges = [float(x) for x in objs[0].ranges]
312
+ self.data["range"] = np.array(ranges) * 1000 # m => km
313
+ self.data["height"] = self.data["range"] + self.site_meta["altitude"]
246
314
  self.instrument = instruments.RADIOMETRICS
247
315
 
248
316
  def screen_time(self, expected_date: datetime.date | None) -> None:
@@ -289,6 +357,35 @@ class RadiometricsCombined:
289
357
  self.data[name] = CloudnetArray(float(value), key)
290
358
 
291
359
 
360
+ def _read_file(filename: Path) -> RadiometricsMP | RadiometricsWVR:
361
+ with open(filename) as f:
362
+ first_line = f.readline()
363
+ obj = (
364
+ RadiometricsWVR(filename)
365
+ if "RETRIEVAL COEFFICIENTS" in first_line
366
+ else RadiometricsMP(filename)
367
+ )
368
+ obj.read_raw_data()
369
+ obj.read_data()
370
+ return obj
371
+
372
+
373
+ def _parse_value(text: str) -> float:
374
+ return math.nan if "*" in text else float(text)
375
+
376
+
377
+ def _parse_date(text: str) -> datetime.date:
378
+ month, day, year = map(int, text.split("/"))
379
+ if year < 100:
380
+ year += 2000
381
+ return datetime.date(year, month, day)
382
+
383
+
384
+ def _parse_time(text: str) -> datetime.time:
385
+ hour, minute, second = map(int, text.split(":"))
386
+ return datetime.time(hour, minute, second)
387
+
388
+
292
389
  def _parse_datetime(text: str) -> datetime.datetime:
293
390
  date, time = text.split()
294
391
  month, day, year = map(int, date.split("/"))
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 75
2
+ MINOR = 76
3
3
  PATCH = 1
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.75.1
3
+ Version: 1.76.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -9,7 +9,7 @@ cloudnetpy/metadata.py,sha256=lO7BCbVAzFoH3Nq-VuezYX0f7MnbG1Zp11g5GSiuQwM,6189
9
9
  cloudnetpy/output.py,sha256=l0LoOhcGCBrg2EJ4NT1xZ7-UKWdV7X7yQ0fJmhkwJVc,15829
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cloudnetpy/utils.py,sha256=SSZWk82c4nkAiTcLdOKGVvxt5ovETdAMn_TLxVeYpBY,33473
12
- cloudnetpy/version.py,sha256=tMFs6L1VKyLJzgBVt3-cNkUiP4Yz4Ld_Cu8UkEL_7U4,72
12
+ cloudnetpy/version.py,sha256=h_CrP8sn9JN6-YKTFNNaJcVYnr_Y-PGPAYa6HOR0wGo,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=s-SJaysvVpVVo5kidiruWQO6p3gv2TXwY1wEHYO5D6I,44
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=RcmbKxm2COkE7WEya0mK3yX5rzUbrewRVh3ekm01RtM,10598
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -49,7 +49,7 @@ cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo
49
49
  cloudnetpy/instruments/nc_lidar.py,sha256=5gQG9PApnNPrHmS9_zanl8HEYIQuGRpbnzC3wfTcOyQ,1705
50
50
  cloudnetpy/instruments/nc_radar.py,sha256=HlaZeH5939R86ukF8K-P4Kfzb5-CpLB15LU2u94C5eI,7330
51
51
  cloudnetpy/instruments/pollyxt.py,sha256=U3g-ttmcs02LuLwVOydP3GjeNcmDyoYQroB-leIGdHY,10060
52
- cloudnetpy/instruments/radiometrics.py,sha256=ySG4a042XkgjMTG8d20oAPNvFvw9bMwwiqS3zv-JF_w,11825
52
+ cloudnetpy/instruments/radiometrics.py,sha256=_ZBHYiw3u4m0UDPaYRHnx-ofq2FS59Vdv3-fLiOzm9I,15471
53
53
  cloudnetpy/instruments/rain_e_h3.py,sha256=9TdpP4UzMBNIt2iE2GL6K9dFldzTHPLOrU8Q3tcosCU,5317
54
54
  cloudnetpy/instruments/rpg.py,sha256=m3-xLJ-w2T7Ip7jBveWsGrts4tmNvdc-Lb4HebvHQjQ,17319
55
55
  cloudnetpy/instruments/rpg_reader.py,sha256=ThztFuVrWxhmWVAfZTfQDeUiKK1XMTbtv08IBe8GK98,11364
@@ -116,10 +116,10 @@ cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18
116
116
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
117
117
  cloudnetpy/products/mwr_tools.py,sha256=8HPZpQMTojKZP1JS1S83IE0sxmbDE9bxlaWoqmGnUZE,6199
118
118
  cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
119
- cloudnetpy-1.75.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
119
+ cloudnetpy-1.76.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
120
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
121
- cloudnetpy-1.75.1.dist-info/METADATA,sha256=E9p1xLzJFAIa_z2emwjuzlVEHuWB1YiYuY1z9pn1ZR4,5796
122
- cloudnetpy-1.75.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
123
- cloudnetpy-1.75.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
- cloudnetpy-1.75.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
- cloudnetpy-1.75.1.dist-info/RECORD,,
121
+ cloudnetpy-1.76.1.dist-info/METADATA,sha256=D879CAfuvaUbCF960Vvrb0Xiik6hijq1yiFREIFjZcE,5796
122
+ cloudnetpy-1.76.1.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
123
+ cloudnetpy-1.76.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
+ cloudnetpy-1.76.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
+ cloudnetpy-1.76.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5