cloudnetpy 1.80.8__py3-none-any.whl → 1.81.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.
Files changed (83) hide show
  1. cloudnetpy/categorize/__init__.py +1 -1
  2. cloudnetpy/categorize/atmos_utils.py +31 -27
  3. cloudnetpy/categorize/attenuations/__init__.py +4 -4
  4. cloudnetpy/categorize/attenuations/liquid_attenuation.py +7 -5
  5. cloudnetpy/categorize/attenuations/melting_attenuation.py +3 -3
  6. cloudnetpy/categorize/attenuations/rain_attenuation.py +4 -4
  7. cloudnetpy/categorize/categorize.py +25 -11
  8. cloudnetpy/categorize/classify.py +9 -8
  9. cloudnetpy/categorize/containers.py +13 -10
  10. cloudnetpy/categorize/disdrometer.py +5 -3
  11. cloudnetpy/categorize/droplet.py +12 -9
  12. cloudnetpy/categorize/falling.py +9 -8
  13. cloudnetpy/categorize/freezing.py +10 -7
  14. cloudnetpy/categorize/insects.py +18 -17
  15. cloudnetpy/categorize/lidar.py +7 -3
  16. cloudnetpy/categorize/melting.py +16 -15
  17. cloudnetpy/categorize/model.py +17 -10
  18. cloudnetpy/categorize/mwr.py +5 -3
  19. cloudnetpy/categorize/radar.py +15 -13
  20. cloudnetpy/cli.py +10 -8
  21. cloudnetpy/cloudnetarray.py +8 -7
  22. cloudnetpy/concat_lib.py +29 -20
  23. cloudnetpy/datasource.py +26 -21
  24. cloudnetpy/exceptions.py +12 -10
  25. cloudnetpy/instruments/basta.py +19 -9
  26. cloudnetpy/instruments/bowtie.py +18 -11
  27. cloudnetpy/instruments/ceilo.py +22 -10
  28. cloudnetpy/instruments/ceilometer.py +33 -34
  29. cloudnetpy/instruments/cl61d.py +5 -3
  30. cloudnetpy/instruments/cloudnet_instrument.py +7 -7
  31. cloudnetpy/instruments/copernicus.py +16 -7
  32. cloudnetpy/instruments/disdrometer/common.py +5 -4
  33. cloudnetpy/instruments/disdrometer/parsivel.py +14 -9
  34. cloudnetpy/instruments/disdrometer/thies.py +11 -7
  35. cloudnetpy/instruments/fd12p.py +7 -6
  36. cloudnetpy/instruments/galileo.py +16 -7
  37. cloudnetpy/instruments/hatpro.py +33 -24
  38. cloudnetpy/instruments/lufft.py +6 -4
  39. cloudnetpy/instruments/mira.py +33 -19
  40. cloudnetpy/instruments/mrr.py +12 -12
  41. cloudnetpy/instruments/nc_lidar.py +1 -1
  42. cloudnetpy/instruments/nc_radar.py +8 -8
  43. cloudnetpy/instruments/pollyxt.py +19 -12
  44. cloudnetpy/instruments/radiometrics.py +17 -10
  45. cloudnetpy/instruments/rain_e_h3.py +9 -5
  46. cloudnetpy/instruments/rpg.py +32 -21
  47. cloudnetpy/instruments/rpg_reader.py +15 -12
  48. cloudnetpy/instruments/vaisala.py +32 -24
  49. cloudnetpy/instruments/weather_station.py +28 -21
  50. cloudnetpy/model_evaluation/file_handler.py +27 -29
  51. cloudnetpy/model_evaluation/plotting/plot_tools.py +7 -5
  52. cloudnetpy/model_evaluation/plotting/plotting.py +41 -32
  53. cloudnetpy/model_evaluation/products/advance_methods.py +38 -34
  54. cloudnetpy/model_evaluation/products/grid_methods.py +10 -9
  55. cloudnetpy/model_evaluation/products/model_products.py +15 -9
  56. cloudnetpy/model_evaluation/products/observation_products.py +12 -10
  57. cloudnetpy/model_evaluation/products/product_resampling.py +11 -7
  58. cloudnetpy/model_evaluation/products/tools.py +18 -14
  59. cloudnetpy/model_evaluation/statistics/statistical_methods.py +6 -5
  60. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +18 -25
  61. cloudnetpy/model_evaluation/utils.py +3 -3
  62. cloudnetpy/output.py +15 -32
  63. cloudnetpy/plotting/plotting.py +22 -12
  64. cloudnetpy/products/classification.py +15 -9
  65. cloudnetpy/products/der.py +24 -19
  66. cloudnetpy/products/drizzle.py +21 -13
  67. cloudnetpy/products/drizzle_error.py +8 -7
  68. cloudnetpy/products/drizzle_tools.py +27 -23
  69. cloudnetpy/products/epsilon.py +6 -5
  70. cloudnetpy/products/ier.py +11 -5
  71. cloudnetpy/products/iwc.py +18 -9
  72. cloudnetpy/products/lwc.py +41 -31
  73. cloudnetpy/products/mwr_tools.py +30 -19
  74. cloudnetpy/products/product_tools.py +23 -19
  75. cloudnetpy/utils.py +84 -98
  76. cloudnetpy/version.py +2 -2
  77. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/METADATA +3 -2
  78. cloudnetpy-1.81.1.dist-info/RECORD +126 -0
  79. cloudnetpy-1.80.8.dist-info/RECORD +0 -126
  80. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/WHEEL +0 -0
  81. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/entry_points.txt +0 -0
  82. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/licenses/LICENSE +0 -0
  83. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  import logging
