cloudnetpy 1.75.0__py3-none-any.whl → 1.76.0__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.
@@ -247,15 +247,15 @@ def _get_keymap(filetype: str) -> dict:
247
247
  keymaps = {
248
248
  "znc": OrderedDict(
249
249
  [
250
- ("Zg", "Zh"),
250
+ ("Zg", "Zh"), # fallback
251
251
  ("Zh2l", "Zh"),
252
- ("VELg", "v"),
252
+ ("VELg", "v"), # fallback
253
253
  ("VELh2l", "v"),
254
- ("RMSg", "width"),
254
+ ("RMSg", "width"), # fallback
255
255
  ("RMSh2l", "width"),
256
- ("LDRg", "ldr"),
256
+ ("LDRg", "ldr"), # fallback
257
257
  ("LDRh2l", "ldr"),
258
- ("SNRg", "SNR"),
258
+ ("SNRg", "SNR"), # fallback
259
259
  ("SNRh2l", "SNR"),
260
260
  ("elv", "elevation"),
261
261
  ("azi", "azimuth_angle"),
@@ -265,20 +265,23 @@ def _get_keymap(filetype: str) -> dict:
265
265
  ("rg0", "rg0"),
266
266
  ],
267
267
  ),
268
- "mmclx": {
269
- "Zg": "Zh",
270
- "VELg": "v",
271
- "RMSg": "width",
272
- "LDRg": "ldr",
273
- "SNRg": "SNR",
274
- "elv": "elevation",
275
- "azi": "azimuth_angle",
276
- "nfft": "nfft",
277
- "nave": "nave",
278
- "prf": "prf",
279
- "rg0": "rg0",
280
- "NyquistVelocity": "NyquistVelocity", # variable in some mmclx files
281
- },
268
+ "mmclx": OrderedDict(
269
+ [
270
+ ("Ze", "Zh"), # fallback for old mmclx files
271
+ ("Zg", "Zh"),
272
+ ("VELg", "v"),
273
+ ("RMSg", "width"),
274
+ ("LDRg", "ldr"),
275
+ ("SNRg", "SNR"),
276
+ ("elv", "elevation"),
277
+ ("azi", "azimuth_angle"),
278
+ ("nfft", "nfft"),
279
+ ("nave", "nave"),
280
+ ("prf", "prf"),
281
+ ("rg0", "rg0"),
282
+ ("NyquistVelocity", "NyquistVelocity"), # variable in some mmclx files
283
+ ]
284
+ ),
282
285
  }
283
286
 
284
287
  return keymaps.get(filetype.lower(), keymaps["mmclx"])
@@ -6,6 +6,7 @@ import logging
6
6
  import os
7
7
  import re
8
8
  from operator import attrgetter
9
+ from pathlib import Path
9
10
  from typing import Any, NamedTuple
10
11
 
11
12
  import numpy as np
@@ -18,8 +19,8 @@ from cloudnetpy.metadata import MetaData
18
19
 
19
20
 
