cloudnetpy 1.82.3__tar.gz → 1.83.1__tar.gz

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 (133) hide show
  1. {cloudnetpy-1.82.3/cloudnetpy.egg-info → cloudnetpy-1.83.1}/PKG-INFO +1 -1
  2. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuations/melting_attenuation.py +7 -6
  3. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/datasource.py +2 -2
  4. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/disdrometer/parsivel.py +1 -0
  5. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/nc_radar.py +2 -2
  6. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/weather_station.py +63 -29
  7. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/observation_products.py +1 -1
  8. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/plotting/plot_meta.py +24 -0
  9. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/plotting/plotting.py +68 -5
  10. cloudnetpy-1.83.1/cloudnetpy/products/classification.py +464 -0
  11. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/version.py +2 -2
  12. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1/cloudnetpy.egg-info}/PKG-INFO +1 -1
  13. cloudnetpy-1.82.3/cloudnetpy/products/classification.py +0 -228
  14. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/LICENSE +0 -0
  15. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/MANIFEST.in +0 -0
  16. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/README.md +0 -0
  17. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/__init__.py +0 -0
  18. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/__init__.py +0 -0
  19. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/atmos_utils.py +0 -0
  20. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuation.py +0 -0
  21. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  22. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  23. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  24. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  25. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/categorize.py +0 -0
  26. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/classify.py +0 -0
  27. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/containers.py +0 -0
  28. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/disdrometer.py +0 -0
  29. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/droplet.py +0 -0
  30. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/falling.py +0 -0
  31. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/freezing.py +0 -0
  32. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/insects.py +0 -0
  33. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/itu.py +0 -0
  34. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/lidar.py +0 -0
  35. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/melting.py +0 -0
  36. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/model.py +0 -0
  37. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/mwr.py +0 -0
  38. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/categorize/radar.py +0 -0
  39. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/cli.py +0 -0
  40. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/cloudnetarray.py +0 -0
  41. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/concat_lib.py +0 -0
  42. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/constants.py +0 -0
  43. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/exceptions.py +0 -0
  44. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/__init__.py +0 -0
  45. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/basta.py +0 -0
  46. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/bowtie.py +0 -0
  47. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/ceilo.py +0 -0
  48. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/ceilometer.py +0 -0
  49. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/cl61d.py +0 -0
  50. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
  51. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/copernicus.py +0 -0
  52. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
  53. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/disdrometer/common.py +0 -0
  54. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
  55. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/fd12p.py +0 -0
  56. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/galileo.py +0 -0
  57. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/hatpro.py +0 -0
  58. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/instruments.py +0 -0
  59. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/lufft.py +0 -0
  60. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/mira.py +0 -0
  61. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/mrr.py +0 -0
  62. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/nc_lidar.py +0 -0
  63. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/pollyxt.py +0 -0
  64. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/radiometrics.py +0 -0
  65. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  66. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/rpg.py +0 -0
  67. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/rpg_reader.py +0 -0
  68. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/toa5.py +0 -0
  69. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/instruments/vaisala.py +0 -0
  70. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/metadata.py +0 -0
  71. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/__init__.py +0 -0
  72. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/file_handler.py +0 -0
  73. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/metadata.py +0 -0
  74. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  75. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  76. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  77. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  78. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
  79. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  80. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
  81. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
  82. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  83. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  84. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/products/tools.py +0 -0
  85. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  86. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  87. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  88. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  89. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  90. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  91. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  92. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  93. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  94. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  95. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  96. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  97. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  98. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  99. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  100. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  101. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
  102. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
  103. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  104. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
  105. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  106. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  107. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  108. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
  109. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/model_evaluation/utils.py +0 -0
  110. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/output.py +0 -0
  111. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/plotting/__init__.py +0 -0
  112. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/__init__.py +0 -0
  113. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/der.py +0 -0
  114. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/drizzle.py +0 -0
  115. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/drizzle_error.py +0 -0
  116. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/drizzle_tools.py +0 -0
  117. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/epsilon.py +0 -0
  118. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/ier.py +0 -0
  119. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/iwc.py +0 -0
  120. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/lwc.py +0 -0
  121. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  122. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/mwr_tools.py +0 -0
  123. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/products/product_tools.py +0 -0
  124. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/py.typed +0 -0
  125. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy/utils.py +0 -0
  126. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy.egg-info/SOURCES.txt +0 -0
  127. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  128. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy.egg-info/entry_points.txt +0 -0
  129. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy.egg-info/requires.txt +0 -0
  130. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/cloudnetpy.egg-info/top_level.txt +0 -0
  131. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/docs/source/conf.py +0 -0
  132. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/pyproject.toml +0 -0
  133. {cloudnetpy-1.82.3 → cloudnetpy-1.83.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.82.3
3
+ Version: 1.83.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -13,14 +13,14 @@ def calc_melting_attenuation(
13
13
  data: Observations, classification: ClassificationResult
14
14
  ) -> Attenuation:
15
15
  shape = classification.category_bits.melting.shape
16
- is_rain = classification.is_rain
16
+ no_rain = classification.is_rain == 0
17
17
 
18
18
  affected_region = classification.category_bits.freezing.copy()
19
19
 
20
20
  if data.disdrometer is None:
21
- affected_region[~is_rain, :] = False
22
21
  above_melting = utils.ffill(classification.category_bits.melting)
23
22
  affected_region[~above_melting] = False
23
+ affected_region[no_rain, :] = False
24
24
  return Attenuation(
25
25
  amount=ma.masked_all(shape),
26
26
  error=ma.masked_all(shape),
@@ -29,22 +29,23 @@ def calc_melting_attenuation(
29
29
  )
30
30
 
31
31
  rainfall_rate = data.disdrometer.data["rainfall_rate"][:]
32
- rainfall_rate[is_rain == 0] = ma.masked
32
+ rainfall_rate = ma.where(no_rain, 0, rainfall_rate)
33
+
33
34
  frequency = data.radar.radar_frequency
34
35
 
35
36
  attenuation_array = _calc_melting_attenuation(rainfall_rate, frequency)
36
37
 
37
38
  amount = affected_region * utils.transpose(attenuation_array)
38
39
 
39
- affected_region[amount == 0] = False
40
+ no_attenuation = amount == 0
40
41
 
41
- amount[amount == 0] = ma.masked
42
+ affected_region[no_attenuation] = False
43
+ amount[no_attenuation] = ma.masked
42
44
 
43
45
  band = utils.get_wl_band(data.radar.radar_frequency)
44
46
  error_factor = {"Ka": 0.2, "W": 0.1}[band]
45
47
 
46
48
  error = amount * error_factor
47
- error[~affected_region] = ma.masked
48
49
 
49
50
  return Attenuation(
50
51
  amount=amount,
@@ -74,14 +74,14 @@ class DataSource:
74
74
  ndarray: The actual data.
75
75
 
76
76
  Raises:
77
- RuntimeError: The variable is not found.
77
+ KeyError: The variable is not found.
78
78
 
79
79
  """
80
80
  for arg in args:
81
81
  if arg in self.dataset.variables:
82
82
  return self.dataset.variables[arg][:]
83
83
  msg = f"Missing variable {args[0]} in the input file."
84
- raise RuntimeError(msg)
84
+ raise KeyError(msg)
85
85
 
86
86
  def append_data(
87
87
  self,
@@ -205,6 +205,7 @@ TOA5_HEADERS = {
205
205
  "precipitation": "_rain_accum",
206
206
  "rain accum [mm]": "_rain_accum",
207
207
  "weatherCodeWaWa": "synop_WaWa",
208
+ "wawa": "synop_WaWa",
208
209
  "weather_code_wawa": "synop_WaWa",
209
210
  "radarReflectivity": "radar_reflectivity",
210
211
  "radar_reflectivity": "radar_reflectivity",
@@ -41,7 +41,7 @@ class NcRadar(DataSource, CloudnetInstrument):
41
41
  name = keymap[key]
42
42
  try:
43
43
  array = self.getvar(key)
44
- except RuntimeError:
44
+ except KeyError:
45
45
  logging.warning("Can not find variable %s from the input file", key)
46
46
  continue
47
47
  array = np.array(array) if utils.isscalar(array) else array
@@ -158,7 +158,7 @@ class NcRadar(DataSource, CloudnetInstrument):
158
158
  self.data[key] = CloudnetArray(np.median(np.array(data)), key)
159
159
  if "NyquistVelocity" in self.data:
160
160
  del self.data["NyquistVelocity"]
161
- except RuntimeError:
161
+ except KeyError:
162
162
  logging.warning("Unable to find nyquist_velocity")
163
163
 
164
164
  def test_if_all_masked(self) -> None:
@@ -70,6 +70,8 @@ def ws2nc(
70
70
  ws = LimassolWS(weather_station_file, site_meta)
71
71
  elif site_meta["name"] == "L'Aquila":
72
72
  ws = LAquilaWS(weather_station_file, site_meta)
73
+ elif site_meta["name"] == "Maïdo Observatory":
74
+ ws = MaidoWS(weather_station_file, site_meta)
73
75
  else:
74
76
  msg = "Unsupported site"
75
77
  raise ValueError(msg)
@@ -162,6 +164,26 @@ class WS(CSVFile):
162
164
 
163
165
 
164
166
  class PalaiseauWS(WS):
167
+ expected_header_identifiers: tuple[str, ...] = (
168
+ "DateTime(yyyy-mm-ddThh:mm:ssZ)",
169
+ "Windspeed(m/s)",
170
+ "Winddirection(deg",
171
+ "Airtemperature",
172
+ "Relativehumidity(%)",
173
+ "Pressure(hPa)",
174
+ "Precipitationrate(mm/min)",
175
+ "precipitation",
176
+ )
177
+ keys: tuple[str, ...] = (
178
+ "wind_speed",
179
+ "wind_direction",
180
+ "air_temperature",
181
+ "relative_humidity",
182
+ "air_pressure",
183
+ "rainfall_rate",
184
+ "rainfall_amount",
185
+ )
186
+
165
187
  def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
166
188
  super().__init__(site_meta)
167
189
  self.filenames = filenames
@@ -175,16 +197,16 @@ class PalaiseauWS(WS):
175
197
  for row in data:
176
198
  if not (columns := row.split()):
177
199
  continue
178
- if row.startswith("#"):
179
- header_row = "".join(columns)
180
- if header_row not in header:
181
- header.append(header_row)
182
- else:
200
+ if re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", columns[0]):
183
201
  timestamp = datetime.datetime.strptime(
184
202
  columns[0], "%Y-%m-%dT%H:%M:%SZ"
185
203
  ).replace(tzinfo=datetime.timezone.utc)
186
204
  values.append([timestamp] + [float(x) for x in columns[1:]])
187
205
  timestamps.append(timestamp)
206
+ else:
207
+ header_row = "".join(columns)
208
+ if header_row not in header:
209
+ header.append(header_row)
188
210
 
189
211
  self._validate_header(header)
190
212
  return {"time": timestamps, "values": values}
@@ -204,16 +226,9 @@ class PalaiseauWS(WS):
204
226
  ]
205
227
 
206
228
  def add_data(self) -> None:
207
- keys = (
208
- "wind_speed",
209
- "wind_direction",
210
- "air_temperature",
211
- "relative_humidity",
212
- "air_pressure",
213
- "rainfall_rate",
214
- "rainfall_amount",
215
- )
216
- for ind, key in enumerate(keys):
229
+ for ind, key in enumerate(self.keys):
230
+ if key.startswith("_"):
231
+ continue
217
232
  array = [row[ind + 1] for row in self._data["values"]]
218
233
  array_masked = ma.masked_invalid(array)
219
234
  self.data[key] = CloudnetArray(array_masked, key)
@@ -223,27 +238,46 @@ class PalaiseauWS(WS):
223
238
  self.data["rainfall_amount"][:] / 1000
224
239
  ) # mm -> m
225
240
 
226
- @staticmethod
227
- def _validate_header(header: list[str]) -> None:
228
- expected_identifiers = [
229
- "DateTime(yyyy-mm-ddThh:mm:ssZ)",
230
- "Windspeed(m/s)",
231
- "Winddirection(deg",
232
- "Airtemperature",
233
- "Relativehumidity(%)",
234
- "Pressure(hPa)",
235
- "Precipitationrate(mm/min)",
236
- "precipitation",
237
- ]
241
+ def _validate_header(self, header: list[str]) -> None:
238
242
  column_titles = [row for row in header if "Col." in row]
239
243
  error_msg = "Unexpected weather station file format"
240
- if len(column_titles) != len(expected_identifiers):
244
+ if len(column_titles) != len(self.expected_header_identifiers):
241
245
  raise ValueError(error_msg)
242
- for title, identifier in zip(column_titles, expected_identifiers, strict=True):
246
+ for title, identifier in zip(
247
+ column_titles, self.expected_header_identifiers, strict=True
248
+ ):
243
249
  if identifier not in title:
244
250
  raise ValueError(error_msg)
245
251
 
246
252
 
253
+ class MaidoWS(PalaiseauWS):
254
+ expected_header_identifiers = (
255
+ "DateTimeyyyy-mm-ddThh:mm:ssZ",
256
+ "Winddirection-average",
257
+ "Windspeed-maximumvalue(m/s)",
258
+ "Windspeed-average(m/s)",
259
+ "Pressure-average(hPa)",
260
+ "Relativehumidity-maximumvalue(%)",
261
+ "Relativehumidity-average(%)",
262
+ "Airtemperature-minimumvalue",
263
+ "Airtemperature-average",
264
+ )
265
+
266
+ keys = (
267
+ "wind_direction",
268
+ "_wind_speed_max",
269
+ "wind_speed",
270
+ "air_pressure",
271
+ "_relative_humidity_max",
272
+ "relative_humidity",
273
+ "_air_temperature_min",
274
+ "air_temperature",
275
+ )
276
+
277
+ def convert_rainfall_amount(self) -> None:
278
+ pass
279
+
280
+
247
281
  class BucharestWS(PalaiseauWS):
248
282
  def convert_rainfall_rate(self) -> None:
249
283
  rainfall_rate = self.data["rainfall_rate"][:]
@@ -104,7 +104,7 @@ class ObservationManager(DataSource):
104
104
  """Check if rainrate in file."""
105
105
  try:
106
106
  self.getvar("rainrate")
107
- except RuntimeError:
107
+ except KeyError:
108
108
  return False
109
109
  return True
110
110
 
@@ -90,6 +90,12 @@ _CLABEL = {
90
90
  ("Clutter", _COLORS["shockred"]),
91
91
  ("_Lidar molecular scattering", _COLORS["pink"]),
92
92
  ),
93
+ "signal_source_status": (
94
+ ("Clear sky", _COLORS["white"]),
95
+ ("Radar & lidar", _COLORS["green"]),
96
+ ("Radar only", _COLORS["lightsteel"]),
97
+ ("Lidar only", _COLORS["yellow"]),
98
+ ),
93
99
  "ice_retrieval_status": (
94
100
  ("_No ice", _COLORS["white"]),
95
101
  ("Reliable", _COLORS["green"]),
@@ -124,6 +130,15 @@ _CLABEL = {
124
130
  ("Unfeasible", _COLORS["red"]),
125
131
  ("Surrounding ice", _COLORS["lightsteel"]),
126
132
  ),
133
+ "radar_attenuation_status": (
134
+ ("_Clear sky", _COLORS["white"]),
135
+ ("Negligible", _COLORS["green"]),
136
+ ("Minor", _COLORS["lightgreen"]),
137
+ ("Moderate", _COLORS["yellow"]),
138
+ ("Severe", _COLORS["red"]),
139
+ ("Unquantifiable", _COLORS["seaweed_roll"]),
140
+ ("Undetected", _COLORS["skyblue"]),
141
+ ),
127
142
  }
128
143
 
129
144
 
@@ -213,6 +228,9 @@ ATTRIBUTES = {
213
228
  ),
214
229
  },
215
230
  "fallback": {
231
+ "cloud_top_height_agl": PlotMeta(
232
+ moving_average=False,
233
+ ),
216
234
  "nubf": PlotMeta(plot_range=(0, 5)),
217
235
  "ze_sat": PlotMeta(
218
236
  plot_range=(-40, 15),
@@ -281,6 +299,9 @@ ATTRIBUTES = {
281
299
  "der_retrieval_status": PlotMeta(
282
300
  clabel=_CLABEL["der_retrieval_status"],
283
301
  ),
302
+ "radar_attenuation_status": PlotMeta(
303
+ clabel=_CLABEL["radar_attenuation_status"],
304
+ ),
284
305
  "mu": PlotMeta(
285
306
  plot_range=(0, 10),
286
307
  ),
@@ -554,6 +575,9 @@ ATTRIBUTES = {
554
575
  "detection_status": PlotMeta(
555
576
  clabel=_CLABEL["detection_status"],
556
577
  ),
578
+ "signal_source_status": PlotMeta(
579
+ clabel=_CLABEL["signal_source_status"],
580
+ ),
557
581
  "iwc": PlotMeta(
558
582
  plot_range=(1e-7, 1e-3),
559
583
  log_scale=True,
@@ -28,6 +28,7 @@ from cloudnetpy.categorize.atmos_utils import calc_altitude
28
28
  from cloudnetpy.exceptions import PlottingError
29
29
  from cloudnetpy.instruments.ceilometer import calc_sigma_units
30
30
  from cloudnetpy.plotting.plot_meta import ATTRIBUTES, PlotMeta
31
+ from cloudnetpy.products.classification import TopStatus
31
32
 
32
33
  EARTHCARE_MAX_X = 517.84
33
34
 
@@ -375,6 +376,7 @@ class Plot:
375
376
  "air_temperature": (add, -273.15, "\u00b0C"),
376
377
  "r_accum_RT": (multiply, 1000, "mm"),
377
378
  "r_accum_NRT": (multiply, 1000, "mm"),
379
+ "cloud_top_height_agl": (multiply, con.M_TO_KM, "Height (km AGL)"),
378
380
  }
379
381
  conversion_method, conversion, units = units_conversion.get(
380
382
  self.sub_plot.variable.name, (multiply, 1, None)
@@ -467,6 +469,12 @@ class Plot:
467
469
  self._data = data_new
468
470
  figure_data.time_including_gaps = time_new
469
471
 
472
+ def _read_cloud_top_flags(
473
+ self, figure_data: FigureData, flag_value: int | tuple[int, ...]
474
+ ) -> ndarray:
475
+ status = figure_data.file.variables["cloud_top_height_status"][:]
476
+ return np.isin(status, flag_value)
477
+
470
478
  def _read_flagged_data(self, figure_data: FigureData) -> ndarray:
471
479
  flag_names = [
472
480
  f"{self.sub_plot.variable.name}_quality_flag",
@@ -503,6 +511,32 @@ class Plot2D(Plot):
503
511
  if figure_data.is_mwrpy_product():
504
512
  self._fill_flagged_data(figure_data)
505
513
 
514
+ if figure_data.variables[0].name == "signal_source_status":
515
+ self._indicate_rainy_profiles(figure_data)
516
+
517
+ def _indicate_rainy_profiles(self, figure_data: FigureData) -> None:
518
+ if "rain_detected" not in figure_data.file.variables:
519
+ return
520
+ rain = figure_data.file.variables["rain_detected"][:]
521
+ is_rain: ma.MaskedArray = ma.masked_array(np.zeros_like(rain), mask=(rain == 0))
522
+ if is_rain.mask.all():
523
+ return
524
+ self._ax.plot(
525
+ figure_data.time,
526
+ is_rain,
527
+ color="red",
528
+ marker="|",
529
+ linestyle="None",
530
+ markersize=10,
531
+ zorder=-999,
532
+ label="Rain",
533
+ )
534
+ self._ax.legend(
535
+ markerscale=0.75,
536
+ numpoints=1,
537
+ frameon=False,
538
+ )
539
+
506
540
  def _fill_flagged_data(self, figure_data: FigureData) -> None:
507
541
  flags = self._read_flagged_data(figure_data)
508
542
  batches = find_batches_of_ones(flags)
@@ -672,11 +706,35 @@ class Plot1D(Plot):
672
706
  self.sub_plot.set_yax(ylabel=units, y_limits=self._get_y_limits())
673
707
  pos = self._ax.get_position()
674
708
  self._ax.set_position((pos.x0, pos.y0, pos.width * 0.965, pos.height))
709
+ self._plot_flags(figure_data)
710
+
711
+ def _plot_flags(self, figure_data: FigureData) -> None:
675
712
  if figure_data.is_mwrpy_product():
676
713
  flags = self._read_flagged_data(figure_data)
677
714
  if np.any(flags):
678
715
  self._plot_flag_data(figure_data.time[flags], self._data_orig[flags])
679
716
  self._add_legend()
717
+ if (
718
+ figure_data.variables[0].name == "cloud_top_height_agl"
719
+ and "cloud_top_height_status" in figure_data.file.variables
720
+ ):
721
+ legend: tuple = ()
722
+ flag_value = (TopStatus.MODERATE_ATT, TopStatus.UNCORR_ATT)
723
+ flags = self._read_cloud_top_flags(figure_data, flag_value)
724
+ if np.any(flags):
725
+ self._plot_flag_data(
726
+ figure_data.time[flags], self._data_orig[flags], color="orange"
727
+ )
728
+ legend += ("Suspicious",)
729
+ flag_value = (TopStatus.SEVERE_ATT, TopStatus.ABOVE_RANGE)
730
+ flags = self._read_cloud_top_flags(figure_data, flag_value)
731
+ if np.any(flags):
732
+ self._plot_flag_data(
733
+ figure_data.time[flags], self._data_orig[flags], color="red"
734
+ )
735
+ legend += ("Unreliable",)
736
+ if legend:
737
+ self._add_legend(name=legend)
680
738
 
681
739
  def plot_tb(self, figure_data: FigureData, freq_ind: int) -> None:
682
740
  if len(self._data.shape) != 2 or freq_ind >= self._data.shape[1]:
@@ -728,20 +786,22 @@ class Plot1D(Plot):
728
786
  },
729
787
  )
730
788
 
731
- def _plot_flag_data(self, time: ndarray, values: ndarray) -> None:
789
+ def _plot_flag_data(
790
+ self, time: ndarray, values: ndarray, color: str = "salmon"
791
+ ) -> None:
732
792
  self._ax.plot(
733
793
  time,
734
794
  values,
735
- color="salmon",
795
+ color=color,
736
796
  marker=".",
737
797
  lw=0,
738
798
  markersize=3,
739
799
  zorder=_get_zorder("flags"),
740
800
  )
741
801
 
742
- def _add_legend(self) -> None:
802
+ def _add_legend(self, name: str | tuple = ("Flagged data",)) -> None:
743
803
  self._ax.legend(
744
- ["Flagged data"],
804
+ name,
745
805
  markerscale=3,
746
806
  numpoints=1,
747
807
  frameon=False,
@@ -772,7 +832,10 @@ class Plot1D(Plot):
772
832
  custom_options = {
773
833
  "tb": {
774
834
  "color": "lightblue",
775
- }
835
+ },
836
+ "cloud_top_height_agl": {
837
+ "color": "steelblue",
838
+ },
776
839
  }
777
840
 
778
841
  variable_name = self.sub_plot.variable.name