2
2
  from datetime import datetime, timezone
3
+ from os import PathLike
3
4
 
4
5
  import numpy as np
6
+ import numpy.typing as npt
5
7
  from numpy import ma
6
8
 
7
9
  from cloudnetpy import utils
@@ -24,7 +26,7 @@ class ObservationManager(DataSource):
24
26
  should be processed using CloudnetPy for this class to work properly.
25
27
  """
26
28
 
27
- def __init__(self, obs: str, obs_file: str):
29
+ def __init__(self, obs: str, obs_file: str | PathLike) -> None:
28
30
  super().__init__(obs_file)
29
31
  self.obs = obs
30
32
  self._file = obs_file
@@ -45,13 +47,13 @@ class ObservationManager(DataSource):
45
47
  tzinfo=timezone.utc,
46
48
  )
47
49
 
48
- def _get_radar_frequency(self) -> np.ndarray | None:
50
+ def _get_radar_frequency(self) -> npt.NDArray | None:
49
51
  try:
50
52
  return self.getvar("radar_frequency")
51
53
  except (KeyError, RuntimeError):
52
54
  return None
53
55
 
54
- def _get_z_sensitivity(self) -> np.ndarray | None:
56
+ def _get_z_sensitivity(self) -> npt.NDArray | None:
55
57
  try:
56
58
  return self.getvar("Z_sensitivity")
57
59
  except (KeyError, RuntimeError):
@@ -72,14 +74,14 @@ class ObservationManager(DataSource):
72
74
  logging.exception(msg)
73
75
  raise
74
76
 
75
- def _generate_cf(self) -> np.ndarray:
77
+ def _generate_cf(self) -> npt.NDArray:
76
78
  """Generates cloud fractions using categorize bits and masking conditions."""
77
79
  categorize_bits = CategorizeBits(self._file)
78
80
  cloud_mask = self._classify_basic_mask(categorize_bits.category_bits)
79
81
  return self._mask_cloud_bits(cloud_mask)
80
82
 
81
83
  @staticmethod
82
- def _classify_basic_mask(bits: CategoryBits) -> np.ndarray:
84
+ def _classify_basic_mask(bits: CategoryBits) -> npt.NDArray:
83
85
  cloud_mask = bits.droplet + bits.falling * 2
84
86
  cloud_mask[bits.falling & bits.freezing] = (
85
87
  cloud_mask[bits.falling & bits.freezing] + 2
@@ -90,7 +92,7 @@ class ObservationManager(DataSource):
90
92
  return cloud_mask
91
93
 
92
94
  @staticmethod
93
- def _mask_cloud_bits(cloud_mask: np.ndarray) -> np.ndarray:
95
+ def _mask_cloud_bits(cloud_mask: npt.NDArray) -> npt.NDArray:
94
96
  """Creates cloud fraction."""
95
97
  for i in [1, 3, 4, 5]:
96
98
  cloud_mask[cloud_mask == i] = 1
@@ -116,7 +118,7 @@ class ObservationManager(DataSource):
116
118
  rainrate_threshold = 2
117
119
  return rainrate_threshold
118
120
 
119
- def _rain_index(self) -> np.ndarray:
121
+ def _rain_index(self) -> npt.NDArray:
120
122
  rainrate = self.getvar("rainrate")
121
123
  rainrate_threshold = self._get_rainrate_threshold()
122
124
  return rainrate > rainrate_threshold
@@ -130,13 +132,13 @@ class ObservationManager(DataSource):
130
132
  self._get_rain_iwc(iwc_status)
131
133
  self._mask_iwc(iwc, iwc_status)
132
134
 
133
- def _mask_iwc(self, iwc: np.ndarray, iwc_status: np.ndarray) -> None:
135
+ def _mask_iwc(self, iwc: npt.NDArray, iwc_status: npt.NDArray) -> None:
134
136
  """Leaves only reliable data and corrected liquid attenuation."""
135
137
  iwc_mask = ma.copy(iwc)
136
138
  iwc_mask[np.bitwise_and(iwc_status != 1, iwc_status != 2)] = ma.masked
137
139
  self.append_data(iwc_mask, "iwc")
138
140
 
139
- def _mask_iwc_att(self, iwc: np.ndarray, iwc_status: np.ndarray) -> None:
141
+ def _mask_iwc_att(self, iwc: npt.NDArray, iwc_status: npt.NDArray) -> None:
140
142
  """Leaves only where reliable data, corrected liquid attenuation