20
21
  def radiometrics2nc(
21
- full_path: str,
22
- output_file: str,
22
+ full_path: str | os.PathLike,
23
+ output_file: str | os.PathLike,
23
24
  site_meta: dict,
24
25
  uuid: str | None = None,
25
26
  date: str | datetime.date | None = None,
@@ -46,19 +47,11 @@ def radiometrics2nc(
46
47
  """
47
48
  if isinstance(date, str):
48
49
  date = datetime.date.fromisoformat(date)
49
-
50
50
  if os.path.isdir(full_path):
51
- valid_filenames = utils.get_sorted_filenames(full_path, ".csv")
51
+ valid_filenames = list(Path(full_path).iterdir())
52
52
  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
-
53
+ valid_filenames = [Path(full_path)]
54
+ objs = [_read_file(filename) for filename in valid_filenames]
62
55
  radiometrics = RadiometricsCombined(objs, site_meta)
63
56
  radiometrics.screen_time(date)
64
57
  radiometrics.sort_timestamps()
@@ -81,15 +74,16 @@ class Record(NamedTuple):
81
74
  values: dict[str, Any]
82
75
 
83
76
 
84
- class Radiometrics:
85
- """Reader for level 2 files of Radiometrics microwave radiometers.
77
+ class RadiometricsMP:
78
+ """Reader for level 2 files (*.csv) from Radiometrics MP-3000A and similar
79
+ microwave radiometers.
86
80
 
87
81
  References:
88
82
  Radiometrics (2008). Profiler Operator's Manual: MP-3000A, MP-2500A,
89
83
  MP-1500A, MP-183A.
90
84
  """
91
85
 
92
- def __init__(self, filename: str):
86
+ def __init__(self, filename: Path):
93
87
  self.filename = filename
94
88
  self.raw_data: list[Record] = []
95
89
  self.data: dict = {}
@@ -152,12 +146,19 @@ class Radiometrics:
152
146
  ahs = []
153
147
  ah_times = []
154
148
  block_titles = {}
149
+ skip_procs = set()
155
150
  for record in self.raw_data:
156
151
  if record.block_type == 100:
157
152
  block_type = int(record.values["Record Type"]) - 1
158
153
  title = record.values["Title"]
159
154
  block_titles[block_type] = title
160
155
  if title := block_titles.get(record.block_type + record.block_index):
156
+ # "LV2 Processor" values "Zenith" and "0.00:90.00" should be OK
157
+ # but "Angle20(N)" and similar should be skipped.
158
+ proc = record.values["LV2 Processor"]
159
+ if proc.startswith("Angle"):
160
+ skip_procs.add(proc)
161
+ continue
161
162
  if title == "Temperature (K)":
162
163
  temp_times.append(record.timestamp)
163
164
  temps.append(
@@ -194,6 +195,9 @@ class Radiometrics:
194
195
  irt = record.values["Tir(K)"]
195
196
  irts.append([float(irt)])
196
197
  elif record.block_type == 300:
198
+ if skip_procs:
199
+ skip_procs.pop()
200
+ continue
197
201
  lwp = record.values["Int. Liquid(mm)"]
198
202
  iwv = record.values["Int. Vapor(cm)"]
199
203
  times.append(record.timestamp)
@@ -224,13 +228,69 @@ class Radiometrics:
224
228
  )
225
229
 
226
230
 
231
+ class RadiometricsWVR:
232
+ """Reader for *.los files from Radiometrics WVR-1100 microwave
233
+ radiometer.
234
+ """
235
+
236
+ def __init__(self, filename: Path):
237
+ self.filename = filename
238
+ self.raw_data: dict = {}
239
+ self.data: dict = {}
240
+ self.instrument = instruments.RADIOMETRICS
241
+ self.ranges: list[str] = []
242
+
243
+ def read_raw_data(self) -> None:
244
+ with open(self.filename, encoding="utf8") as infile:
245
+ for line in infile:
246
+ columns = line.split()
247
+ if columns[:2] == ["date", "time"]:
248
+ headers = columns
249
+ break
250
+ else:
251
+ msg = "No headers found"
252
+ raise RuntimeError(msg)
253
+ for key in headers:
254
+ self.raw_data[key] = []
255
+ for line in infile:
256
+ for key, value in zip(headers, line.split(), strict=False):
257
+ parsed_value: Any
258
+ if key == "date":
259
+ month, day, year = map(int, value.split("/"))
260
+ if year < 100:
261
+ year += 2000
262
+ parsed_value = datetime.date(year, month, day)
263
+ elif key == "time":
264
+ hour, minute, second = map(int, value.split(":"))
265
+ parsed_value = datetime.time(hour, minute, second)
266
+ else:
267
+ parsed_value = float(value)
268
+ self.raw_data[key].append(parsed_value)
269
+
270
+ def read_data(self) -> None:
271
+ self.data["time"] = np.array(
272
+ [
273
+ datetime.datetime.combine(date, time)
274
+ for date, time in zip(
275
+ self.raw_data["date"], self.raw_data["time"], strict=False
276
+ )
277
+ ],
278
+ dtype="datetime64[s]",
279
+ )
280
+ self.data["lwp"] = np.array(self.raw_data["LiqCM"]) * 10 # cm => kg m-2
281
+ self.data["iwv"] = np.array(self.raw_data["VapCM"]) * 10 # cm => kg m-2
282
+ is_zenith = np.abs(np.array(self.raw_data["ELact"]) - 90.0) < 1.0
283
+ for key in self.data:
284
+ self.data[key] = self.data[key][is_zenith]
285
+
286
+
227
287
  class RadiometricsCombined:
228
288
  site_meta: dict
229
289
  data: dict
230
290
  date: datetime.date | None
231
291
  instrument: instruments.Instrument
232
292
 
233
- def __init__(self, objs: list[Radiometrics], site_meta: dict):
293
+ def __init__(self, objs: list[RadiometricsMP | RadiometricsWVR], site_meta: dict):
234
294
  self.site_meta = site_meta
235
295
  self.data = {}
236
296
  self.date = None
@@ -240,9 +300,10 @@ class RadiometricsCombined:
240
300
  raise InconsistentDataError(msg)
241
301
  for key in obj.data:
242
302
  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"]
303
+ if objs[0].ranges:
304
+ ranges = [float(x) for x in objs[0].ranges]
305
+ self.data["range"] = np.array(ranges) * 1000 # m => km
306
+ self.data["height"] = self.data["range"] + self.site_meta["altitude"]
246
307
  self.instrument = instruments.RADIOMETRICS
247
308
 
248
309
  def screen_time(self, expected_date: datetime.date | None) -> None:
@@ -289,6 +350,19 @@ class RadiometricsCombined:
289
350
  self.data[name] = CloudnetArray(float(value), key)
290
351
 
291
352
 
353
+ def _read_file(filename: Path) -> RadiometricsMP | RadiometricsWVR:
354
+ with open(filename) as f:
355
+ first_line = f.readline()
356
+ obj = (
357
+ RadiometricsWVR(filename)
358
+ if "RETRIEVAL COEFFICIENTS" in first_line
359
+ else RadiometricsMP(filename)
360
+ )
361
+ obj.read_raw_data()
362
+ obj.read_data()
363
+ return obj
364
+
365
+
292
366
  def _parse_datetime(text: str) -> datetime.datetime:
293
367
  date, time = text.split()
294
368
  month, day, year = map(int, date.split("/"))
cloudnetpy/utils.py CHANGED
@@ -965,7 +965,8 @@ def get_files_with_common_range(filenames: list) -> list:
965
965
  with netCDF4.Dataset(file) as nc:
966
966
  n_range.append(len(nc.variables["range"]))
967
967
  most_common = np.bincount(n_range).argmax()
968
- if n_removed := len(filenames) - n_range.count(int(most_common)) > 0:
968
+ n_removed = len(filenames) - n_range.count(int(most_common))
969
+ if n_removed > 0:
969
970
  logging.warning(
970
971
  "Removing %s files due to inconsistent height vector", n_removed
971
972
  )
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 75
2
+ MINOR = 76
3
3
  PATCH = 0
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.0
3
+ Version: 1.76.0
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -8,8 +8,8 @@ cloudnetpy/exceptions.py,sha256=hYbUtBwjCIfxnPe_5mELDEw87AWITBrwuo7WYIEKmJ8,1579
8
8
  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
- cloudnetpy/utils.py,sha256=mzibw-RiC4e6rJGeJUf3RR_j-LmmcwqejzbacmXCmZA,33460
12
- cloudnetpy/version.py,sha256=2mtP3uOUUyCLGEPBZOTqdIlYgEriB62_Dv5irb5ckbI,72
11
+ cloudnetpy/utils.py,sha256=SSZWk82c4nkAiTcLdOKGVvxt5ovETdAMn_TLxVeYpBY,33473
12
+ cloudnetpy/version.py,sha256=vHHlyjedUCAOK4akFXnc6Xk6GWb-5gg4KD85wtoljYA,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
@@ -44,12 +44,12 @@ cloudnetpy/instruments/galileo.py,sha256=BjWE15_S3tTCOmAM5k--oicI3wghKaO0hv9EUBx
44
44
  cloudnetpy/instruments/hatpro.py,sha256=G1fHsY9LTos4vHP5kFubjE5Wg2uTVFZpYDSD8VAo-zw,9590
45
45
  cloudnetpy/instruments/instruments.py,sha256=hdELBl4zw9gZghXLjmUBmC_XnyGvNuOyaYZzSl9M17k,4704
46
46
  cloudnetpy/instruments/lufft.py,sha256=nIoEKuuFGKq2dLqkX7zW-HpAifefG472tZhKfXE1yoA,4212
47
- cloudnetpy/instruments/mira.py,sha256=Wofp8HbiAwJce_IbOLjpEFV07H_Kh4170C9Wygiz-ew,11401
47
+ cloudnetpy/instruments/mira.py,sha256=IH88dnV5fdAQ-A04S23ROgNmT4GBAtzXQxCr_9fWj-Q,11634
48
48
  cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo,5711
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=__H7KFBcXT0FP8lis3P25MuMa5-S8GrTzKtazMKO678,14822
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.0.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
119
+ cloudnetpy-1.76.0.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
120
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
121
- cloudnetpy-1.75.0.dist-info/METADATA,sha256=DTNgu10C6RJgm06RyBCawO2SxeBlB_dTB_beAzd1NNM,5796
122
- cloudnetpy-1.75.0.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
123
- cloudnetpy-1.75.0.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
- cloudnetpy-1.75.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
- cloudnetpy-1.75.0.dist-info/RECORD,,
121
+ cloudnetpy-1.76.0.dist-info/METADATA,sha256=sefr7DyZ7S4JNZcT_TvGj_b9hxEZOXJp887yXKW0tkg,5796
122
+ cloudnetpy-1.76.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
123
+ cloudnetpy-1.76.0.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
+ cloudnetpy-1.76.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
+ cloudnetpy-1.76.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5