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.
- cloudnetpy/instruments/radiometrics.py +136 -39
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/METADATA +1 -1
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/RECORD +8 -8
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/WHEEL +1 -1
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.75.1.dist-info → cloudnetpy-1.76.1.dist-info}/top_level.txt +0 -0
@@ -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 =
|
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
|
85
|
-
"""Reader for level 2 files
|
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:
|
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
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
self.data["
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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[
|
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
|
-
|
244
|
-
|
245
|
-
|
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
@@ -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=
|
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=
|
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.
|
119
|
+
cloudnetpy-1.76.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
120
120
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
121
|
-
cloudnetpy-1.
|
122
|
-
cloudnetpy-1.
|
123
|
-
cloudnetpy-1.
|
124
|
-
cloudnetpy-1.
|
125
|
-
cloudnetpy-1.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|