141
143
  and uncorrected liquid attenuation.
142
144
  """
@@ -144,7 +146,7 @@ class ObservationManager(DataSource):
144
146
  iwc_att[iwc_status > 3] = ma.masked
145
147
  self.append_data(iwc_att, "iwc_att")
146
148
 
147
- def _get_rain_iwc(self, iwc_status: np.ndarray) -> None:
149
+ def _get_rain_iwc(self, iwc_status: npt.NDArray) -> None:
148
150
  """Finds columns where is rain, return boolean of x-axis shape."""
149
151
  iwc_rain = np.zeros(iwc_status.shape, dtype=bool)
150
152
  iwc_rain[iwc_status == 5] = 1
@@ -1,4 +1,6 @@
1
1
  import logging
2
+ from os import PathLike
3
+ from uuid import UUID
2
4
 
3
5
  import cloudnetpy.model_evaluation.products.tools as tl
4
6
  from cloudnetpy.model_evaluation.file_handler import (
@@ -12,18 +14,19 @@ from cloudnetpy.model_evaluation.products.grid_methods import ProductGrid
12
14
  from cloudnetpy.model_evaluation.products.model_products import ModelManager
13
15
  from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
14
16
  from cloudnetpy.model_evaluation.utils import file_exists
17
+ from cloudnetpy.utils import get_uuid
15
18
 
16
19
 
17
20
  def process_L3_day_product(
18
21
  model: str,
19
22
  obs: str,
20
- model_files: list,
21
- product_file: str,
22
- output_file: str,
23
- uuid: str | None = None,
23
+ model_files: list[str | PathLike],
24
+ product_file: str | PathLike,
25
+ output_file: str | PathLike,
26
+ uuid: str | UUID | None = None,
24
27
  *,
25
28
  overwrite: bool = False,
26
- ) -> str:
29
+ ) -> UUID:
27
30
  """Main function to generate downsample of observations to match model grid.
28
31
 
29
32
  This function will generate a L3 product nc-file. It includes the information of
