cloudnetpy 1.61.15__py3-none-any.whl → 1.61.17__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.
Files changed (67) hide show
  1. cloudnetpy/categorize/atmos.py +1 -0
  2. cloudnetpy/categorize/atmos_utils.py +1 -1
  3. cloudnetpy/categorize/categorize.py +1 -0
  4. cloudnetpy/categorize/classify.py +1 -0
  5. cloudnetpy/categorize/containers.py +3 -1
  6. cloudnetpy/categorize/disdrometer.py +1 -0
  7. cloudnetpy/categorize/droplet.py +2 -2
  8. cloudnetpy/categorize/falling.py +1 -0
  9. cloudnetpy/categorize/freezing.py +1 -0
  10. cloudnetpy/categorize/insects.py +1 -0
  11. cloudnetpy/categorize/lidar.py +1 -0
  12. cloudnetpy/categorize/melting.py +1 -0
  13. cloudnetpy/categorize/model.py +1 -0
  14. cloudnetpy/categorize/mwr.py +1 -0
  15. cloudnetpy/categorize/radar.py +1 -0
  16. cloudnetpy/cloudnetarray.py +2 -0
  17. cloudnetpy/concat_lib.py +1 -0
  18. cloudnetpy/constants.py +1 -0
  19. cloudnetpy/datasource.py +5 -3
  20. cloudnetpy/instruments/basta.py +1 -0
  21. cloudnetpy/instruments/ceilo.py +2 -1
  22. cloudnetpy/instruments/cl61d.py +1 -0
  23. cloudnetpy/instruments/copernicus.py +2 -1
  24. cloudnetpy/instruments/disdrometer/parsivel.py +6 -4
  25. cloudnetpy/instruments/galileo.py +1 -0
  26. cloudnetpy/instruments/hatpro.py +1 -0
  27. cloudnetpy/instruments/lufft.py +1 -0
  28. cloudnetpy/instruments/mira.py +1 -0
  29. cloudnetpy/instruments/mrr.py +1 -1
  30. cloudnetpy/instruments/nc_lidar.py +1 -0
  31. cloudnetpy/instruments/nc_radar.py +1 -0
  32. cloudnetpy/instruments/pollyxt.py +1 -0
  33. cloudnetpy/instruments/radiometrics.py +2 -1
  34. cloudnetpy/instruments/rpg.py +2 -1
  35. cloudnetpy/instruments/rpg_reader.py +1 -1
  36. cloudnetpy/instruments/toa5.py +1 -1
  37. cloudnetpy/instruments/vaisala.py +7 -6
  38. cloudnetpy/instruments/weather_station.py +118 -65
  39. cloudnetpy/model_evaluation/file_handler.py +2 -2
  40. cloudnetpy/model_evaluation/metadata.py +1 -1
  41. cloudnetpy/model_evaluation/plotting/plot_tools.py +2 -2
  42. cloudnetpy/model_evaluation/plotting/plotting.py +11 -8
  43. cloudnetpy/model_evaluation/products/grid_methods.py +1 -1
  44. cloudnetpy/model_evaluation/products/model_products.py +7 -7
  45. cloudnetpy/model_evaluation/products/observation_products.py +8 -8
  46. cloudnetpy/model_evaluation/products/tools.py +5 -7
  47. cloudnetpy/model_evaluation/statistics/statistical_methods.py +2 -2
  48. cloudnetpy/output.py +3 -1
  49. cloudnetpy/plotting/plot_meta.py +2 -2
  50. cloudnetpy/plotting/plotting.py +36 -23
  51. cloudnetpy/products/classification.py +10 -9
  52. cloudnetpy/products/der.py +3 -2
  53. cloudnetpy/products/drizzle.py +3 -3
  54. cloudnetpy/products/drizzle_tools.py +1 -1
  55. cloudnetpy/products/ier.py +1 -0
  56. cloudnetpy/products/iwc.py +4 -3
  57. cloudnetpy/products/lwc.py +2 -3
  58. cloudnetpy/products/mwr_tools.py +2 -4
  59. cloudnetpy/products/product_tools.py +2 -1
  60. cloudnetpy/utils.py +9 -14
  61. cloudnetpy/version.py +1 -1
  62. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.17.dist-info}/METADATA +1 -1
  63. cloudnetpy-1.61.17.dist-info/RECORD +115 -0
  64. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.17.dist-info}/WHEEL +1 -1
  65. cloudnetpy-1.61.15.dist-info/RECORD +0 -115
  66. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.17.dist-info}/LICENSE +0 -0
  67. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.17.dist-info}/top_level.txt +0 -0
