cloudnetpy 1.49.9__py3-none-any.whl → 1.87.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.
Files changed (116) hide show
  1. cloudnetpy/categorize/__init__.py +1 -2
  2. cloudnetpy/categorize/atmos_utils.py +297 -67
  3. cloudnetpy/categorize/attenuation.py +31 -0
  4. cloudnetpy/categorize/attenuations/__init__.py +37 -0
  5. cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
  6. cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
  7. cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
  8. cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
  9. cloudnetpy/categorize/categorize.py +332 -156
  10. cloudnetpy/categorize/classify.py +127 -125
  11. cloudnetpy/categorize/containers.py +107 -76
  12. cloudnetpy/categorize/disdrometer.py +40 -0
  13. cloudnetpy/categorize/droplet.py +23 -21
  14. cloudnetpy/categorize/falling.py +53 -24
  15. cloudnetpy/categorize/freezing.py +25 -12
  16. cloudnetpy/categorize/insects.py +35 -23
  17. cloudnetpy/categorize/itu.py +243 -0
  18. cloudnetpy/categorize/lidar.py +36 -41
  19. cloudnetpy/categorize/melting.py +34 -26
  20. cloudnetpy/categorize/model.py +84 -37
  21. cloudnetpy/categorize/mwr.py +18 -14
  22. cloudnetpy/categorize/radar.py +215 -102
  23. cloudnetpy/cli.py +578 -0
  24. cloudnetpy/cloudnetarray.py +43 -89
  25. cloudnetpy/concat_lib.py +218 -78
  26. cloudnetpy/constants.py +28 -10
  27. cloudnetpy/datasource.py +61 -86
  28. cloudnetpy/exceptions.py +49 -20
  29. cloudnetpy/instruments/__init__.py +5 -0
  30. cloudnetpy/instruments/basta.py +29 -12
  31. cloudnetpy/instruments/bowtie.py +135 -0
  32. cloudnetpy/instruments/ceilo.py +138 -115
  33. cloudnetpy/instruments/ceilometer.py +164 -80
  34. cloudnetpy/instruments/cl61d.py +21 -5
  35. cloudnetpy/instruments/cloudnet_instrument.py +74 -36
  36. cloudnetpy/instruments/copernicus.py +108 -30
  37. cloudnetpy/instruments/da10.py +54 -0
  38. cloudnetpy/instruments/disdrometer/common.py +126 -223
  39. cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
  40. cloudnetpy/instruments/disdrometer/thies.py +254 -87
  41. cloudnetpy/instruments/fd12p.py +201 -0
  42. cloudnetpy/instruments/galileo.py +65 -23
  43. cloudnetpy/instruments/hatpro.py +123 -49
  44. cloudnetpy/instruments/instruments.py +113 -1
  45. cloudnetpy/instruments/lufft.py +39 -17
  46. cloudnetpy/instruments/mira.py +268 -61
  47. cloudnetpy/instruments/mrr.py +187 -0
  48. cloudnetpy/instruments/nc_lidar.py +19 -8
  49. cloudnetpy/instruments/nc_radar.py +109 -55
  50. cloudnetpy/instruments/pollyxt.py +135 -51
  51. cloudnetpy/instruments/radiometrics.py +313 -59
  52. cloudnetpy/instruments/rain_e_h3.py +171 -0
  53. cloudnetpy/instruments/rpg.py +321 -189
  54. cloudnetpy/instruments/rpg_reader.py +74 -40
  55. cloudnetpy/instruments/toa5.py +49 -0
  56. cloudnetpy/instruments/vaisala.py +95 -343
  57. cloudnetpy/instruments/weather_station.py +774 -105
  58. cloudnetpy/metadata.py +90 -19
  59. cloudnetpy/model_evaluation/file_handler.py +55 -52
  60. cloudnetpy/model_evaluation/metadata.py +46 -20
  61. cloudnetpy/model_evaluation/model_metadata.py +1 -1
  62. cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
  63. cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
  64. cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
  65. cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
  66. cloudnetpy/model_evaluation/products/model_products.py +43 -35
  67. cloudnetpy/model_evaluation/products/observation_products.py +41 -35
  68. cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
  69. cloudnetpy/model_evaluation/products/tools.py +29 -20
  70. cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
  71. cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
  72. cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
  73. cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
  74. cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
  75. cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
  76. cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
  77. cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
  78. cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
  79. cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
  80. cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
  81. cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
  82. cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
  83. cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
  84. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
  85. cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
  86. cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
  87. cloudnetpy/model_evaluation/utils.py +2 -1
  88. cloudnetpy/output.py +170 -111
  89. cloudnetpy/plotting/__init__.py +2 -1
  90. cloudnetpy/plotting/plot_meta.py +562 -822
  91. cloudnetpy/plotting/plotting.py +1142 -704
  92. cloudnetpy/products/__init__.py +1 -0
  93. cloudnetpy/products/classification.py +370 -88
  94. cloudnetpy/products/der.py +85 -55
  95. cloudnetpy/products/drizzle.py +77 -34
  96. cloudnetpy/products/drizzle_error.py +15 -11
  97. cloudnetpy/products/drizzle_tools.py +79 -59
  98. cloudnetpy/products/epsilon.py +211 -0
  99. cloudnetpy/products/ier.py +27 -50
  100. cloudnetpy/products/iwc.py +55 -48
  101. cloudnetpy/products/lwc.py +96 -70
  102. cloudnetpy/products/mwr_tools.py +186 -0
  103. cloudnetpy/products/product_tools.py +170 -128
  104. cloudnetpy/utils.py +455 -240
  105. cloudnetpy/version.py +2 -2
  106. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
  107. cloudnetpy-1.87.3.dist-info/RECORD +127 -0
  108. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
  109. cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
  110. docs/source/conf.py +2 -2
  111. cloudnetpy/categorize/atmos.py +0 -361
  112. cloudnetpy/products/mwr_multi.py +0 -68
  113. cloudnetpy/products/mwr_single.py +0 -75
  114. cloudnetpy-1.49.9.dist-info/RECORD +0 -112
  115. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
  116. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,27 @@
