cloudnetpy 1.80.2__py3-none-any.whl → 1.80.3__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.
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from typing import Any, NamedTuple
12
12
 
13
13
  import numpy as np
14
+ from numpy import ma
14
15
 
15
16
  from cloudnetpy import output, utils
16
17
  from cloudnetpy.cloudnetarray import CloudnetArray
@@ -151,6 +152,15 @@ class RadiometricsMP:
151
152
  ah_times = []
152
153
  block_titles = {}
153
154
  skip_procs = set()
155
+
156
+ def _parse_floats() -> list[float]:
157
+ return [
158
+ float(record.values[column])
159
+ if record.values[column].replace(".", "", 1).isdigit()
160
+ else math.nan
161
+ for column in self.ranges
162
+ ]
163
+
154
164
  for record in self.raw_data:
155
165
  if record.block_type == 100:
156
166
  block_type = int(record.values["Record Type"]) - 1
@@ -165,15 +175,13 @@ class RadiometricsMP:
165
175
  continue
166
176
  if title == "Temperature (K)":
167
177
  temp_times.append(record.timestamp)
168
- temps.append(
169
- [float(record.values[column]) for column in self.ranges]
170
- )
178
+ temps.append(_parse_floats())
171
179
  elif title == "Relative Humidity (%)":
172
180
  rh_times.append(record.timestamp)
173
- rhs.append([float(record.values[column]) for column in self.ranges])
181
+ rhs.append(_parse_floats())
174
182
  elif title == "Vapor Density (g/m^3)":
175
183
  ah_times.append(record.timestamp)
176
- ahs.append([float(record.values[column]) for column in self.ranges])
184
+ ahs.append(_parse_floats())
177
185
  elif record.block_type == 10:
178
186
  if record.block_index == 0:
179
187
  lwp = record.values["Lqint(mm)"]
@@ -185,15 +193,13 @@ class RadiometricsMP:
185
193
  irt_times.append(record.timestamp)
186
194
  irts.append([float(irt)])
187
195
  temp_times.append(record.timestamp)
188
- temps.append(
189
- [float(record.values[column]) for column in self.ranges]
190
- )
196
+ temps.append(_parse_floats())
191
197
  elif record.block_index == 1:
192
198
  ah_times.append(record.timestamp)
193
- ahs.append([float(record.values[column]) for column in self.ranges])
199
+ ahs.append(_parse_floats())
194
200
  elif record.block_index == 2:
195
201
  rh_times.append(record.timestamp)
196
- rhs.append([float(record.values[column]) for column in self.ranges])
202
+ rhs.append(_parse_floats())
197
203
  elif record.block_type == 200:
198
204
  irt_times.append(record.timestamp)
199
205
  irt = record.values["Tir(K)"]
@@ -350,7 +356,8 @@ class RadiometricsCombined:
350
356
  if key in ("temperature", "relative_humidity", "absolute_humidity")
351
357
  else None
352
358
  )
353
- self.data[key] = CloudnetArray(array, key, dimensions=dimensions)
359
+ array_masked = ma.masked_invalid(array) if np.isnan(array).any() else array
360
+ self.data[key] = CloudnetArray(array_masked, key, dimensions=dimensions)
354
361
 
355
362
  def add_meta(self) -> None:
356
363
  """Adds some metadata."""
@@ -3,7 +3,7 @@ import datetime
3
3
  import math
4
4
  import re
5
5
  from collections import defaultdict
6
- from collections.abc import Iterable
6
+ from collections.abc import Iterable, Sequence
7
7
  from os import PathLike
8
8
 
9
9
  import numpy as np
@@ -21,11 +21,11 @@ from cloudnetpy.utils import datetime2decimal_hours
21
21
 
22
22
 
23
23
  def ws2nc(
24
- weather_station_file: str | list[str],
24
+ weather_station_file: str | PathLike | Sequence[str | PathLike],
25
25
  output_file: str,
26
26
  site_meta: dict,
27
27
  uuid: str | None = None,
28
- date: str | None = None,
28
+ date: str | datetime.date | None = None,
29
29
  ) -> str:
