cloudnetpy 1.61.4__py3-none-any.whl → 1.61.6__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,4 +1,6 @@
1
1
  """Module that generates Cloudnet categorize file."""
2
+ import logging
3
+
2
4
  from cloudnetpy import output, utils
3
5
  from cloudnetpy.categorize import atmos, classify
4
6
  from cloudnetpy.categorize.disdrometer import Disdrometer
@@ -7,7 +9,7 @@ from cloudnetpy.categorize.model import Model
7
9
  from cloudnetpy.categorize.mwr import Mwr
8
10
  from cloudnetpy.categorize.radar import Radar
9
11
  from cloudnetpy.datasource import DataSource
10
- from cloudnetpy.exceptions import ValidTimeStampError
12
+ from cloudnetpy.exceptions import DisdrometerDataError, ValidTimeStampError
11
13
  from cloudnetpy.metadata import MetaData
12
14
 
13
15
 
@@ -61,7 +63,7 @@ def generate_categorize(
61
63
  def _interpolate_to_cloudnet_grid() -> list:
62
64
  wl_band = utils.get_wl_band(data["radar"].radar_frequency)
63
65
  data["mwr"].rebin_to_grid(time)
64
- if is_disdrometer:
66
+ if data["disdrometer"] is not None:
65
67
  data["disdrometer"].interpolate_to_grid(time)
66
68
  data["model"].interpolate_to_common_height(wl_band)
67
69
  model_gap_ind = data["model"].interpolate_to_grid(time, height)
@@ -94,7 +96,7 @@ def generate_categorize(
94
96
  def _prepare_output() -> dict:
95
97
  data["radar"].add_meta()
96
98
  data["model"].screen_sparse_fields()
97
- if is_disdrometer:
99
+ if data["disdrometer"] is not None:
98
100
  data["radar"].data.pop("rainfall_rate", None)
99
101
  data["disdrometer"].data.pop("n_particles", None)
100
102
  for key in ("category_bits", "insect_prob"):
@@ -111,7 +113,7 @@ def generate_categorize(
111
113
  **data["model"].data,
112
114
  **data["model"].data_sparse,
113
115
  **data["mwr"].data,
114
- **(data["disdrometer"].data if is_disdrometer else {}),
116
+ **(data["disdrometer"].data if data["disdrometer"] is not None else {}),
115
117
  }
116
118
 
117
119
  def _define_dense_grid() -> tuple:
@@ -123,17 +125,18 @@ def generate_categorize(
123
125
  obj.close()
124
126
 
125
127
  try:
126
- is_disdrometer = "disdrometer" in input_files
127
-
128
128
  data: dict = {
129
129
  "radar": Radar(input_files["radar"]),
130
130
  "lidar": Lidar(input_files["lidar"]),
131
131
  "mwr": Mwr(input_files["mwr"]),
132
132
  "lv0_files": input_files.get("lv0_files"),
133
- "disdrometer": Disdrometer(input_files["disdrometer"])
134
- if is_disdrometer
135
- else None,
133
+ "disdrometer": None,
136
134
  }
135
+ if "disdrometer" in input_files:
136
+ try:
137
+ data["disdrometer"] = Disdrometer(input_files["disdrometer"])
138
+ except DisdrometerDataError as err:
139
+ logging.warning("Unable to use disdrometer: %s", err)
137
140
  data["model"] = Model(input_files["model"], data["radar"].altitude)
138
141
  time, height = _define_dense_grid()
139
142
  valid_ind = _interpolate_to_cloudnet_grid()
@@ -7,6 +7,7 @@ from scipy.interpolate import interp1d
7
7
 
8
8
  from cloudnetpy.categorize.lidar import get_gap_ind
9
9
  from cloudnetpy.datasource import DataSource
10
+ from cloudnetpy.exceptions import DisdrometerDataError
10
11
 
11
12
 
12
13
  class Disdrometer(DataSource):
@@ -29,6 +30,9 @@ class Disdrometer(DataSource):
29
30
  def _init_rainfall_rate(self) -> None:
30
31
  keys = ("rainfall_rate", "n_particles")
31
32
  for key in keys:
33
+ if key not in self.dataset.variables:
34
+ msg = f"variable {key} is missing"
35
+ raise DisdrometerDataError(msg)
32
36
  self.append_data(self.dataset.variables[key][:], key)
33
37
 
34
38
  def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray:
@@ -151,7 +151,8 @@ class Thies(Disdrometer):
151
151
  with open(filename) as file:
152
152
  first_line = file.readline()
153
153
  if "TOA5" in first_line:
154
- for row in read_toa5(filename):
154
+ units, process, rows = read_toa5(filename)
155
+ for row in rows:
155
156
  self._read_line(row["RawString"], row["TIMESTAMP"])
156
157
  else:
157
158
  with open(filename) as file:
@@ -4,7 +4,9 @@ from os import PathLike
4
4
  from typing import Any
5
5
 
6
6
 
7
- def read_toa5(filename: str | PathLike) -> list[dict[str, Any]]:
7
+ def read_toa5(
8
+ filename: str | PathLike,
9
+ ) -> tuple[dict[str, str], dict[str, str], list[dict[str, Any]]]:
8
10
  """Read ASCII data from Campbell Scientific datalogger such as CR1000.
9
11
 
10
12
  References
@@ -18,9 +20,11 @@ def read_toa5(filename: str | PathLike) -> list[dict[str, Any]]:
18
20
  msg = "Invalid TOA5 file"
19
21
  raise ValueError(msg)
20
22
  header_line = next(reader)
21
- _units_line = next(reader)
22
- _process_line = next(reader)
23
+ units_line = next(reader)
24
+ process_line = next(reader)
23
25
  output = []
26
+ units = dict(zip(header_line, units_line, strict=False))
27
+ process = dict(zip(header_line, process_line, strict=False))
24
28
 
25
29
  row_template: dict[str, Any] = {}
26
30
  for header in header_line:
@@ -42,4 +46,4 @@ def read_toa5(filename: str | PathLike) -> list[dict[str, Any]]:
42
46
  else:
43
47
  row[key] = parsed_value
44
48
  output.append(row)
45
- return output
49
+ return units, process, output
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
 
3
+ import numpy as np
3
4
  from numpy import ma
4
5
 
5
6
  from cloudnetpy import output
@@ -8,6 +9,7 @@ from cloudnetpy.cloudnetarray import CloudnetArray
8
9
  from cloudnetpy.exceptions import ValidTimeStampError, WeatherStationDataError
9
10
  from cloudnetpy.instruments import instruments
10
11
  from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
12
+ from cloudnetpy.instruments.toa5 import read_toa5
11
13
  from cloudnetpy.metadata import MetaData
12
14
  from cloudnetpy.utils import datetime2decimal_hours
13
15
 
@@ -19,7 +21,7 @@ def ws2nc(
19
21
  uuid: str | None = None,
20
22
  date: str | None = None,
21
23
  ) -> str:
22
- """Converts weather-station data into Cloudnet Level 1b netCDF file.
24
+ """Converts weather station data into Cloudnet Level 1b netCDF file.
23
25
 
24
26
  Args:
25
27
  weather_station_file: Filename of weather-station ASCII file.
@@ -37,7 +39,14 @@ def ws2nc(
37
39
  ValidTimeStampError: No valid timestamps found.
38
40
  """
39
41
  try:
40
- ws = WS(weather_station_file, site_meta)
42
+ ws: WS
43
+ if site_meta["name"] == "Palaiseau":
44
+ ws = PalaiseauWS(weather_station_file, site_meta)
45
+ elif site_meta["name"] == "Granada":
46
+ ws = GranadaWS(weather_station_file, site_meta)
47
+ else:
48
+ msg = "Unsupported site"
49
+ raise ValueError(msg) # noqa: TRY301
41
50
  if date is not None:
42
51
  ws.screen_timestamps(date)
43
52
  ws.convert_time()
@@ -53,11 +62,29 @@ def ws2nc(
53
62
 
54
63
 
55
64
  class WS(CloudnetInstrument):
65
+ date: list[str]
66
+
67
+ def convert_time(self) -> None:
68
+ pass
69
+
70
+ def screen_timestamps(self, date: str) -> None:
71
+ pass
72
+
73
+ def add_date(self) -> None:
74
+ pass
75
+
76
+ def add_data(self) -> None:
77
+ pass
78
+
79
+ def convert_units(self) -> None:
80
+ pass
81
+
82
+
83
+ class PalaiseauWS(WS):
56
84
  def __init__(self, filename: str, site_meta: dict):
57
85
  super().__init__()
58
86
  self.filename = filename
59
87
  self.site_meta = site_meta
60
- self.date: list[str] = []
61
88
  self.instrument = instruments.GENERIC_WEATHER_STATION
62
89
  self._data = self._read_data()
63
90
 
@@ -145,7 +172,94 @@ class WS(CloudnetInstrument):
145
172
  self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
146
173
  self.data["rainfall_amount"].data = (
147
174
  self.data["rainfall_amount"][:] / 1000
148
- ) # m -> mm
175
+ ) # mm -> m
176
+
177
+
178
+ class GranadaWS(WS):
179
+ def __init__(self, filename: str, site_meta: dict):
180
+ super().__init__()
181
+ self.filename = filename
182
+ self.site_meta = site_meta
183
+ self.instrument = instruments.GENERIC_WEATHER_STATION
184
+ self._data = self._read_data()
185
+
186
+ def _read_data(self) -> dict:
187
+ keymap = {
188
+ "TIMESTAMP": "time",
189
+ "air_t_Avg": "air_temperature",
190
+ "rh_Avg": "relative_humidity",
191
+ "pressure_Avg": "air_pressure",
192
+ "wind_speed_avg": "wind_speed",
193
+ "wind_dir_avg": "wind_direction",
194
+ "rain_Tot": "rainfall_rate",
195
+ }
196
+ expected_units = {
197
+ "air_t_Avg": "degC",
198
+ "rh_Avg": "%",
199
+ "pressure_Avg": "hPa",
200
+ "wind_speed_avg": "m/s",
201
+ "wind_dir_avg": "Deg",
202
+ "rain_Tot": "mm",
203
+ }
204
+ units, process, rows = read_toa5(self.filename)
205
+ for key in units:
206
+ if key in expected_units and expected_units[key] != units[key]:
207
+ msg = (
208
+ f"Expected {key} to have units {expected_units[key]},"
209
+ f" got {units[key]} instead"
210
+ )
211
+ raise ValueError(msg)
212
+
213
+ data: dict[str, list] = {keymap[key]: [] for key in units if key in keymap}
214
+ for row in rows:
215
+ for key, value in row.items():
216
+ if key not in keymap:
217
+ continue
218
+ parsed = value
219
+ if keymap[key] != "time":
220
+ parsed = float(value)
221
+ data[keymap[key]].append(parsed)
222
+ return data
223
+
224
+ def convert_time(self) -> None:
225
+ pass
226
+
227
+ def screen_timestamps(self, date: str) -> None:
228
+ dates = [str(d.date()) for d in self._data["time"]]
229
+ valid_ind = [ind for ind, d in enumerate(dates) if d == date]
230
+ if not valid_ind:
231
+ raise ValidTimeStampError
232
+ for key in self._data:
233
+ self._data[key] = [
234
+ x for ind, x in enumerate(self._data[key]) if ind in valid_ind
235
+ ]
236
+
237
+ def add_date(self) -> None:
238
+ first_date = self._data["time"][0].date()
239
+ self.date = [
240
+ str(first_date.year),
241
+ str(first_date.month).zfill(2),
242
+ str(first_date.day).zfill(2),
243
+ ]
244
+
245
+ def add_data(self) -> None:
246
+ for key, value in self._data.items():
247
+ parsed = datetime2decimal_hours(value) if key == "time" else np.array(value)
248
+ self.data[key] = CloudnetArray(parsed, key)
249
+ self.data["rainfall_amount"] = CloudnetArray(
250
+ np.cumsum(self._data["rainfall_rate"]), "rainfall_amount"
251
+ )
252
+
253
+ def convert_units(self) -> None:
254
+ temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
255
+ self.data["air_temperature"].data = temperature_kelvins
256
+ self.data["relative_humidity"].data = self.data["relative_humidity"][:] / 100
257
+ self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa -> Pa
258
+ rainfall_rate = self.data["rainfall_rate"][:]
259
+ self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
260
+ self.data["rainfall_amount"].data = (
261
+ self.data["rainfall_amount"][:] / 1000
262
+ ) # mm -> m
149
263
 
150
264
 
151
265
  ATTRIBUTES = {
@@ -130,3 +130,4 @@ class Mwr:
130
130
  )
131
131
  self.nc_l2.source_file_uuids = self.nc_l1c.file_uuid
132
132
  self.nc_l2.mwrpy_version = mwrpy_version
133
+ self.nc_l2.instrument_pid = self.nc_l1c.instrument_pid
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 61
3
- PATCH = 4
3
+ PATCH = 6
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.61.4
3
+ Version: 1.61.6
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -8,14 +8,14 @@ cloudnetpy/metadata.py,sha256=v_VDo2vbdTxB0zIsfP69IcrwSKiRlLpsGdq6JPI4CoA,5306
8
8
  cloudnetpy/output.py,sha256=WoVTNuxni0DUr163vZ-_mDr1brXhY15XSlGMrq9Aoqw,14700
9
9
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  cloudnetpy/utils.py,sha256=0TlHm71YtSrKXBsRKctitnhQrvZPE-ulEVeAQW-oK58,27398
11
- cloudnetpy/version.py,sha256=e_eLbPeFYVSCx8YcbtXxBImBp3rQVNwG5SXDoE8Pkps,72
11
+ cloudnetpy/version.py,sha256=EsOOkDhmmN4gMdVOGcAyBa9FEtyJpkC4FPZ6YnoxVgA,72
12
12
  cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
13
13
  cloudnetpy/categorize/atmos.py,sha256=fWW8ye_8HZASRAiYwURFKWzcGOYIA2RKeVxCq0lVOuM,12389
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=wndpwJxc2-QnNTkV8tc8I11Vs_WkNz9sVMX1fuGgUC4,3777
15
- cloudnetpy/categorize/categorize.py,sha256=QNSqEtpe1qtFQAKOG1suffPaYEwtOf4KMij9PdW9-Us,17477
15
+ cloudnetpy/categorize/categorize.py,sha256=p3LLBXSu5AOMrJWwAZeS6J-Xzh4PkXuPmOCZiEHzt2w,17678
16
16
  cloudnetpy/categorize/classify.py,sha256=x7aqPfhw4xuER22sqOb9ES9nijwk1E8b7HF7uaFJD7k,9218
17
17
  cloudnetpy/categorize/containers.py,sha256=j6oSKPeZcq9vFthYaocAw1m6yReRNNPYUQF5UTDq4YM,4232
18
- cloudnetpy/categorize/disdrometer.py,sha256=0Z0nvUtoZKDxiUfBZzoYZxUFOVjq-thmYfaCkskeECs,1799
18
+ cloudnetpy/categorize/disdrometer.py,sha256=daPB1JgERRqa0Ekxx_LYCP8mDe3XnUYj2VlsIKAB7sE,2003
19
19
  cloudnetpy/categorize/droplet.py,sha256=pUmB-gN0t9sVgsGLof6X9N0nuEb4EBtEUswwpoQapTY,8687
20
20
  cloudnetpy/categorize/falling.py,sha256=xES5ZdYs34tbX1p4a9kzt9r3G5s25Mpvs5WeFs1KNzo,4385
21
21
  cloudnetpy/categorize/freezing.py,sha256=684q83TPQ5hHrbbHX-E36VoTlWLSOlGfOW1FC8b3wfI,3754
@@ -45,13 +45,13 @@ cloudnetpy/instruments/pollyxt.py,sha256=SccV9htZ5MWrK7JEleOr4hbmeTr-lKktUzAt7H9
45
45
  cloudnetpy/instruments/radiometrics.py,sha256=2ofeZ6KJ_JOWTd3UA-wSzJpM5cjN7R4jZeBLJCQKEYc,7624
46
46
  cloudnetpy/instruments/rpg.py,sha256=U8nEOlOI74f2lk2w4C4xKZCrW6AkDZpQZYE3yv7SNHE,17130
47
47
  cloudnetpy/instruments/rpg_reader.py,sha256=LAdXL3TmD5QzQbqtPOcemZji_qkXwmw6a6F8NmF6Zg8,11355
48
- cloudnetpy/instruments/toa5.py,sha256=xYJYEVNykCWqIsESno0eBIWqkYb-LHXjFjUp3EoqGDU,1565
48
+ cloudnetpy/instruments/toa5.py,sha256=1JnuYViD8c_tHJZ9lf4OU44iepEkXHsXOzDfVf_b0qc,1759
49
49
  cloudnetpy/instruments/vaisala.py,sha256=GzESZvboOoXzWmmr9dC-y6oM6ogc-M-zT3KmBTaD0LI,14512
50
- cloudnetpy/instruments/weather_station.py,sha256=gTY3Y5UATqJo9Gld4hm7WdsKBwcF8WgNTIK2nOfl3Nc,5739
50
+ cloudnetpy/instruments/weather_station.py,sha256=0ajUvZ4BQIr_HXwBgEek2rf3Oorqp4L-t95gIUVK9vM,9650
51
51
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
52
52
  cloudnetpy/instruments/disdrometer/common.py,sha256=g52iK2aNp3Z88kovUmGVpC54NZomPa9D871gzO0AmQ4,9267
53
53
  cloudnetpy/instruments/disdrometer/parsivel.py,sha256=WiL-vCjw9Gmb5irvW3AXddsyprp8MGOfqcVAlfy0zpc,25521
54
- cloudnetpy/instruments/disdrometer/thies.py,sha256=lNR5ahOKIsO_gcpmbYZwh2UP2aawpQ5J9RrrnPKFFIE,10046
54
+ cloudnetpy/instruments/disdrometer/thies.py,sha256=8V_1wx-8ncBJKs40e1_kvpOh3wj5UIl8YwvkVHf34MA,10086
55
55
  cloudnetpy/model_evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  cloudnetpy/model_evaluation/file_handler.py,sha256=oUGIblcEWLLv16YKUch-M5KA-dGRAcuHa-9anP3xtX4,6447
57
57
  cloudnetpy/model_evaluation/metadata.py,sha256=7ZL87iDbaQJIMu8wfnMvb01cGVPkl8RtvEm_tt9uIHE,8413
@@ -105,11 +105,11 @@ cloudnetpy/products/ier.py,sha256=IcGPlQahbwJjp3vOOrxWSYW2FPzbSV0KQL5eYECc4kU,77
105
105
  cloudnetpy/products/iwc.py,sha256=MUPuVKWgqOuuLRCGk3QY74uBZB_7P1qlinlP8nEvz9o,10124
106
106
  cloudnetpy/products/lwc.py,sha256=TbIR6kMwjbm63ed5orB1pkqx9ZBm8C5TF2JmT8WKdKI,18794
107
107
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
108
- cloudnetpy/products/mwr_tools.py,sha256=PRm5aCULccUehU-Byk55wYhhEHseMjoAjGBu5TSyHao,4621
108
+ cloudnetpy/products/mwr_tools.py,sha256=RuzokxxqXlTGk7XAOrif_FDPUJdf0j_wJgNq-7a_nK8,4684
109
109
  cloudnetpy/products/product_tools.py,sha256=rhx_Ru9FLlQqCNM-awoiHx18-Aq1eBwL9LiUaQoJs6k,10412
110
110
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
111
- cloudnetpy-1.61.4.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
112
- cloudnetpy-1.61.4.dist-info/METADATA,sha256=gM3zR9qeG2l3S744iQ035IAb7bxzONhHnegT14nGHfM,5784
113
- cloudnetpy-1.61.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
114
- cloudnetpy-1.61.4.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
115
- cloudnetpy-1.61.4.dist-info/RECORD,,
111
+ cloudnetpy-1.61.6.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
112
+ cloudnetpy-1.61.6.dist-info/METADATA,sha256=0LfEZSv2s7N1rWbBerVyMlCKhFGupgn6KqjvJvZxDa4,5784
113
+ cloudnetpy-1.61.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
114
+ cloudnetpy-1.61.6.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
115
+ cloudnetpy-1.61.6.dist-info/RECORD,,