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,47 +1,62 @@
1
1
  """Module for reading and processing Vaisala / Lufft ceilometers."""
2
+
3
+ import datetime
4
+ import logging
5
+ import os.path
2
6
  from itertools import islice
7
+ from os import PathLike
8
+ from uuid import UUID
3
9
 
4
10
  import netCDF4
11
+ import numpy as np
12
+ from ceilopyter import read_ct25k
13
+ from numpy import ma
5
14
 
6
15
  from cloudnetpy import output
7
16
  from cloudnetpy.instruments.cl61d import Cl61d
17
+ from cloudnetpy.instruments.da10 import Da10
8
18
  from cloudnetpy.instruments.lufft import LufftCeilo
9
- from cloudnetpy.instruments.vaisala import ClCeilo, Ct25k
10
- from cloudnetpy.metadata import MetaData
19
+ from cloudnetpy.instruments.vaisala import ClCeilo, Cs135, Ct25k
20
+ from cloudnetpy.metadata import COMMON_ATTRIBUTES, MetaData
21
+ from cloudnetpy.utils import get_uuid
22
+
23
+ CeiloObject = ClCeilo | Ct25k | LufftCeilo | Cl61d | Cs135 | Da10
11
24
 
12
25
 
13
26
  def ceilo2nc(
14
- full_path: str,
15
- output_file: str,
27
+ full_path: str | PathLike,
28
+ output_file: str | PathLike,
16
29
  site_meta: dict,
17
- uuid: str | None = None,
18
- date: str | None = None,
19
- ) -> str:
20
- """Converts Vaisala / Lufft ceilometer data into Cloudnet Level 1b netCDF file.
30
+ uuid: str | UUID | None = None,
31
+ date: str | datetime.date | None = None,
32
+ ) -> UUID:
33
+ """Converts Vaisala, Lufft and Campbell Scientific ceilometer data into
34
+ Cloudnet Level 1b netCDF file.
21
35
 
22
- This function reads raw Vaisala (CT25k, CL31, CL51, CL61-D) and Lufft
23
- (CHM15k, CHM15kx) ceilometer files and writes the data into netCDF file.
24
- Three variants of the attenuated backscatter are saved in the file:
36
+ This function reads raw Vaisala (CT25k, CL31, CL51, CL61, DA10), Lufft
37
+ (CHM 15k, CHM 15k-x) and Campbell Scientific (CS135) ceilometer files and writes
38
+ the data into netCDF file. Three variants of the backscatter are saved:
25
39
 
26
40
  1. Raw backscatter, `beta_raw`
27
41
  2. Signal-to-noise screened backscatter, `beta`
28
42
  3. SNR-screened backscatter with smoothed weak background, `beta_smooth`
29
43
 
30
- With CL61-D `beta_raw` is not saved due to large file size. Instead, two additional
31
- depolarisation parameters are saved:
44
+ With CL61 two additional depolarisation parameters are saved:
32
45
 
33
46
  1. Signal-to-noise screened depolarisation, `depolarisation`
34
47
  2. SNR-screened depolarisation with smoothed weak background,
35
48
  `depolarisation_smooth`
36
49
 
50
+ With most instruments, screened backscatter is screened using beta_smooth
51
+ mask to improve detection of weak aerosol layers and supercooled liquid clouds.
52
+
37
53
  Args:
38
- full_path: Ceilometer file name. For Vaisala it is a text file, for CHM15k(x)
39
- it is a netCDF file.
54
+ full_path: Ceilometer file name.
40
55
  output_file: Output file name, e.g. 'ceilo.nc'.
41
56
  site_meta: Dictionary containing information about the site and instrument.
42
57
  Required key value pairs are `name` and `altitude` (metres above mean
43
58
  sea level). Also, 'calibration_factor' is recommended because the default
44
- value is probably incorrect. If the backround noise is *not*
59
+ value is probably incorrect. If the background noise is *not*
45
60
  range-corrected, you must define: {'range_corrected': False}.