@@ -47,12 +47,16 @@ def ws2nc(
47
47
  ws: WS
48
48
  if site_meta["name"] == "Palaiseau":
49
49
  ws = PalaiseauWS(weather_station_file, site_meta)
50
+ elif site_meta["name"] == "Bucharest":
51
+ ws = BucharestWS(weather_station_file, site_meta)
50
52
  elif site_meta["name"] == "Granada":
51
53
  ws = GranadaWS(weather_station_file, site_meta)
52
54
  elif site_meta["name"] == "Kenttärova":
53
55
  ws = KenttarovaWS(weather_station_file, site_meta)
54
56
  elif site_meta["name"] == "Hyytiälä":
55
57
  ws = HyytialaWS(weather_station_file, site_meta)
58
+ elif site_meta["name"] == "Galați":
59
+ ws = GalatiWS(weather_station_file, site_meta)
56
60
  else:
57
61
  msg = "Unsupported site"
58
62
  raise ValueError(msg) # noqa: TRY301
@@ -66,6 +70,7 @@ def ws2nc(
66
70
  ws.convert_pressure()
67
71
  ws.convert_rainfall_rate()
68
72
  ws.convert_rainfall_amount()
73
+ ws.normalize_rainfall_amount()
69
74
  ws.calculate_rainfall_amount()
70
75
  attributes = output.add_time_attribute(ATTRIBUTES, ws.date)
71
76
  output.update_attributes(ws.data, attributes)
@@ -75,9 +80,11 @@ def ws2nc(
75
80
 
76
81
 
77
82
  class WS(CloudnetInstrument):
78
- def __init__(self):
83
+ def __init__(self, site_meta: dict):
79
84
  super().__init__()
80
85
  self._data: dict
86
+ self.site_meta = site_meta
87
+ self.instrument = instruments.GENERIC_WEATHER_STATION
81
88
 
82
89
  date: list[str]
83
90
 
@@ -111,6 +118,15 @@ class WS(CloudnetInstrument):
111
118
  x for ind, x in enumerate(self._data[key]) if ind in valid_ind
112
119
  ]
113
120
 
121
+ @staticmethod
122
+ def format_data(data: dict) -> dict:
123
+ for key, value in data.items():
124
+ new_value = np.array(value)
125
+ if key != "time":
126
+ new_value = ma.masked_where(np.isnan(new_value), new_value)
127
+ data[key] = new_value
128
+ return data
129
+
114
130
  def convert_temperature_and_humidity(self) -> None:
115
131
  temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
116
132
  self.data["air_temperature"].data = temperature_kelvins
@@ -123,6 +139,17 @@ class WS(CloudnetInstrument):
123
139
  def convert_pressure(self) -> None:
124
140
  self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa to Pa
125
141
 
142
+ def normalize_rainfall_amount(self) -> None:
143
+ if "rainfall_amount" in self.data:
144
+ amount = self.data["rainfall_amount"][:]
145
+ offset = 0
146
+ for i in range(1, len(amount)):
147
+ if amount[i] + offset < amount[i - 1]:
148
+ offset += amount[i - 1]
149
+ amount[i] += offset
150
+ amount -= amount[0]
151
+ self.data["rainfall_amount"].data = amount
152
+
126
153
  def convert_time(self) -> None:
127
154
  pass
128
155
 
@@ -132,50 +159,30 @@ class WS(CloudnetInstrument):
132
159
 
133
160
  class PalaiseauWS(WS):
134
161
  def __init__(self, filenames: list[str], site_meta: dict):
135
- super().__init__()
136
- if len(filenames) != 1:
137
- raise ValueError
138
- self.filename = filenames[0]
139
- self.site_meta = site_meta
140
- self.instrument = instruments.GENERIC_WEATHER_STATION
162
+ super().__init__(site_meta)
163
+ self.filenames = filenames
141
164
  self._data = self._read_data()
142
165
 
143
166
  def _read_data(self) -> dict:
144
167
  timestamps, values, header = [], [], []
145
- with open(self.filename, encoding="latin-1") as f:
146
- data = f.readlines()
147
- for row in data:
148
- splat = row.split()
149
- try:
150
- timestamp = datetime.datetime.strptime(
151
- splat[0],
152
- "%Y-%m-%dT%H:%M:%SZ",
153
- ).replace(tzinfo=datetime.timezone.utc)
154
- temp: list[str | float] = list(splat)
155
- temp[1:] = [float(x) for x in temp[1:]]
156
- values.append(temp)
157
- timestamps.append(timestamp)
158
- except ValueError:
159
- header.append("".join(splat))
160
-
161
- # Simple validation for now:
162
- expected_identifiers = [
163
- "DateTime(yyyy-mm-ddThh:mm:ssZ)",
164
- "Windspeed(m/s)",
165
- "Winddirection(degres)",
166
- "Airtemperature(°C)",
167
- "Relativehumidity(%)",
168
- "Pressure(hPa)",
169
- "Precipitationrate(mm/min)",
170
- "24-hrcumulatedprecipitationsince00UT(mm)",
171
- ]
172
- column_titles = [row for row in header if "Col." in row]
173
- error_msg = "Unexpected weather station file format"
174
- if len(column_titles) != len(expected_identifiers):
175
- raise ValueError(error_msg)
176
- for title, identifier in zip(column_titles, expected_identifiers, strict=True):
177
- if identifier not in title:
178
- raise ValueError(error_msg)
168
+ for filename in self.filenames:
169
+ with open(filename, encoding="latin-1") as f:
170
+ data = f.readlines()
171
+ for row in data:
172
+ if not (columns := row.split()):
173
+ continue
174
+ if row.startswith("#"):
175
+ header_row = "".join(columns)
176
+ if header_row not in header:
177
+ header.append(header_row)
178
+ else:
179
+ timestamp = datetime.datetime.strptime(
180
+ columns[0], "%Y-%m-%dT%H:%M:%SZ"
181
+ ).replace(tzinfo=datetime.timezone.utc)
182
+ values.append([timestamp] + [float(x) for x in columns[1:]])
183
+ timestamps.append(timestamp)
184
+
185
+ self._validate_header(header)
179
186
  return {"time": timestamps, "values": values}
180
187
 
181
188
  def convert_time(self) -> None:
@@ -212,15 +219,39 @@ class PalaiseauWS(WS):
212
219
  self.data["rainfall_amount"][:] / 1000
213
220
  ) # mm -> m
214
221
 
222
+ @staticmethod
223
+ def _validate_header(header: list[str]) -> None:
224
+ expected_identifiers = [
225
+ "DateTime(yyyy-mm-ddThh:mm:ssZ)",
226
+ "Windspeed(m/s)",
227
+ "Winddirection(deg",
228
+ "Airtemperature",
229
+ "Relativehumidity(%)",
230
+ "Pressure(hPa)",
231
+ "Precipitationrate(mm/min)",
232
+ "precipitation",
233
+ ]
234
+ column_titles = [row for row in header if "Col." in row]
235
+ error_msg = "Unexpected weather station file format"
236
+ if len(column_titles) != len(expected_identifiers):
237
+ raise ValueError(error_msg)
238
+ for title, identifier in zip(column_titles, expected_identifiers, strict=True):
239
+ if identifier not in title:
240
+ raise ValueError(error_msg)
241
+
242
+
243
+ class BucharestWS(PalaiseauWS):
244
+ def convert_rainfall_rate(self) -> None:
245
+ rainfall_rate = self.data["rainfall_rate"][:]
246
+ self.data["rainfall_rate"].data = rainfall_rate * MM_H_TO_M_S
247
+
215
248
 
216
249
  class GranadaWS(WS):
217
250
  def __init__(self, filenames: list[str], site_meta: dict):
218
251
  if len(filenames) != 1:
219
252
  raise ValueError
220
- super().__init__()
253
+ super().__init__(site_meta)
221
254
  self.filename = filenames[0]
222
- self.site_meta = site_meta
223
- self.instrument = instruments.GENERIC_WEATHER_STATION
224
255
  self._data = self._read_data()
225
256
 
226
257
  def _read_data(self) -> dict:
@@ -264,10 +295,8 @@ class GranadaWS(WS):
264
295
 
265
296
  class KenttarovaWS(WS):
266
297
  def __init__(self, filenames: list[str], site_meta: dict):
267
- super().__init__()
298
+ super().__init__(site_meta)
268
299
  self.filenames = filenames
269
- self.site_meta = site_meta
270
- self.instrument = instruments.GENERIC_WEATHER_STATION
271
300
  self._data = self._read_data()
272
301
 
273
302
  def _read_data(self) -> dict:
@@ -302,12 +331,7 @@ class KenttarovaWS(WS):
302
331
  merged = {key: [*merged[key], *data[key]] for key in merged}
303
332
  else:
304
333
  merged = data
305
- for key, value in merged.items():
306
- new_value = np.array(value)
307
- if key != "time":
308
- new_value = ma.masked_where(np.isnan(new_value), new_value)
309
- merged[key] = new_value
310
- return merged
334
+ return self.format_data(merged)
311
335
 
312
336
  def convert_rainfall_rate(self) -> None:
313
337
  # Rainfall rate is 10-minute averaged in mm h-1
@@ -320,18 +344,15 @@ class KenttarovaWS(WS):
320
344
 
321
345
 
322
346
  class HyytialaWS(WS):
323
- """
324
- Hyytiälä rain-gauge variables: a = Pluvio400 and b = Pluvio200.
347
+ """Hyytiälä rain-gauge variables: a = Pluvio400 and b = Pluvio200.
325
348
  E.g.
326
349
  - AaRNRT/mm = amount of non-real-time rain total (Pluvio400) [mm]
327
- - BbRT/mm = Bucket content in real-time (Pluvio200) [mm]
350
+ - BbRT/mm = Bucket content in real-time (Pluvio200) [mm].
328
351
  """
329
352
 
330
353
  def __init__(self, filenames: list[str], site_meta: dict):
331
- super().__init__()
354
+ super().__init__(site_meta)
332
355
  self.filename = filenames[0]
333
- self.site_meta = site_meta
334
- self.instrument = instruments.GENERIC_WEATHER_STATION
335
356
  self._data = self._read_data()
336
357
 
337
358
  def _read_data(self) -> dict:
@@ -381,12 +402,7 @@ class HyytialaWS(WS):
381
402
  "wind_direction": raw_data["WD/ds"],
382
403
  "rainfall_rate": raw_data["AaNRT/mm"],
383
404
  }
384
- for key, value in data.items():
385
- new_value = np.array(value)
386
- if key != "time":
387
- new_value = ma.masked_where(np.isnan(new_value), new_value)
388
- data[key] = new_value
389
- return data
405
+ return self.format_data(data)
390
406
 
391
407
  def convert_pressure(self) -> None:
392
408
  self.data["air_pressure"].data = (
@@ -394,6 +410,43 @@ class HyytialaWS(WS):
394
410
  ) # kPa to Pa
395
411
 
396
412
 
413
+ class GalatiWS(WS):
414
+ def __init__(self, filenames: list[str], site_meta: dict):
415
+ super().__init__(site_meta)
416
+ self.filename = filenames[0]
417
+ self._data = self._read_data()
418
+
419
+ def _read_data(self) -> dict:
420
+ with open(self.filename, newline="") as f:
421
+ reader = csv.DictReader(f)
422
+ raw_data: dict = {key: [] for key in reader.fieldnames} # type: ignore[union-attr]
423
+ for row in reader:
424
+ for key, value in row.items():
425
+ parsed_value: float | datetime.datetime
426
+ if key == "TimeStamp":
427
+ parsed_value = datetime.datetime.strptime(
428
+ value, "%Y-%m-%d %H:%M:%S.%f"
429
+ )
430
+ else:
431
+ try:
432
+ parsed_value = float(value)
433
+ except ValueError:
434
+ parsed_value = math.nan
435
+ raw_data[key].append(parsed_value)
436
+ data = {
437
+ "time": raw_data["TimeStamp"],
438
+ "air_temperature": raw_data["Temperature"],
439
+ "relative_humidity": raw_data["RH"],
440
+ "air_pressure": raw_data["Atmospheric_pressure"],
441
+ "rainfall_rate": raw_data["Precipitations"],
442
+ }
443
+ return self.format_data(data)
444
+
445
+ def convert_pressure(self) -> None:
446
+ mmHg2Pa = 133.322
447
+ self.data["air_pressure"].data = self.data["air_pressure"][:] * mmHg2Pa
448
+
449
+
397
450
  ATTRIBUTES = {
398
451
  "rainfall_amount": MetaData(
399
452
  long_name="Rainfall amount",
@@ -145,7 +145,7 @@ def _augment_global_attributes(root_group: netCDF4.Dataset) -> None:
145
145
 
146
146
 
147
147
  def _add_source(root_ground: netCDF4.Dataset, objects: tuple, files: tuple) -> None:
148
- """Generates source info for multiple files"""
148
+ """Generates source info for multiple files."""
149
149
  model, obs = objects
150
150
  model_files, obs_file = files
151
151
  source = f"Observation file: {os.path.basename(obs_file)}"
@@ -160,7 +160,7 @@ def _add_source(root_ground: netCDF4.Dataset, objects: tuple, files: tuple) -> N
160
160
 
161
161
 
162
162
  def add_time_attribute(date: datetime) -> dict:
163
- """ "Adds time attribute with correct units.
163
+ """Adds time attribute with correct units.
164
164
 
165
165
  Args:
166
166
  attributes: Attributes of variables.
@@ -114,7 +114,7 @@ REGRID_PRODUCT_ATTRIBUTES = {
114
114
  comment=(
115
115
  "Cloud fraction generated from observations and by volume,\n"
116
116
  "averaged onto the models grid with height and time. Volume is\n"
117
- "space withing four grid points."
117
+ "space within four grid points."
118
118
  ),
119
119
  ),
120
120
  "cf_A": MetaData(
@@ -13,7 +13,7 @@ def parse_wanted_names(
13
13
  *,
14
14
  advance: bool = False,
15
15
  ) -> tuple[list, list]:
16
- """Returns standard and advection lists of product types to plot"""
16
+ """Returns standard and advection lists of product types to plot."""
17
17
  if variables:
18
18
  names = variables
19
19
  else:
@@ -76,7 +76,7 @@ def sort_cycles(names: list, model: str) -> tuple[list, list]:
76
76
 
77
77
 
78
78
  def read_data_characters(nc_file: str, name: str, model: str) -> tuple:
79
- """Gets dimensions and data for plotting"""
79
+ """Gets dimensions and data for plotting."""
80
80
  nc = netCDF4.Dataset(nc_file)
81
81
  data = nc.variables[name][:]
82
82
  data = mask_small_values(data, name)
@@ -7,9 +7,11 @@ import matplotlib as mpl
7
7
  import matplotlib.pyplot as plt
8
8
  import netCDF4
9
9
  import numpy as np
10
+ from matplotlib.axes import Axes
10
11
  from matplotlib.colorbar import Colorbar
11
12
  from matplotlib.colors import ListedColormap
12
13
  from matplotlib.patches import Patch
14
+ from matplotlib.pyplot import Figure
13
15
  from mpl_toolkits.axes_grid1 import make_axes_locatable
14
16
  from numpy import ma
15
17
 
@@ -187,6 +189,7 @@ def get_group_plots(
187
189
  show (bool): Show figure before saving if True
188
190
  cycle (str): Name of cycle if exists
189
191
  title (bool): True or False if wanted to add title to subfig
192
+ include_xlimits (bool): Show labels at the ends of x-axis
190
193
  """
191
194
  fig, ax = initialize_figure(len(names))
192
195
  model_run = model
@@ -248,6 +251,7 @@ def get_pair_plots(
248
251
  show (bool): Show figure before saving if True
249
252
  cycle (str): Name of cycle if exists
250
253
  title (bool): True or False if wanted add title to subfig
254
+ include_xlimits (bool): Show labels at the ends of x-axis
251
255
  """
252
256
  variable_info = ATTRIBUTES[product]
253
257
  model_ax = names[0]
@@ -306,6 +310,7 @@ def get_single_plots(
306
310
  show (bool): Show figure before saving if True
307
311
  cycle (str): Name of cycle if exists
308
312
  title (bool): True or False if wanted to add title to subfig
313
+ include_xlimits (bool): Show labels at the ends of x-axis
309
314
  """
310
315
  figs = []
311
316
  axes = []
@@ -387,6 +392,7 @@ def get_statistic_plots(
387
392
  image_name (str, optional): Saving name of generated fig
388
393
  show (bool): Show figure before saving if True
389
394
  cycle (str): Name of cycle if exists
395
+ title (bool): True or False if wanted to add title to subfig
390
396
  """
391
397
  model_run = model
392
398
  name = ""
@@ -687,8 +693,8 @@ def plot_vertical_profile(
687
693
  ax.xaxis.grid(which="major")
688
694
 
689
695
 
690
- def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
691
- """Set up fig and ax object, if subplot"""
696
+ def initialize_figure(n_subplots: int, stat: str = "") -> tuple[Figure, list[Axes]]:
697
+ """Set up fig and ax object, if subplot."""
692
698
  if n_subplots <= 0:
693
699
  n_subplots = 1
694
700
  fig, axes = plt.subplots(n_subplots, 1, figsize=(16, 4 + (n_subplots - 1) * 4.8))
@@ -714,7 +720,6 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
714
720
  right=0.75,
715
721
  hspace=0.2,
716
722
  )
717
- axes = axes.flatten()
718
723
  if stat == "vertical" and n_subplots > 1:
719
724
  fig, axes = plt.subplots(
720
725
  int(n_subplots / 2),
@@ -731,10 +736,8 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
731
736
  right=0.75,
732
737
  hspace=0.16,
733
738
  )
734
- axes = axes.flatten()
735
- if n_subplots == 1:
736
- axes = [axes]
737
- return fig, axes
739
+ axes_list = [axes] if isinstance(axes, Axes) else axes.flatten().tolist()
740
+ return fig, axes_list
738
741
 
739
742
 
740
743
  def init_colorbar(plot, axis) -> Colorbar:
@@ -750,7 +753,7 @@ def _set_title(
750
753
  variable_info,
751
754
  model_name: str = "",
752
755
  ) -> None:
753
- """Generates subtitles for different product types"""
756
+ """Generates subtitles for different product types."""
754
757
  parts = field_name.split("_")
755
758
  if parts[0] == product:
756
759
  title = _get_product_title(variable_info)
@@ -154,7 +154,7 @@ class ProductGrid:
154
154
  x_ind: np.ndarray,
155
155
  y_ind: np.ndarray,
156
156
  ) -> np.ndarray | None:
157
- """Reshapes True observation values to windows shape"""
157
+ """Reshapes True observation values to windows shape."""
158
158
  window_size = tl.get_obs_window_size(x_ind, y_ind)
159
159
  if window_size is not None:
160
160
  return self._obs_data[ind].reshape(window_size)
@@ -54,7 +54,7 @@ class ModelManager(DataSource):
54
54
  self.resolution_h = self._get_horizontal_resolution()
55
55
 
56
56
  def _read_cycle_name(self, model_file: str) -> str:
57
- """Get cycle name from model_metadata.py for saving variable name(s)"""
57
+ """Get cycle name from model_metadata.py for saving variable name(s)."""
58
58
  try:
59
59
  cycles = self.model_info.cycle
60
60
  if cycles is None:
@@ -68,7 +68,7 @@ class ModelManager(DataSource):
68
68
  return ""
69
69
 
70
70
  def _generate_products(self) -> None:
71
- """Process needed data of model to a ModelManager object"""
71
+ """Process needed data of model to a ModelManager object."""
72
72
  cls = importlib.import_module(__name__).ModelManager
73
73
  try:
74
74
  name = f"_get_{self._product}"
@@ -123,10 +123,10 @@ class ModelManager(DataSource):
123
123
  return q * p / (287 * t)
124
124
 
125
125
  def _add_variables(self) -> None:
126
- """Add basic variables off model and cycle"""
126
+ """Add basic variables off model and cycle."""
127
127
 
128
128
  def _add_common_variables() -> None:
129
- """Model variables that are always the same within cycles"""
129
+ """Model variables that are always the same within cycles."""
130
130
  wanted_vars = self.model_vars.common_var
131
131
  if wanted_vars is None:
132
132
  msg = f"Model {self.model} has no common variables"
@@ -140,7 +140,7 @@ class ModelManager(DataSource):
140
140
  self.append_data(data, f"{var}")
141
141
 
142
142
  def _add_cycle_variables() -> None:
143
- """Add cycle depending variables"""
143
+ """Add cycle depending variables."""
144
144
  wanted_vars = self.model_vars.cycle_var
145
145
  if wanted_vars is None:
146
146
  msg = f"Model {self.model} has no cycle variables"
@@ -160,7 +160,7 @@ class ModelManager(DataSource):
160
160
  _add_cycle_variables()
161
161
 
162
162
  def cut_off_extra_levels(self, data: np.ndarray) -> np.ndarray:
163
- """Remove unused levels (over 22km) from model data"""
163
+ """Remove unused levels (over 22km) from model data."""
164
164
  try:
165
165
  level = self.model_info.level
166
166
  except KeyError:
@@ -169,7 +169,7 @@ class ModelManager(DataSource):
169
169
  return data[:, :level] if data.ndim > 1 else data[:level]
170
170
 
171
171
  def _calculate_wind_speed(self) -> np.ndarray:
172
- """Real wind from x- and y-components"""
172
+ """Real wind from x- and y-components."""
173
173
  u = self.getvar("uwind")
174
174
  v = self.getvar("vwind")
175
175
  u = self.cut_off_extra_levels(u)
@@ -58,7 +58,7 @@ class ObservationManager(DataSource):
58
58
  return None
59
59
 
60
60
  def _generate_product(self) -> None:
61
- """Process needed data of observation to a ObservationManager object"""
61
+ """Process needed data of observation to a ObservationManager object."""
62
62
  try:
63
63
  if self.obs == "cf":
64
64
  self.append_data(self._generate_cf(), "cf")
@@ -73,7 +73,7 @@ class ObservationManager(DataSource):
73
73
  raise
74
74
 
75
75
  def _generate_cf(self) -> np.ndarray:
76
- """Generates cloud fractions using categorize bits and masking conditions"""
76
+ """Generates cloud fractions using categorize bits and masking conditions."""
77
77
  categorize_bits = CategorizeBits(self._file)
78
78
  cloud_mask = self._classify_basic_mask(categorize_bits.category_bits)
79
79
  return self._mask_cloud_bits(cloud_mask)
@@ -91,7 +91,7 @@ class ObservationManager(DataSource):
91
91
 
92
92
  @staticmethod
93
93
  def _mask_cloud_bits(cloud_mask: np.ndarray) -> np.ndarray:
94
- """Creates cloud fraction"""
94
+ """Creates cloud fraction."""
95
95
  for i in [1, 3, 4, 5]:
96
96
  cloud_mask[cloud_mask == i] = 1
97
97
  for i in [2, 6, 7, 8]:
@@ -99,7 +99,7 @@ class ObservationManager(DataSource):
99
99
  return cloud_mask
100
100
 
101
101
  def _check_rainrate(self) -> bool:
102
- """Check if rainrate in file"""
102
+ """Check if rainrate in file."""
103
103
  try:
104
104
  self.getvar("rainrate")
105
105
  except RuntimeError:
@@ -122,7 +122,7 @@ class ObservationManager(DataSource):
122
122
  return rainrate > rainrate_threshold
123
123
 
124
124
  def _generate_iwc_masks(self) -> None:
125
- """Generates ice water content variables with different masks"""
125
+ """Generates ice water content variables with different masks."""
126
126
  # TODO: Differences with CloudnetPy (status=2) and Legacy data (status=3)
127
127
  iwc = self.getvar(self.obs)
128
128
  iwc_status = self.getvar("iwc_retrieval_status")
@@ -131,21 +131,21 @@ class ObservationManager(DataSource):
131
131
  self._mask_iwc(iwc, iwc_status)
132
132
 
133
133
  def _mask_iwc(self, iwc: np.ndarray, iwc_status: np.ndarray) -> None:
134
- """Leaves only reliable data and corrected liquid attenuation"""
134
+ """Leaves only reliable data and corrected liquid attenuation."""
135
135
  iwc_mask = ma.copy(iwc)
136
136
  iwc_mask[np.bitwise_and(iwc_status != 1, iwc_status != 2)] = ma.masked
137
137
  self.append_data(iwc_mask, "iwc")
138
138
 
139
139
  def _mask_iwc_att(self, iwc: np.ndarray, iwc_status: np.ndarray) -> None:
140
140
  """Leaves only where reliable data, corrected liquid attenuation
141
- and uncorrected liquid attenuation
141
+ and uncorrected liquid attenuation.
142
142
  """
143
143
  iwc_att = ma.copy(iwc)
144
144
  iwc_att[iwc_status > 3] = ma.masked
145
145
  self.append_data(iwc_att, "iwc_att")
146
146
 
147
147
  def _get_rain_iwc(self, iwc_status: np.ndarray) -> None:
148
- """Finds columns where is rain, return boolean of x-axis shape"""
148
+ """Finds columns where is rain, return boolean of x-axis shape."""
149
149
  iwc_rain = np.zeros(iwc_status.shape, dtype=bool)
150
150
  iwc_rain[iwc_status == 5] = 1
151
151
  iwc_rain = np.any(iwc_rain, axis=1)
@@ -10,7 +10,7 @@ from cloudnetpy.model_evaluation.products.observation_products import Observatio
10
10
 
11
11
 
12
12
  def check_model_file_list(name: str, models: list) -> None:
13
- """Check that files in models are from same model and date"""
13
+ """Check that files in models are from same model and date."""
14
14
  for m in models:
15
15
  if name not in m:
16
16
  logging.error("Invalid model file set")
@@ -35,16 +35,14 @@ def calculate_advection_time(
35
35
  wind: ma.MaskedArray,
36
36
  sampling: int,
37
37
  ) -> np.ndarray:
38
- """Calculates time which variable takes to go through the time window
38
+ """Calculates time which variable takes to go through the time window.
39
39
 
40
- Notes
40
+ Notes:
41
41
  Wind speed is stronger in upper levels, so advection time is more
42
42
  there then lower levels. Effect is small in a mid-latitudes,
43
43
  but visible in a tropics.
44
44
 
45
45
  sampling = 1 -> hour, sampling 1/6 -> 10min
46
-
47
- References
48
46
  """
49
47
  t_adv = resolution * 1000 / wind / 60**2
50
48
  t_adv[t_adv.mask] = 0
@@ -76,7 +74,7 @@ def get_adv_indices(
76
74
 
77
75
 
78
76
  def get_obs_window_size(ind_x: np.ndarray, ind_y: np.ndarray) -> tuple | None:
79
- """Returns shape (tuple) of window area, where values are True"""
77
+ """Returns shape (tuple) of window area, where values are True."""
80
78
  x = np.where(ind_x)[0]
81
79
  y = np.where(ind_y)[0]
82
80
  if np.any(x) and np.any(y):
@@ -90,5 +88,5 @@ def add_date(model_obj: ModelManager, obs_obj: ObservationManager) -> None:
90
88
 
91
89
 
92
90
  def average_column_sum(data: np.ndarray) -> np.ndarray:
93
- """Returns average sum of columns which have any data"""
91
+ """Returns average sum of columns which have any data."""
94
92
  return np.nanmean(np.nansum(data, 1) > 0)
@@ -9,7 +9,7 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
9
9
 
10
10
 
11
11
  class DayStatistics:
12
- """Class for calculating statistical analysis of day scale products
12
+ """Class for calculating statistical analysis of day scale products.
13
13
 
14
14
  Class generates one statistical method at the time with given model data
15
15
  and observation data of wanted product.
@@ -107,7 +107,7 @@ def combine_masked_indices(
107
107
  model: ma.MaskedArray,
108
108
  observation: ma.MaskedArray,
109
109
  ) -> tuple[ma.MaskedArray, ma.MaskedArray]:
110
- """Connects two array masked indices to one and add in two array same mask"""
110
+ """Connects two array masked indices to one and add in two array same mask."""
111
111
  observation[np.where(np.isnan(observation))] = ma.masked
112
112
  model[model < np.min(observation)] = ma.masked
113
113
  combine_mask = model.mask + observation.mask
cloudnetpy/output.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Functions for file writing."""
2
+
2
3
  import datetime
3
4
  import logging
4
5
  from os import PathLike
@@ -140,10 +141,11 @@ def get_l1b_title(instrument: Instrument, location: str) -> str:
140
141
 
141
142
 
142
143
  def get_references(identifier: str | None = None, extra: list | None = None) -> str:
143
- """ "Returns references.
144
+ """Returns references.
144
145
 
145
146
  Args:
146
147
  identifier: Cloudnet file type, e.g., 'iwc'.
148
+ extra: List of additional references to include
147
149
 
148
150
  """
149
151
  references = "https://doi.org/10.21105/joss.02123"
@@ -1,11 +1,11 @@
1
1
  """Metadata for plotting module."""
2
+
2
3
  from collections.abc import Sequence
3
4
  from typing import NamedTuple
4
5
 
5
6
 
6
7
  class PlotMeta(NamedTuple):
7
- """
8
- A class representing the metadata for plotting.
8
+ """A class representing the metadata for plotting.
9
9
 
10
10
  Attributes:
11
11
  cmap: The colormap to be used for the plot.