1
1
  """Module for reading raw cloud radar data."""
2
+
3
+ import datetime
2
4
  import os
5
+ import tempfile
6
+ from os import PathLike
3
7
  from tempfile import TemporaryDirectory
8
+ from uuid import UUID
4
9
 
5
10
  import numpy as np
6
11
 
7
12
  from cloudnetpy import concat_lib, output, utils
8
13
  from cloudnetpy.instruments.instruments import COPERNICUS
9
14
  from cloudnetpy.instruments.nc_radar import ChilboltonRadar
10
- from cloudnetpy.metadata import MetaData
15
+ from cloudnetpy.metadata import COMMON_ATTRIBUTES, MetaData
11
16
 
12
17
 
13
18
  def copernicus2nc(
14
- raw_files: str,
15
- output_file: str,
19
+ raw_files: str | PathLike,
20
+ output_file: str | PathLike,
16
21
  site_meta: dict,
17
- uuid: str | None = None,
18
- date: str | None = None,
19
- ) -> str:
22
+ uuid: str | UUID | None = None,
23
+ date: str | datetime.date | None = None,
24
+ ) -> UUID:
20
25
  """Converts 'Copernicus' cloud radar data into Cloudnet Level 1b netCDF file.
21
26
 
22
27
  Args:
@@ -41,6 +46,10 @@ def copernicus2nc(
41
46
  >>> copernicus2nc('/one/day/of/copernicus/files/', 'radar.nc', site_meta)
42
47
 
43
48
  """