46
61
  You can also explicitly set the instrument model with
47
62
  e.g. {'model': 'cl61d'}.
@@ -63,38 +78,101 @@ def ceilo2nc(
63
78
  >>> ceilo2nc('chm15k_raw.nc', 'chm15k.nc', site_meta)
64
79
 
65
80
  """
81
+ if isinstance(date, str):
82
+ date = datetime.date.fromisoformat(date)
83
+ uuid = get_uuid(uuid)
66
84
  snr_limit = 5
67
85
  ceilo_obj = _initialize_ceilo(full_path, site_meta, date)
68
- calibration_factor = site_meta.get("calibration_factor", None)
86
+ calibration_factor = site_meta.get("calibration_factor")
69
87
  range_corrected = site_meta.get("range_corrected", True)
70
- ceilo_obj.read_ceilometer_file(calibration_factor)
71
- ceilo_obj.data["beta"] = ceilo_obj.calc_screened_product(
72
- ceilo_obj.data["beta_raw"], snr_limit, range_corrected
73
- )
74
- ceilo_obj.data["beta_smooth"] = ceilo_obj.calc_beta_smooth(
75
- ceilo_obj.data["beta"], snr_limit, range_corrected
76
- )
77
- assert ceilo_obj.instrument is not None and ceilo_obj.instrument.model is not None
78
- if "cl61" in ceilo_obj.instrument.model.lower():
79
- ceilo_obj.data["depolarisation"].mask = ceilo_obj.data["beta"].mask
80
- ceilo_obj.remove_raw_data()
88
+ if range_corrected is False:
89
+ logging.warning("Raw data not range-corrected.")
90
+ if isinstance(ceilo_obj, Ct25k):
91
+ c_obj = read_ct25k(full_path, calibration_factor, range_corrected)
92
+ ceilo_obj.data["beta"] = c_obj.beta
93
+ ceilo_obj.data["beta_raw"] = c_obj.beta_raw
94
+ ceilo_obj.data["time"] = c_obj.time
95
+ ceilo_obj.data["range"] = c_obj.range
96
+ if c_obj.zenith_angle is not None:
97
+ ceilo_obj.data["zenith_angle"] = np.median(c_obj.zenith_angle)
98
+ ceilo_obj.data["calibration_factor"] = c_obj.calibration_factor
99
+ ceilo_obj.sort_time()
100
+ ceilo_obj.screen_date()
101
+ ceilo_obj.convert_to_fraction_hour()
102
+ else:
103
+ ceilo_obj.read_ceilometer_file(calibration_factor)
104
+ ceilo_obj.check_beta_raw_shape()
105
+ n_negatives = _get_n_negatives(ceilo_obj)
106
+ ceilo_obj.data["beta"] = ceilo_obj.calc_screened_product(
107
+ ceilo_obj.data["beta_raw"],
108
+ snr_limit,
109
+ range_corrected=range_corrected,
110
+ n_negatives=n_negatives,
111
+ )
112
+ ceilo_obj.data["beta_smooth"] = ceilo_obj.calc_beta_smooth(
113
+ ceilo_obj.data["beta"],
114
+ snr_limit,
115
+ range_corrected=range_corrected,
116
+ n_negatives=n_negatives,
117
+ )
118
+ if ceilo_obj.instrument is None or ceilo_obj.instrument.model is None:
119
+ msg = "Failed to read ceilometer model"
120
+ raise RuntimeError(msg)
121
+ if (
122
+ any(
123
+ model in ceilo_obj.instrument.model.lower()
124
+ for model in ("cl61", "chm15k", "chm15kx", "cl51", "cl31", "da10")
125
+ )
126
+ and range_corrected
127
+ ):
128
+ mask = ceilo_obj.data["beta_smooth"].mask
129
+ ceilo_obj.data["beta"] = ma.masked_where(mask, ceilo_obj.data["beta_raw"])
130
+ ceilo_obj.data["beta"][ceilo_obj.data["beta"] <= 0] = ma.masked
131
+ if "depolarisation" in ceilo_obj.data:
132
+ ceilo_obj.data["depolarisation"].mask = ceilo_obj.data["beta"].mask
81
133
  ceilo_obj.screen_depol()
134
+ ceilo_obj.screen_invalid_values()
135
+ ceilo_obj.screen_sunbeam()
82
136
  ceilo_obj.prepare_data()
83
137
  ceilo_obj.data_to_cloudnet_arrays()
138
+ ceilo_obj.add_site_geolocation()
84
139
  attributes = output.add_time_attribute(ATTRIBUTES, ceilo_obj.date)
85
140
  output.update_attributes(ceilo_obj.data, attributes)
86
141
  for key in ("beta", "beta_smooth"):
87
142
  ceilo_obj.add_snr_info(key, snr_limit)
88
- uuid = output.save_level1b(ceilo_obj, output_file, uuid)
143
+ output.save_level1b(ceilo_obj, output_file, uuid)
89
144
  return uuid
90
145
 
91
146
 
147
+ def _get_n_negatives(ceilo_obj: CeiloObject) -> int:
148
+ is_old_chm_version = (
149
+ hasattr(ceilo_obj, "is_old_version") and ceilo_obj.is_old_version
150
+ )
151
+ is_old_vaisala_model = ceilo_obj.instrument is not None and getattr(
152
+ ceilo_obj.instrument, "model", ""
153
+ ).lower() in ("ct25k", "cl31")
154
+ if is_old_chm_version or is_old_vaisala_model:
155
+ return 20
156
+ return 5
157
+
158
+
92
159
  def _initialize_ceilo(
93
- full_path: str, site_meta: dict, date: str | None = None
94
- ) -> ClCeilo | Ct25k | LufftCeilo | Cl61d:
160
+ full_path: str | PathLike,
161
+ site_meta: dict,
162
+ date: datetime.date | None = None,
163
+ ) -> CeiloObject:
95
164
  if "model" in site_meta:
96
- if site_meta["model"] not in ("cl31", "cl51", "cl61d", "ct25k", "chm15k"):
97
- raise ValueError(f"Invalid ceilometer model: {site_meta['model']}")
165
+ if site_meta["model"] not in (
166
+ "cl31",
167
+ "cl51",
168
+ "cl61d",
169
+ "ct25k",
170
+ "chm15k",
171
+ "cs135",
172
+ "da10",
173
+ ):
174
+ msg = f"Invalid ceilometer model: {site_meta['model']}"
175
+ raise ValueError(msg)
98
176
  if site_meta["model"] in ("cl31", "cl51"):
99
177
  model = "cl31_or_cl51"
100
178
  else:
@@ -107,25 +185,37 @@ def _initialize_ceilo(
107
185
  return Ct25k(full_path, site_meta, date)
108
186
  if model == "cl61d":
109
187
  return Cl61d(full_path, site_meta, date)
188
+ if model == "da10":
189
+ return Da10(full_path, site_meta, date)
190
+ if model == "cs135":
191
+ return Cs135(full_path, site_meta, date)
110
192
  return LufftCeilo(full_path, site_meta, date)
111
193
 
112
194
 
113
- def _find_ceilo_model(full_path: str) -> str:
195
+ def _find_ceilo_model(full_path: str | PathLike) -> str:
196
+ model = None
114
197
  try:
115
198
  with netCDF4.Dataset(full_path) as nc:
116
199
  title = nc.title
117
200
  for identifier in ["cl61d", "cl61-d"]:
118
- if identifier in title.lower() or identifier in full_path.lower():
119
- return "cl61d"
120
- return "chm15k"
201
+ if (
202
+ identifier in title.lower()
203
+ or identifier in os.path.basename(full_path).lower()
204
+ ):
205
+ model = "cl61d"
206
+ if model is None:
207
+ model = "chm15k"
121
208
  except OSError:
122
209
  with open(full_path, "rb") as file:
123
210
  for line in islice(file, 100):
124
211
  if line.startswith(b"\x01CL"):
125
- return "cl31_or_cl51"
126
- if line.startswith(b"\x01CT"):
127
- return "ct25k"
128
- raise RuntimeError("Error: Unknown ceilo model.")
212
+ model = "cl31_or_cl51"
213
+ elif line.startswith(b"\x01CT"):
214
+ model = "ct25k"
215
+ if model is None:
216
+ msg = "Unable to determine ceilometer model"
217
+ raise RuntimeError(msg)
218
+ return model
129
219
 
130
220
 
131
221
  ATTRIBUTES = {
@@ -133,86 +223,19 @@ ATTRIBUTES = {
133
223
  long_name="Lidar volume linear depolarisation ratio",
134
224
  units="1",
135
225
  comment="SNR-screened lidar volume linear depolarisation ratio at 910.55 nm.",
226
+ dimensions=("time", "range"),
136
227
  ),
137
- "scale": MetaData(long_name="Scale", units="%", comment="100 (%) is normal."),
138
- "software_level": MetaData(
139
- long_name="Software level ID",
140
- units="1",
141
- ),
142
- "laser_temperature": MetaData(
143
- long_name="Laser temperature",
144
- units="C",
145
- ),
146
- "window_transmission": MetaData(
147
- long_name="Window transmission estimate",
148
- units="%",
149
- ),
150
- "laser_energy": MetaData(
151
- long_name="Laser pulse energy",
152
- units="%",
153
- ),
154
- "background_light": MetaData(
155
- long_name="Background light",
156
- units="mV",
157
- comment="Measured at internal ADC input.",
158
- ),
159
- "backscatter_sum": MetaData(
160
- long_name="Sum of detected and normalized backscatter",
161
- units="sr-1",
162
- comment="Multiplied by scaling factor times 1e4.",
163
- ),
164
- "range_resolution": MetaData(
165
- long_name="Range resolution",
166
- units="m",
167
- ),
168
- "number_of_gates": MetaData(
169
- long_name="Number of range gates in profile",
170
- units="1",
171
- ),
172
- "unit_id": MetaData(
173
- long_name="Ceilometer unit number",
174
- units="1",
175
- ),
176
- "message_number": MetaData(
177
- long_name="Message number",
178
- units="1",
179
- ),
180
- "message_subclass": MetaData(
181
- long_name="Message subclass number",
182
- units="1",
183
- ),
184
- "detection_status": MetaData(
185
- long_name="Detection status",
186
- units="1",
187
- comment="From the internal software of the instrument.",
188
- ),
189
- "warning": MetaData(
190
- long_name="Warning and Alarm flag",
191
- units="1",
192
- definition=(
193
- "\n"
194
- "Value 0: Self-check OK\n"
195
- "Value W: At least one warning on\n"
196
- "Value A: At least one error active."
197
- ),
198
- ),
199
- "warning_flags": MetaData(
200
- long_name="Warning flags",
228
+ "depolarisation_raw": MetaData(
229
+ long_name="Lidar volume linear depolarisation ratio",
201
230
  units="1",
202
- ),
203
- "receiver_sensitivity": MetaData(
204
- long_name="Receiver sensitivity",
205
- units="%",
206
- comment="Expressed as % of nominal factory setting.",
207
- ),
208
- "window_contamination": MetaData(
209
- long_name="Window contamination",
210
- units="mV",
211
- comment="Measured at internal ADC input.",
231
+ comment="SNR-screened lidar volume linear depolarisation ratio at 910.55 nm.",
232
+ dimensions=("time", "range"),
212
233
  ),
213
234
  "calibration_factor": MetaData(
214
235
  long_name="Attenuated backscatter calibration factor",
215
236
  units="1",
216
237
  comment="Calibration factor applied.",
238
+ dimensions=None,
217
239
  ),
240
+ "zenith_angle": COMMON_ATTRIBUTES["zenith_angle"]._replace(dimensions=None),
218
241
  }