@@ -63,6 +66,7 @@ def process_L3_day_product(
63
66
  >>> process_L3_day_product(model, product, [model_file], input_file,
64
67
  output_file)
65
68
  """
69
+ uuid = get_uuid(uuid)
66
70
  product_obj = ObservationManager(obs, product_file)
67
71
  tl.check_model_file_list(model, model_files)
68
72
  for m_file in model_files:
@@ -82,7 +86,7 @@ def process_L3_day_product(
82
86
  update_attributes(model_obj.data, attributes)
83
87
  if not file_exists(output_file) or overwrite:
84
88
  tl.add_date(model_obj, product_obj)
85
- uuid_out = save_downsampled_file(
89
+ save_downsampled_file(
86
90
  f"{obs}_{model}",
87
91
  output_file,
88
92
  (model_obj, product_obj),
@@ -91,4 +95,4 @@ def process_L3_day_product(
91
95
  )
92
96
  else:
93
97
  add_var2ncfile(model_obj, output_file)
94
- return uuid_out
98
+ return uuid
@@ -1,28 +1,32 @@
1
1
  import datetime
2
2
  import logging
3
+ import os.path
4
+ from collections.abc import Sequence
3
5
  from datetime import timedelta
6
+ from os import PathLike
4
7
 
5
8
  import numpy as np
9
+ import numpy.typing as npt
6
10
  from numpy import ma
7
11
 
8
12
  from cloudnetpy.model_evaluation.products.model_products import ModelManager
9
13
  from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
10
14
 
11
15
 
12
- def check_model_file_list(name: str, models: list) -> None:
16
+ def check_model_file_list(name: str, models: Sequence[str | PathLike]) -> None:
13
17
  """Check that files in models are from same model and date."""
14
18
  for m in models:
15
- if name not in m:
19
+ if name not in os.path.basename(m):
16
20
  logging.error("Invalid model file set")
17
21
  msg = f"{m} not from {name}"
18
22
  raise AttributeError(msg)
19
23
 
20
24
 
21
- def time2datetime(time: np.ndarray, date: datetime.datetime) -> np.ndarray:
25
+ def time2datetime(time: npt.NDArray, date: datetime.datetime) -> npt.NDArray:
22
26
  return np.asarray([date + timedelta(hours=float(t)) for t in time])
23
27
 
24
28
 
25
- def rebin_edges(arr: np.ndarray) -> np.ndarray:
29
+ def rebin_edges(arr: npt.NDArray) -> npt.NDArray:
26
30
  """Rebins array bins by half and adds boundaries."""
27
31
  new_arr = [(arr[i] + arr[i + 1]) / 2 for i in range(len(arr) - 1)]
28
32
  new_arr.insert(0, arr[0] - ((arr[0] + arr[1]) / 2))
@@ -34,7 +38,7 @@ def calculate_advection_time(
34
38
  resolution: int,
35
39
  wind: ma.MaskedArray,
36
40
  sampling: int,
37
- ) -> np.ndarray:
41
+ ) -> npt.NDArray:
38
42
  """Calculates time which variable takes to go through the time window.
39
43
 
40
44
  Notes:
@@ -52,10 +56,10 @@ def calculate_advection_time(
52
56
 
53
57
  def get_1d_indices(
54
58
  window: tuple,
55
- data: np.ndarray,
56
- mask: np.ndarray | None = None,
57
- ) -> np.ndarray:
58
- indices: np.ndarray = np.array((window[0] <= data) & (data < window[-1]))
59
+ data: npt.NDArray,
60
+ mask: npt.NDArray | None = None,
61
+ ) -> npt.NDArray:
62
+ indices: npt.NDArray = np.array((window[0] <= data) & (data < window[-1]))
59
63
  if mask is not None:
60
64
  indices[mask] = ma.masked
61
65
  return indices
@@ -64,16 +68,16 @@ def get_1d_indices(
64
68
  def get_adv_indices(
65
69
  model_t: int,
66
70
  adv_t: float,
67
- data: np.ndarray,
68
- mask: np.ndarray | None = None,
69
- ) -> np.ndarray:
71
+ data: npt.NDArray,
72
+ mask: npt.NDArray | None = None,
73
+ ) -> npt.NDArray:
70
74
  adv_indices = ((model_t - adv_t / 2) <= data) & (data < (model_t + adv_t / 2))
71
75
  if mask is not None:
72
76
  adv_indices[mask] = ma.masked
73
77
  return adv_indices
74
78
 
75
79
 
76
- def get_obs_window_size(ind_x: np.ndarray, ind_y: np.ndarray) -> tuple | None:
80
+ def get_obs_window_size(ind_x: npt.NDArray, ind_y: npt.NDArray) -> tuple | None:
77
81
  """Returns shape (tuple) of window area, where values are True."""
78
82
  x = np.where(ind_x)[0]
79
83
  y = np.where(ind_y)[0]
@@ -87,6 +91,6 @@ def add_date(model_obj: ModelManager, obs_obj: ObservationManager) -> None:
87
91
  model_obj.date.append(getattr(obs_obj.dataset, a))
88
92
 
89
93
 
90
- def average_column_sum(data: np.ndarray) -> np.ndarray:
94
+ def average_column_sum(data: npt.NDArray) -> npt.NDArray:
91
95
  """Returns average sum of columns which have any data."""
92
96
  return np.nanmean(np.nansum(data, 1) > 0)
@@ -3,6 +3,7 @@ import os
3
3
  import sys
4
4
 
5
5
  import numpy as np
6
+ import numpy.typing as npt
6
7
  from numpy import ma
7
8
 
8
9
  sys.path.append(os.path.dirname(os.path.abspath(__file__)))
@@ -20,8 +21,8 @@ class DayStatistics:
20
21
  done with. A list includes observed product name (str), model variable (str)
21
22
  name and a name of observation variable (str). Example: ['cf', 'ECMWF',
22
23
  'Cloud fraction by volume']
23
- model (np.ndarray): Ndarray of model simulation of product
24
- observation (np.ndarray): Ndrray of Downsampled observation of product
24
+ model (npt.NDArray): Ndarray of model simulation of product
25
+ observation (npt.NDArray): Ndrray of Downsampled observation of product
25
26
 
26
27
  Raises:
27
28
  RuntimeError: A function of given method not found
@@ -44,9 +45,9 @@ class DayStatistics:
44
45
  self,
45
46
  method: str,
46
47
  product_info: list,
47
- model: np.ndarray,
48
- observation: np.ndarray,
49
- ):
48
+ model: npt.NDArray,
49
+ observation: npt.NDArray,
50
+ ) -> None:
50
51
  self.method = method