49
+ if isinstance(date, str):
50
+ date = datetime.date.fromisoformat(date)
51
+ uuid = utils.get_uuid(uuid)
52
+
44
53
  keymap = {
45
54
  "ZED_HC": "Zh",
46
55
  "VEL_HC": "v",
@@ -55,15 +64,25 @@ def copernicus2nc(
55
64
  "beamwidthH": "beamwidthH",
56
65
  }
57
66
 
67
+ nc_filename: str | PathLike
58
68
  with TemporaryDirectory() as temp_dir:
59
69
  if os.path.isdir(raw_files):
60
- nc_filename = f"{temp_dir}/tmp.nc"
61
- valid_filenames = utils.get_sorted_filenames(raw_files, ".nc")
62
- valid_filenames = utils.get_files_with_common_range(valid_filenames)
63
- variables = list(keymap.keys())
64
- concat_lib.concatenate_files(
65
- valid_filenames, nc_filename, variables=variables
66
- )
70
+ with tempfile.NamedTemporaryFile(
71
+ dir=temp_dir,
72
+ suffix=".nc",
73
+ delete=False,
74
+ ) as temp_file:
75
+ nc_filename = temp_file.name
76
+ valid_filenames = utils.get_sorted_filenames(raw_files, ".nc")
77
+ valid_filenames = utils.get_files_with_variables(
78
+ valid_filenames, ["time", "ZED_HC"]
79
+ )
80
+ variables = list(keymap.keys())
81
+ concat_lib.concatenate_files(
82
+ valid_filenames,
83
+ nc_filename,
84
+ variables=variables,
85
+ )
67
86
  else:
68
87
  nc_filename = raw_files
69
88
 
@@ -75,19 +94,26 @@ def copernicus2nc(
75
94
  copernicus.sort_timestamps()
76
95
  copernicus.remove_duplicate_timestamps()
77
96
  copernicus.calibrate_reflectivity()
78
- copernicus.screen_by_snr(snr_limit=3)
97
+ copernicus.screen_using_top_gates_snr()
79
98
  copernicus.mask_corrupted_values()
99
+ copernicus.mask_first_range_gates()
80
100
  copernicus.mask_invalid_data()
81
- copernicus.add_time_and_range()
101
+ copernicus.fix_range_offset(site_meta)
102
+ copernicus.screen_negative_ranges()
82
103
  copernicus.add_radar_specific_variables()
83
104
  copernicus.add_nyquist_velocity(keymap)
84
105
  copernicus.add_site_geolocation()
85
- valid_indices = copernicus.add_zenith_and_azimuth_angles()
106
+ valid_indices = copernicus.add_zenith_and_azimuth_angles(
107
+ elevation_threshold=1.1,
108
+ elevation_diff_threshold=0.5,
109
+ azimuth_diff_threshold=0.1,
110
+ )
86
111
  copernicus.screen_time_indices(valid_indices)
87
112
  copernicus.add_height()
113
+ copernicus.test_if_all_masked()
88
114
  attributes = output.add_time_attribute(ATTRIBUTES, copernicus.date)
89
115
  output.update_attributes(copernicus.data, attributes)
90
- uuid = output.save_level1b(copernicus, output_file, uuid)
116
+ output.save_level1b(copernicus, output_file, uuid)
91
117
  return uuid
92
118
 
93
119
 
@@ -100,17 +126,35 @@ class Copernicus(ChilboltonRadar):
100
126
 
101
127
  """
102
128
 
103
- def __init__(self, full_path: str, site_meta: dict):
129
+ def __init__(self, full_path: str | PathLike, site_meta: dict) -> None:
104
130
  super().__init__(full_path, site_meta)
105
131
  self.instrument = COPERNICUS
106
132
 
107
- def calibrate_reflectivity(self):
108
- default_offset = -146.8 # TODO: check this value
109
- calibration_factor = self.site_meta.get("calibration_offset", default_offset)
110
- self.data["Zh"].data[:] += calibration_factor
111
- self.append_data(np.array(calibration_factor), "calibration_offset")
133
+ def calibrate_reflectivity(self) -> None:
134
+ zed_hc = self.dataset["ZED_HC"]
135
+ offset_applied = getattr(zed_hc, "applied_calibration_offset", 0)
136
+
137
+ # Estimated by comparing with MIRA-35 data:
138
+ default_offset = -149.5
139
+ zh_offset = self.site_meta.get("Zh_offset", default_offset)
140
+
141
+ self.data["Zh"].data[:] = self.data["Zh"].data[:] - offset_applied + zh_offset
142
+ self.append_data(np.array(zh_offset, dtype=np.float32), "Zh_offset")
143
+
144
+ def fix_range_offset(self, site_meta: dict) -> None:
145
+ range_var = self.dataset["range"]
146
+ offset_applied = getattr(range_var, "range_offset", 0)
112
147
 
113
- def mask_corrupted_values(self):
148
+ # Estimated by comparing with MIRA-35 data:
149
+ default_offset = -720
150
+ range_offset = site_meta.get("range_offset", default_offset)
151
+
152
+ self.data["range"].data[:] = (
153
+ self.data["range"].data[:] - offset_applied + range_offset
154
+ )
155
+ self.append_data(np.array(range_offset, dtype=float), "range_offset")
156
+
157
+ def mask_corrupted_values(self) -> None:
114
158
  """Experimental masking of corrupted Copernicus data.
115
159
 
116
160
  Notes:
@@ -122,14 +166,48 @@ class Copernicus(ChilboltonRadar):
122
166
  ind = np.where(np.abs(self.data[key][:]) > value)
123
167
  self.data["v"].mask_indices(ind)
124
168
 
169
+ def screen_negative_ranges(self) -> None:
170
+ """Screens negative range values."""
171
+ valid_ind = np.where(self.data["range"][:] >= 0)[0]
172
+ for key, cloudnet_array in self.data.items():
173
+ try:
174
+ data = cloudnet_array[:]
175
+ if data.ndim == 2:
176
+ cloudnet_array.data = data[:, valid_ind]
177
+ elif key == "range":
178
+ cloudnet_array.data = data[valid_ind]
179
+ except IndexError:
180
+ continue
181
+
125
182
 
126
183
  ATTRIBUTES = {
127
- "calibration_offset": MetaData(
184
+ "Zh_offset": MetaData(
128
185
  long_name="Radar reflectivity calibration offset",
129
- units="1",
130
- comment="Calibration offset applied.",
186
+ units="dBZ",
187
+ comment=(
188
+ "Calibration offset applied after removing the original offset "
189
+ "from the raw files."
190
+ ),
191
+ dimensions=None,
192
+ ),
193
+ "Zh": COMMON_ATTRIBUTES["Zh"]._replace(ancillary_variables="Zh_offset"),
194
+ "range_offset": MetaData(
195
+ long_name="Radar range offset",
196
+ units="m",
197
+ comment=(
198
+ "Range offset applied after removing the original offset "
199
+ "from the raw files."
200
+ ),
201
+ dimensions=None,
202
+ ),
203
+ "range": COMMON_ATTRIBUTES["range"]._replace(ancillary_variables="range_offset"),
204
+ "antenna_diameter": MetaData(
205
+ long_name="Antenna diameter", units="m", dimensions=("time",)
206
+ ),
207
+ "beamwidthV": MetaData(
208
+ long_name="Vertical angular beamwidth", units="degree", dimensions=("time",)
209
+ ),
210
+ "beamwidthH": MetaData(
211
+ long_name="Horizontal angular beamwidth", units="degree", dimensions=("time",)
131
212
  ),
132
- "antenna_diameter": MetaData(long_name="Antenna diameter", units="m"),
133
- "beamwidthV": MetaData(long_name="Vertical angular beamwidth", units="degree"),
134
- "beamwidthH": MetaData(long_name="Horizontal angular beamwidth", units="degree"),
135
213
  }
@@ -0,0 +1,54 @@
1
+ import datetime
2
+ import logging
3
+ from os import PathLike
4
+
5
+ import netCDF4
6
+
7
+ from cloudnetpy import utils
8
+ from cloudnetpy.exceptions import LidarDataError
9
+ from cloudnetpy.instruments import instruments
10
+ from cloudnetpy.instruments.nc_lidar import NcLidar
11
+
12
+
13
+ class Da10(NcLidar):
14
+ """Class for Vaisala DA10 differential absorption lidar."""
15
+
16
+ def __init__(
17
+ self,
18
+ file_name: str | PathLike,
19
+ site_meta: dict,
20
+ expected_date: datetime.date | None = None,
21
+ ) -> None:
22
+ super().__init__()
23
+ self.file_name = file_name
24
+ self.site_meta = site_meta
25
+ self.expected_date = expected_date
26
+ self.instrument = instruments.DA10
27
+
28
+ def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
29
+ with netCDF4.Dataset(self.file_name) as dataset:
30
+ self.dataset = dataset
31
+ self._fetch_attributes()
32
+ self._fetch_zenith_angle("tilt_angle", default=3.0)
33
+ self._fetch_range(reference="lower")
34
+ self._fetch_lidar_variables(calibration_factor)
35
+ self._fetch_time_and_date()
36
+ self.dataset = None
37
+
38
+ def _fetch_lidar_variables(self, calibration_factor: float | None = None) -> None:
39
+ if self.dataset is None:
40
+ msg = "No dataset found"
41
+ raise RuntimeError(msg)
42
+ beta_raw = self.dataset.variables["beta_att"][:]
43
+ if utils.is_all_masked(beta_raw):
44
+ msg = "All beta_raw values are masked. Check the input file(s)."
45
+ raise LidarDataError(msg)
46
+ if calibration_factor is None:
47
+ logging.warning("Using default calibration factor")
48
+ calibration_factor = 1
49
+ beta_raw *= calibration_factor
50
+ self.data["calibration_factor"] = float(calibration_factor)
51
+ self.data["beta_raw"] = beta_raw
52
+
53
+ def _fetch_attributes(self) -> None:
54
+ self.serial_number = getattr(self.dataset, "instrument_serial_number", None)