30
30
  """Converts weather station data into Cloudnet Level 1b netCDF file.
31
31
 
@@ -43,8 +43,10 @@ def ws2nc(
43
43
  Raises:
44
44
  ValidTimeStampError: No valid timestamps found.
45
45
  """
46
- if not isinstance(weather_station_file, list):
46
+ if isinstance(weather_station_file, str | PathLike):
47
47
  weather_station_file = [weather_station_file]
48
+ if isinstance(date, str):
49
+ date = datetime.date.fromisoformat(date)
48
50
  ws: WS
49
51
  if site_meta["name"] == "Palaiseau":
50
52
  ws = PalaiseauWS(weather_station_file, site_meta)
@@ -64,6 +66,8 @@ def ws2nc(
64
66
  ws = LampedusaWS(weather_station_file, site_meta)
65
67
  elif site_meta["name"] == "Limassol":
66
68
  ws = LimassolWS(weather_station_file, site_meta)
69
+ elif site_meta["name"] == "L'Aquila":
70
+ ws = LAquilaWS(weather_station_file, site_meta)
67
71
  else:
68
72
  msg = "Unsupported site"
69
73
  raise ValueError(msg)
@@ -98,8 +102,8 @@ class WS(CSVFile):
98
102
  rainfall_amount = ma.cumsum(self.data["rainfall_rate"].data * resolution)
99
103
  self.data["rainfall_amount"] = CloudnetArray(rainfall_amount, "rainfall_amount")
100
104
 
101
- def screen_timestamps(self, date: str) -> None:
102
- dates = np.array([str(d.date()) for d in self._data["time"]])
105
+ def screen_timestamps(self, date: datetime.date) -> None:
106
+ dates = np.array([d.date() for d in self._data["time"]])
103
107
  valid_mask = dates == date
104
108
  if not valid_mask.any():
105
109
  raise ValidTimeStampError
@@ -139,7 +143,7 @@ class WS(CSVFile):
139
143
 
140
144
 
141
145
  class PalaiseauWS(WS):
142
- def __init__(self, filenames: list[str], site_meta: dict):
146
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
143
147
  super().__init__(site_meta)
144
148
  self.filenames = filenames
145
149
  self._data = self._read_data()
@@ -170,8 +174,8 @@ class PalaiseauWS(WS):
170
174
  decimal_hours = datetime2decimal_hours(self._data["time"])
171
175
  self.data["time"] = CloudnetArray(decimal_hours, "time")
172
176
 
173
- def screen_timestamps(self, date: str) -> None:
174
- dates = [str(d.date()) for d in self._data["time"]]
177
+ def screen_timestamps(self, date: datetime.date) -> None:
178
+ dates = [d.date() for d in self._data["time"]]
175
179
  valid_ind = [ind for ind, d in enumerate(dates) if d == date]
176
180
  if not valid_ind:
177
181
  raise ValidTimeStampError
@@ -228,7 +232,7 @@ class BucharestWS(PalaiseauWS):
228
232
 
229
233
 
230
234
  class GranadaWS(WS):
231
- def __init__(self, filenames: list[str], site_meta: dict):
235
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
232
236
  if len(filenames) != 1:
233
237
  raise ValueError
234
238
  super().__init__(site_meta)
@@ -278,7 +282,7 @@ class GranadaWS(WS):
278
282
 
279
283
 
280
284
  class KenttarovaWS(WS):
281
- def __init__(self, filenames: list[str], site_meta: dict):
285
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
282
286
  super().__init__(site_meta)
283
287
  self.filenames = filenames
284
288
  self._data = self._read_data()
@@ -334,7 +338,7 @@ class HyytialaWS(WS):
334
338
  - BbRT/mm = Bucket content in real-time (Pluvio200) [mm].
335
339
  """
336
340
 
337
- def __init__(self, filenames: list[str], site_meta: dict):
341
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
338
342
  super().__init__(site_meta)
339
343
  self.filename = filenames[0]
340
344
  self._data = self._read_data()
@@ -395,7 +399,7 @@ class HyytialaWS(WS):
395
399
 
396
400
 
397
401
  class GalatiWS(WS):
398
- def __init__(self, filenames: list[str], site_meta: dict):
402
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
399
403
  super().__init__(site_meta)
400
404
  self.filename = filenames[0]
401
405
  self._data = self._read_data()
@@ -450,7 +454,7 @@ class GalatiWS(WS):
450
454
 
451
455
 
452
456
  class JuelichWS(WS):
453
- def __init__(self, filenames: list[str], site_meta: dict):
457
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
454
458
  super().__init__(site_meta)
455
459
  self.filename = filenames[0]
456
460
  self._data = self._read_data()
@@ -496,7 +500,7 @@ class JuelichWS(WS):
496
500
  class LampedusaWS(WS):
497
501
  """Read Lampedusa weather station data in ICOS format."""
498
502
 
499
- def __init__(self, filenames: list[str], site_meta: dict):
503
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
500
504
  super().__init__(site_meta)
501
505
  self.filename = filenames[0]
502
506
  self._data = self._read_data()
@@ -549,7 +553,7 @@ class LampedusaWS(WS):
549
553
 
550
554
 
551
555
  class LimassolWS(WS):
552
- def __init__(self, filenames: list[str], site_meta: dict):
556
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
553
557
  super().__init__(site_meta)
554
558
  self.filenames = filenames
555
559
  self._data = defaultdict(list)
@@ -562,8 +566,8 @@ class LimassolWS(WS):
562
566
  decimal_hours = datetime2decimal_hours(self._data["time"])
563
567
  self.data["time"] = CloudnetArray(decimal_hours, "time")
564
568
 
565
- def screen_timestamps(self, date: str) -> None:
566
- dates = [str(d.date()) for d in self._data["time"]]
569
+ def screen_timestamps(self, date: datetime.date) -> None:
570
+ dates = [d.date() for d in self._data["time"]]
567
571
  valid_ind = [ind for ind, d in enumerate(dates) if d == date]
568
572
  if not valid_ind:
569
573
  raise ValidTimeStampError
@@ -646,3 +650,51 @@ def _parse_sirta(filename: str | PathLike):
646
650
  parsed = float(value)
647
651
  output[column].append(parsed)
648
652
  return output
653
+
654
+
655
+ class LAquilaWS(WS):
656
+ def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
657
+ super().__init__(site_meta)
658
+ self.filenames = filenames
659
+ self._data = self._read_data()
660
+
661
+ def _read_data(self) -> dict:
662
+ data: dict[str, list] = {
663
+ key: []
664
+ for key in [
665
+ "time",
666
+ "air_temperature",
667
+ "air_pressure",
668
+ "relative_humidity",
669
+ "rainfall_rate",
670
+ "wind_speed",
671
+ "wind_direction",
672
+ ]
673
+ }
674
+ for filename in self.filenames:
675
+ with open(filename) as f:
676
+ for row in f:
677
+ if row.startswith("#"):
678
+ continue
679
+ columns = row.split(",")
680
+ if len(columns) != 7:
681
+ continue
682
+ timestamp = datetime.datetime.strptime(
683
+ columns[0], "%Y-%m-%dT%H:%M:%SZ"
684
+ ).replace(tzinfo=datetime.timezone.utc)
685
+ data["time"].append(timestamp)
686
+ data["air_temperature"].append(self._parse_value(columns[1]))
687
+ data["air_pressure"].append(self._parse_value(columns[2]))
688
+ data["relative_humidity"].append(self._parse_value(columns[3]))
689
+ data["rainfall_rate"].append(self._parse_value(columns[4]))
690
+ data["wind_speed"].append(self._parse_value(columns[5]))
691
+ data["wind_direction"].append(self._parse_value(columns[6]))
692
+ output = self.format_data(data)
693
+ _, time_ind = np.unique(output["time"], return_index=True)
694
+ for key in output:
695
+ output[key] = output[key][time_ind]
696
+ return output
697
+
698
+ def _parse_value(self, value: str) -> float:
699
+ value = value.strip()
700
+ return float(value) if value else math.nan
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 80
3
- PATCH = 2
3
+ PATCH = 3
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.80.2
3
+ Version: 1.80.3
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=gupxt4f_-eUrFsWMto8tnknoV-p9QauC9L6CJAqBILU,15988
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cloudnetpy/utils.py,sha256=WczDeGN408XSgGeaRLXFmlLjgAS67lK1osV0YEuKmwo,32027
12
- cloudnetpy/version.py,sha256=7zXWRL5tx8CR6R5neJyk3PXzFjoph6dombYnU9CqKUI,72
12
+ cloudnetpy/version.py,sha256=BW7NnO7naEy7O4jbwWQGYbmOwUJPOQWGf7cSaGqVr7g,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
@@ -50,13 +50,13 @@ cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo
50
50
  cloudnetpy/instruments/nc_lidar.py,sha256=5gQG9PApnNPrHmS9_zanl8HEYIQuGRpbnzC3wfTcOyQ,1705
51
51
  cloudnetpy/instruments/nc_radar.py,sha256=HlaZeH5939R86ukF8K-P4Kfzb5-CpLB15LU2u94C5eI,7330
52
52
  cloudnetpy/instruments/pollyxt.py,sha256=U3g-ttmcs02LuLwVOydP3GjeNcmDyoYQroB-leIGdHY,10060
53
- cloudnetpy/instruments/radiometrics.py,sha256=H-tAyCCT53GjODQuDxIx8NFuZazm8BNPt5gHEuxjRxg,15570
53
+ cloudnetpy/instruments/radiometrics.py,sha256=qCiBTlp1H30DZTxKDN5Xn_4d-JkTNavpVIiUvpJkiMM,15617
54
54
  cloudnetpy/instruments/rain_e_h3.py,sha256=JEg4Ko7ZdfjAUJwJ1BWdTkm4K7r3s8WKrPb-HidTqpg,5336
55
55
  cloudnetpy/instruments/rpg.py,sha256=m3-xLJ-w2T7Ip7jBveWsGrts4tmNvdc-Lb4HebvHQjQ,17319
56
56
  cloudnetpy/instruments/rpg_reader.py,sha256=ThztFuVrWxhmWVAfZTfQDeUiKK1XMTbtv08IBe8GK98,11364
57
57
  cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
58
58
  cloudnetpy/instruments/vaisala.py,sha256=W_yu_f92cOq8RiiqDLj7bswxu9UMS3TITPWzP5xPdvA,4615
59
- cloudnetpy/instruments/weather_station.py,sha256=pZK7I5bk1USDRoTeIhZoWzbka9ciea5ypA3oIzZX-7g,24549
59
+ cloudnetpy/instruments/weather_station.py,sha256=C41Fv4kU1ihm3EGWhry5ESe4shnKims8O6wCzz2L78U,26844
60
60
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
61
61
  cloudnetpy/instruments/disdrometer/common.py,sha256=g52iK2aNp3Z88kovUmGVpC54NZomPa9D871gzO0AmQ4,9267
62
62
  cloudnetpy/instruments/disdrometer/parsivel.py,sha256=HJZrEysQkx9MiIVPDV25CYHpXi_SjgZlgO-otoaKK34,25640
@@ -117,10 +117,10 @@ cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18
117
117
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
118
118
  cloudnetpy/products/mwr_tools.py,sha256=8HPZpQMTojKZP1JS1S83IE0sxmbDE9bxlaWoqmGnUZE,6199
119
119
  cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
120
- cloudnetpy-1.80.2.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
+ cloudnetpy-1.80.3.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
121
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
122
- cloudnetpy-1.80.2.dist-info/METADATA,sha256=coxvAU-k7KsWoNiet_mhP3Y5NJ8JcqoWBvoDLXQkwhs,5803
123
- cloudnetpy-1.80.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
- cloudnetpy-1.80.2.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
- cloudnetpy-1.80.2.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
- cloudnetpy-1.80.2.dist-info/RECORD,,
122
+ cloudnetpy-1.80.3.dist-info/METADATA,sha256=N9oYLtyR_xAnhl0Q0pp1d91XHsfb6MPULyqg2_NtBlQ,5803
123
+ cloudnetpy-1.80.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
+ cloudnetpy-1.80.3.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
+ cloudnetpy-1.80.3.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
+ cloudnetpy-1.80.3.dist-info/RECORD,,