51
52
  self.product = product_info
52
53
  self.model_data = model
@@ -1,34 +1,35 @@
1
1
  import pytest
2
2
 
3
3
  from cloudnetpy.model_evaluation.plotting import plotting as pl
4
+ from cloudnetpy.model_evaluation.plotting.plot_meta import PlotMeta
4
5
 
5
6
  MODEL = "ecmwf"
6
-
7
-
8
- class VariableInfo:
9
- def __init__(self):
10
- self.name = "Product"
7
+ VARIABLE_INFO = PlotMeta(
8
+ name="Product",
9
+ cbar="rainbow",
10
+ plot_range=(0, 1),
11
+ plot_scale="linear",
12
+ plot_type="mesh",
13
+ )
11
14
 
12
15
 
13
16
  @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
14
17
  def test_get_cf_title(key) -> None:
15
- var = VariableInfo()
16
18
  field_name = key + "_" + MODEL
17
19
  value = "Product, Volume"
18
20
  if "A" in key:
19
21
  value = "Product, Area"
20
- x = pl._get_cf_title(field_name, var)
22
+ x = pl._get_cf_title(field_name, VARIABLE_INFO)
21
23
  assert x == value
22
24
 
23
25
 
24
26
  @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
25
27
  def test_get_cf_title_cycle(key) -> None:
26
- var = VariableInfo()
27
28
  field_name = key + "_" + MODEL + "_001"
28
29
  value = "Product, Volume"
29
30
  if "A" in key:
30
31
  value = "Product, Area"
31
- x = pl._get_cf_title(field_name, var)
32
+ x = pl._get_cf_title(field_name, VARIABLE_INFO)
32
33
  assert x == value
33
34
 
34
35
 
@@ -44,9 +45,8 @@ def test_get_cf_title_cycle(key) -> None:
44
45
  ],
45
46
  )
46
47
  def test_get_iwc_title(key, value) -> None:
47
- var = VariableInfo()
48
48
  field_name = key + "_" + MODEL
49
- x = pl._get_iwc_title(field_name, var)
49
+ x = pl._get_iwc_title(field_name, VARIABLE_INFO)
50
50
  assert x == value
51
51
 
52
52
 
@@ -62,23 +62,20 @@ def test_get_iwc_title(key, value) -> None:
62
62
  ],
63
63
  )
64
64
  def test_get_iwc_title_cycle(key, value) -> None:
65
- var = VariableInfo()
66
65
  field_name = key + "_" + MODEL + "_001"
67
- x = pl._get_iwc_title(field_name, var)
66
+ x = pl._get_iwc_title(field_name, VARIABLE_INFO)
68
67
  assert x == value
69
68
 
70
69
 
71
70
  def test_get_product_title() -> None:
72
- var = VariableInfo()
73
71
  value = "Product"
74
- x = pl._get_product_title(var)
72
+ x = pl._get_product_title(VARIABLE_INFO)
75
73
  assert x == value
76
74
 
77
75
 
78
76
  def test_get_product_title_cycle() -> None:
79
- var = VariableInfo()
80
77
  value = "Product"
81
- x = pl._get_product_title(var)
78
+ x = pl._get_product_title(VARIABLE_INFO)
82
79
  assert x == value
83
80
 
84
81
 
@@ -88,16 +85,14 @@ def test_get_product_title_cycle() -> None:
88
85
  )
89
86
  def test_get_stat_titles(key, title) -> None:
90
87
  field_name = key + "_" + MODEL
91
- var = VariableInfo()
92
- x = pl._get_stat_titles(field_name, key, var)
88
+ x = pl._get_stat_titles(field_name, key, VARIABLE_INFO)
93
89
  assert x == title
94
90
 
95
91
 
96
92
  @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
97
93
  def test_get_cf_title_stat(key) -> None:
98
94
  field_name = key + "_" + MODEL
99
- var = VariableInfo()
100
- x = pl._get_cf_title_stat(field_name, var)
95
+ x = pl._get_cf_title_stat(field_name, VARIABLE_INFO)
101
96
  value = "Product volume"
102
97
  if "A" in key:
103
98
  value = "Product area"
@@ -114,13 +109,11 @@ def test_get_cf_title_stat(key) -> None:
114
109
  )
115
110
  def test_get_iwc_title_stat(key, value) -> None:
116
111
  field_name = key + "_" + MODEL
117
- var = VariableInfo()
118
- x = pl._get_iwc_title_stat(field_name, var)
112
+ x = pl._get_iwc_title_stat(field_name, VARIABLE_INFO)
119
113
  assert x == value
120
114
 
121
115
 
122
116
  @pytest.mark.parametrize("key", ["lwc"])
123
117
  def test_get_product_title_stat(key) -> None:
124
- var = VariableInfo()
125
- x = pl._get_product_title_stat(var)
118
+ x = pl._get_product_title_stat(VARIABLE_INFO)
126
119
  assert x == "Product"
@@ -1,6 +1,6 @@
1
1
  import os
2
- from pathlib import Path
2
+ from os import PathLike
3
3
 
4
4
 
5
- def file_exists(file_path: str) -> bool:
6
- return Path.is_file(Path(file_path)) and os.path.getsize(file_path) > 0
5
+ def file_exists(file_path: str | PathLike) -> bool:
6
+ return os.path.isfile(file_path) and os.path.getsize(file_path) > 0
cloudnetpy/output.py CHANGED
@@ -18,26 +18,20 @@ from cloudnetpy.metadata import COMMON_ATTRIBUTES
18
18
 
19
19
 
20
20
  def save_level1b(
21
- obj,
21
+ obj, # noqa: ANN001
22
22
  output_file: PathLike | str,
23
- uuid: UUID | str | None = None,
24
- ) -> str:
23
+ uuid: UUID,
24
+ ) -> None:
25
25
  """Saves Cloudnet Level 1b file."""
26
26
  dimensions = _get_netcdf_dimensions(obj)
27
27
  with init_file(output_file, dimensions, obj.data, uuid) as nc:
28
- file_uuid = nc.file_uuid
29
28
  fix_attribute_name(nc)
30
29
  location = obj.site_meta["name"]
31
30
  nc.cloudnet_file_type = obj.instrument.domain
32
31
  nc.title = get_l1b_title(obj.instrument, location)
33
- if isinstance(obj.date, list):
34
- nc.year, nc.month, nc.day = obj.date
35
- elif isinstance(obj.date, datetime.date):
36
- nc.year = str(obj.date.year)
37
- nc.month = str(obj.date.month).zfill(2)
38
- nc.day = str(obj.date.day).zfill(2)
39
- else:
40
- raise TypeError
32
+ nc.year = str(obj.date.year)
33
+ nc.month = str(obj.date.month).zfill(2)
34
+ nc.day = str(obj.date.day).zfill(2)
41
35
  nc.location = location
42
36
  nc.history = get_l1b_history(obj.instrument)
43
37
  nc.source = get_l1b_source(obj.instrument)
@@ -47,10 +41,9 @@ def save_level1b(
47
41
  for software, version in obj.software.items():
48
42
  nc.setncattr(f"{software}_version", version)
49
43
  nc.references = get_references()
50
- return file_uuid
51
44
 
52
45
 
53
- def _get_netcdf_dimensions(obj) -> dict:
46
+ def _get_netcdf_dimensions(obj) -> dict: # noqa: ANN001
54
47
  dimensions = {
55
48
  key: len(obj.data[key][:]) for key in ("time", "range") if key in obj.data
56
49
  }
@@ -78,10 +71,10 @@ def _get_netcdf_dimensions(obj) -> dict:
78
71
  def save_product_file(
79
72
  short_id: str,
80
73
  obj: DataSource,
81
- file_name: str,
82
- uuid: str | None = None,
74
+ file_name: str | PathLike,
75
+ uuid: UUID,
83
76
  copy_from_cat: tuple = (),
84
- ) -> str:
77
+ ) -> None:
85
78
  """Saves a standard Cloudnet product file.
86
79
 
87
80
  Args:
@@ -98,7 +91,6 @@ def save_product_file(
98
91
  "height": len(obj.dataset.variables["height"]),
99
92
  }
100
93
  with init_file(file_name, dimensions, obj.data, uuid) as nc:
101
- file_uuid = nc.file_uuid
102
94
  nc.cloudnet_file_type = short_id
103
95
  vars_from_source = (
104
96
  "altitude",
@@ -121,7 +113,6 @@ def save_product_file(
121
113
  )
122
114
  merge_history(nc, human_readable_file_type, obj)
123
115
  nc.references = get_references(short_id)
124
- return file_uuid
125
116
 
126
117
 
127
118
  def get_l1b_source(instrument: Instrument) -> str:
@@ -252,7 +243,7 @@ def init_file(
252
243
  file_name: PathLike | str,
253
244
  dimensions: dict,
254
245
  cloudnet_arrays: dict,
255
- uuid: UUID | str | None = None,
246
+ uuid: UUID,
256
247
  ) -> netCDF4.Dataset:
257
248
  """Initializes a Cloudnet file for writing.
258
249
 
@@ -325,16 +316,11 @@ def copy_global(
325
316
 
326
317
  def add_time_attribute(
327
318
  attributes: dict,
328
- date: list[str] | datetime.date,
319
+ date: datetime.date,
329
320
  key: str = "time",
330
321
  ) -> dict:
331
322
  """Adds time attribute with correct units."""
332
- if isinstance(date, list):
333
- date_str = "-".join(date)
334
- elif isinstance(date, datetime.date):
335
- date_str = date.isoformat()
336
- else:
337
- raise TypeError
323
+ date_str = date.isoformat()
338
324
  units = f"hours since {date_str} 00:00:00 +00:00"
339
325
  if key not in attributes:
340
326
  attributes[key] = COMMON_ATTRIBUTES[key]
@@ -436,13 +422,10 @@ def _get_identifier(short_id: str) -> str:
436
422
  return short_id
437
423
 
438
424
 
439
- def add_standard_global_attributes(
440
- nc: netCDF4.Dataset,
441
- uuid: UUID | str | None = None,
442
- ) -> None:
425
+ def add_standard_global_attributes(nc: netCDF4.Dataset, uuid: UUID) -> None:
443
426
  nc.Conventions = "CF-1.8"
444
427
  nc.cloudnetpy_version = version.__version__
445
- nc.file_uuid = str(uuid) if uuid is not None else utils.get_uuid()
428
+ nc.file_uuid = str(uuid)
446
429
 
447
430
 
448
431
  def fix_attribute_name(nc: netCDF4.Dataset) -> None:
@@ -1,18 +1,20 @@
1
1
  """Misc. plotting routines for Cloudnet products."""
2
2
 
3
- import os.path
4
3
  import re
5
4
  import textwrap
6
5
  from dataclasses import dataclass
7
6
  from datetime import date
7
+ from os import PathLike
8
8
  from typing import Any
9
9
 
10
10
  import matplotlib.pyplot as plt
11
11
  import netCDF4
12
12
  import numpy as np
13
+ import numpy.typing as npt
13
14
  from matplotlib import rcParams
14
15
  from matplotlib.axes import Axes
15
16
  from matplotlib.colorbar import Colorbar
17
+ from matplotlib.colorizer import ColorizingArtist
16
18
  from matplotlib.colors import ListedColormap
17
19
  from matplotlib.pyplot import Figure
18
20
  from matplotlib.ticker import AutoMinorLocator
@@ -83,12 +85,15 @@ class Dimensions:
83
85
  margin_left (int): Space between left edge of image and plotted data in pixels.
84
86
  """
85
87
 
86
- def __init__(self, fig, axes, pad_inches: float | None = None):
88
+ def __init__(
89
+ self, fig: Figure, axes: list[Axes], pad_inches: float | None = None
90
+ ) -> None:
87
91
  if pad_inches is None:
88
92
  pad_inches = rcParams["savefig.pad_inches"]
89
93
 
94
+ renderer = fig.canvas.get_renderer() # type: ignore[attr-defined]
90
95
  tightbbox = (
91
- fig.get_tightbbox(fig.canvas.get_renderer())
96
+ fig.get_tightbbox(renderer)
92
97
  .padded(pad_inches)
93
98
  .transformed(Affine2D().scale(fig.dpi))
94
99
  )
@@ -112,7 +117,7 @@ class FigureData:
112
117
  file: netCDF4.Dataset,
113
118
  requested_variables: list[str],
114
119
  options: PlotParameters,
115
- ):
120
+ ) -> None:
116
121
  self.file = file
117
122
  self.variables, self.indices = self._get_valid_variables_and_indices(
118
123
  requested_variables
@@ -231,7 +236,7 @@ class SubPlot:
231
236
  variable: netCDF4.Variable,
232
237
  options: PlotParameters,
233
238
  file_type: str | None,
234
- ):
239
+ ) -> None:
235
240
  self.ax = ax
236
241
  self.variable = variable
237
242
  self.options = options
@@ -348,7 +353,7 @@ class SubPlot:
348
353
 
349
354
 
350
355
  class Plot:
351
- def __init__(self, sub_plot: SubPlot):
356
+ def __init__(self, sub_plot: SubPlot) -> None:
352
357
  self.sub_plot = sub_plot
353
358
  self._data = sub_plot.variable[:]
354
359
  self._data_orig = self._data.copy()
@@ -389,7 +394,7 @@ class Plot:
389
394
  def _get_y_limits(self) -> tuple[float, float]:
390
395
  return 0, self.sub_plot.options.max_y
391
396
 
392
- def _init_colorbar(self, plot) -> Colorbar:
397
+ def _init_colorbar(self, plot: ColorizingArtist) -> Colorbar:
393
398
  divider = make_axes_locatable(self._ax)
394
399
  cax = divider.append_axes("right", size="1%", pad=0.25)
395
400
  return plt.colorbar(plot, fraction=1.0, ax=self._ax, cax=cax)
@@ -476,7 +481,7 @@ class Plot:
476
481
 
477
482
 
478
483
  class Plot2D(Plot):
479
- def plot(self, figure_data: FigureData):
484
+ def plot(self, figure_data: FigureData) -> None:
480
485
  self._convert_units()
481
486
  if figure_data.file_type == "cpr-simulation":
482
487
  min_x, max_x = 0, EARTHCARE_MAX_X
@@ -616,7 +621,12 @@ class Plot2D(Plot):
616
621
  linestyles="dashed",
617
622
  )
618
623
 
619
- def _plot_contour(self, figure_data: FigureData, alt: np.ndarray, **options):
624
+ def _plot_contour(
625
+ self,
626
+ figure_data: FigureData,
627
+ alt: npt.NDArray,
628
+ **options, # noqa: ANN003
629
+ ) -> None:
620
630
  time_length = len(figure_data.time_including_gaps)
621
631
  step = max(1, time_length // 200)
622
632
  ind_time = np.arange(0, time_length, step)
@@ -882,11 +892,11 @@ class Plot1D(Plot):
882
892
 
883
893
 
884
894
  def generate_figure(
885
- filename: os.PathLike | str,
895
+ filename: PathLike | str,
886
896
  variables: list[str],
887
897
  *,
888
898
  show: bool = True,
889
- output_filename: os.PathLike | str | None = None,
899
+ output_filename: PathLike | str | None = None,
890
900
  options: PlotParameters | None = None,
891
901
  ) -> Dimensions:
892
902
  """Generate a figure based on the given filename and variables.
@@ -960,7 +970,7 @@ def generate_figure(
960
970
  plt.close(fig)
961
971
 
962
972
 
963
- def lin2log(*args) -> list:
973
+ def lin2log(*args: npt.ArrayLike) -> list[ma.MaskedArray]:
964
974
  return [ma.log10(x) for x in args]
965
975
 
966
976