cloudnetpy 1.72.3__tar.gz → 1.73.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 (131) hide show
  1. {cloudnetpy-1.72.3/cloudnetpy.egg-info → cloudnetpy-1.73.1}/PKG-INFO +1 -1
  2. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/disdrometer.py +10 -10
  3. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/itu.py +5 -5
  4. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/melting.py +1 -1
  5. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/__init__.py +1 -0
  6. cloudnetpy-1.73.1/cloudnetpy/instruments/bowtie.py +95 -0
  7. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/ceilo.py +1 -0
  8. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/ceilometer.py +3 -3
  9. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/cloudnet_instrument.py +12 -73
  10. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/hatpro.py +1 -15
  11. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/pollyxt.py +1 -0
  12. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/rpg.py +0 -8
  13. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/metadata.py +8 -0
  14. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/plotting/plotting.py +1 -1
  15. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/plotting/plotting.py +1 -1
  16. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/utils.py +99 -0
  17. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/version.py +2 -2
  18. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1/cloudnetpy.egg-info}/PKG-INFO +1 -1
  19. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy.egg-info/SOURCES.txt +1 -0
  20. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/LICENSE +0 -0
  21. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/MANIFEST.in +0 -0
  22. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/README.md +0 -0
  23. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/__init__.py +0 -0
  24. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/__init__.py +0 -0
  25. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/atmos_utils.py +0 -0
  26. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuation.py +0 -0
  27. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  28. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  29. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  30. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
  31. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  32. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/categorize.py +0 -0
  33. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/classify.py +0 -0
  34. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/containers.py +0 -0
  35. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/droplet.py +0 -0
  36. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/falling.py +0 -0
  37. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/freezing.py +0 -0
  38. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/insects.py +0 -0
  39. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/lidar.py +0 -0
  40. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/model.py +0 -0
  41. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/mwr.py +0 -0
  42. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/categorize/radar.py +0 -0
  43. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/cli.py +0 -0
  44. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/cloudnetarray.py +0 -0
  45. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/concat_lib.py +0 -0
  46. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/constants.py +0 -0
  47. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/datasource.py +0 -0
  48. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/exceptions.py +0 -0
  49. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/basta.py +0 -0
  50. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/cl61d.py +0 -0
  51. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/copernicus.py +0 -0
  52. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
  53. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/disdrometer/common.py +0 -0
  54. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
  55. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
  56. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/galileo.py +0 -0
  57. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/instruments.py +0 -0
  58. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/lufft.py +0 -0
  59. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/mira.py +0 -0
  60. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/mrr.py +0 -0
  61. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/nc_lidar.py +0 -0
  62. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/nc_radar.py +0 -0
  63. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/radiometrics.py +0 -0
  64. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  65. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/rpg_reader.py +0 -0
  66. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/toa5.py +0 -0
  67. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/vaisala.py +0 -0
  68. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/instruments/weather_station.py +0 -0
  69. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/__init__.py +0 -0
  70. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/file_handler.py +0 -0
  71. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/metadata.py +0 -0
  72. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  73. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  74. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  75. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  76. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  77. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
  78. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
  79. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  80. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
  81. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  82. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/products/tools.py +0 -0
  83. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  84. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  85. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  86. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  87. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  88. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  89. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  90. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  91. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  92. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  93. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  94. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  95. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  96. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  97. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  98. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  99. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
  100. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
  101. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  102. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
  103. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  104. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  105. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  106. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
  107. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/model_evaluation/utils.py +0 -0
  108. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/output.py +0 -0
  109. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/plotting/__init__.py +0 -0
  110. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/plotting/plot_meta.py +0 -0
  111. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/__init__.py +0 -0
  112. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/classification.py +0 -0
  113. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/der.py +0 -0
  114. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/drizzle.py +0 -0
  115. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/drizzle_error.py +0 -0
  116. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/drizzle_tools.py +0 -0
  117. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/epsilon.py +0 -0
  118. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/ier.py +0 -0
  119. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/iwc.py +0 -0
  120. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/lwc.py +0 -0
  121. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  122. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/mwr_tools.py +0 -0
  123. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/products/product_tools.py +0 -0
  124. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy/py.typed +0 -0
  125. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  126. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy.egg-info/entry_points.txt +0 -0
  127. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy.egg-info/requires.txt +0 -0
  128. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/cloudnetpy.egg-info/top_level.txt +0 -0
  129. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/docs/source/conf.py +0 -0
  130. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/pyproject.toml +0 -0
  131. {cloudnetpy-1.72.3 → cloudnetpy-1.73.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.72.3
3
+ Version: 1.73.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -37,18 +37,18 @@ class Disdrometer(DataSource):
37
37
  self.append_data(self.dataset.variables[key][:], key)
38
38
 
39
39
  def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray:
40
- if ma.getmask(y) is ma.nomask:
41
- non_masked_indices = np.arange(len(y))
42
- elif y.mask.all():
43
- return ma.masked_all(x_new.shape)
44
- else:
45
- non_masked_indices = np.where(~y.mask)[0]
46
- non_masked_values = y[non_masked_indices]
47
- non_masked_time = self.time[non_masked_indices]
48
- fun = interp1d(non_masked_time, non_masked_values, fill_value="extrapolate")
40
+ time = self.time
41
+ mask = ma.getmask(y)
42
+ if mask is not ma.nomask:
43
+ if np.all(mask):
44
+ return ma.masked_all(x_new.shape)
45
+ not_masked = ~mask
46
+ y = y[not_masked]
47
+ time = time[not_masked]
48
+ fun = interp1d(time, y, fill_value="extrapolate")
49
49
  interpolated_array = ma.array(fun(x_new))
50
50
  max_time = 1 / 60 # min -> fraction hour
51
- mask_ind = get_gap_ind(non_masked_time, x_new, max_time)
51
+ mask_ind = get_gap_ind(time, x_new, max_time)
52
52
 
53
53
  if len(mask_ind) > 0:
54
54
  msg = f"Unable to interpolate disdrometer for {len(mask_ind)} time steps"
@@ -5,7 +5,7 @@ import cloudnetpy.constants as con
5
5
 
6
6
 
7
7
  def calc_liquid_specific_attenuation(
8
- temperature: npt.NDArray, frequency: float
8
+ temperature: npt.NDArray, frequency: float | np.floating
9
9
  ) -> npt.NDArray:
10
10
  """Calculate cloud liquid water specific attenuation coefficient for
11
11
  frequency up to 200 GHz.
@@ -43,7 +43,7 @@ def calc_gas_specific_attenuation(
43
43
  pressure: npt.NDArray,
44
44
  vapor_pressure: npt.NDArray,
45
45
  temperature: npt.NDArray,
46
- frequency: float,
46
+ frequency: float | np.floating,
47
47
  ) -> npt.NDArray:
48
48
  """Calculate specific attenuation due to dry air and water vapor for
49
49
  frequency up to 1000 GHz.
@@ -72,7 +72,7 @@ def calc_gas_specific_attenuation(
72
72
 
73
73
 
74
74
  def _calc_line_shape(
75
- frequency: float,
75
+ frequency: float | np.floating,
76
76
  center: npt.NDArray,
77
77
  width: npt.NDArray,
78
78
  correction: npt.NDArray | float,
@@ -92,7 +92,7 @@ def _calc_line_shape(
92
92
  def _calc_oxygen_refractivity(
93
93
  dry_pressure: npt.NDArray,
94
94
  vapor_pressure: npt.NDArray,
95
- frequency: float,
95
+ frequency: float | np.floating,
96
96
  theta: npt.NDArray,
97
97
  ) -> npt.NDArray:
98
98
  f0, a1, a2, a3, a4, a5, a6 = OXYGEN_TABLE[:, :, np.newaxis, np.newaxis]
@@ -119,7 +119,7 @@ def _calc_oxygen_refractivity(
119
119
  def _calc_vapor_refractivity(
120
120
  dry_pressure: npt.NDArray,
121
121
  vapor_pressure: npt.NDArray,
122
- frequency: float,
122
+ frequency: float | np.floating,
123
123
  theta: npt.NDArray,
124
124
  ) -> npt.NDArray:
125
125
  f0, b1, b2, b3, b4, b5, b6 = VAPOR_TABLE[:, :, np.newaxis, np.newaxis]
@@ -166,7 +166,7 @@ def _basetop(dprof: np.ndarray, pind: int) -> tuple[int, int]:
166
166
  def _get_temp_indices(t_prof: np.ndarray, t_range: tuple) -> np.ndarray:
167
167
  """Finds indices of temperature profile covering the given range."""
168
168
  ind = np.where((t_prof > min(t_range) + T0) & (t_prof < max(t_range) + T0))[0]
169
- return np.array([]) if len(ind) == 0 else np.arange(min(ind), max(ind) + 1)
169
+ return np.array([]) if len(ind) == 0 else np.arange(np.min(ind), np.max(ind) + 1)
170
170
 
171
171
 
172
172
  def _find_model_temperature_range(model_type: str) -> tuple[float, float]:
@@ -1,4 +1,5 @@
1
1
  from .basta import basta2nc
2
+ from .bowtie import bowtie2nc
2
3
  from .ceilo import ceilo2nc
3
4
  from .copernicus import copernicus2nc
4
5
  from .disdrometer import parsivel2nc, thies2nc
@@ -0,0 +1,95 @@
1
+ from os import PathLike
2
+
3
+ from cloudnetpy import output
4
+ from cloudnetpy.constants import G_TO_KG, MM_H_TO_M_S
5
+ from cloudnetpy.exceptions import ValidTimeStampError
6
+ from cloudnetpy.instruments.instruments import FMCW94
7
+ from cloudnetpy.instruments.nc_radar import NcRadar
8
+ from cloudnetpy.metadata import MetaData
9
+
10
+
11
+ def bowtie2nc(
12
+ bowtie_file: str | PathLike,
13
+ output_file: str,
14
+ site_meta: dict,
15
+ uuid: str | None = None,
16
+ date: str | None = None,
17
+ ) -> str:
18
+ """Converts data from 'BOW-TIE' campaign cloud radar on RV-Meteor into
19
+ Cloudnet Level 1b netCDF file.
20
+
21
+ Args:
22
+ bowtie_file: Input filename.
23
+ output_file: Output filename.
24
+ site_meta: Dictionary containing information about the site. Required key
25
+ value pair is `name`. Optional are `latitude`, `longitude`, `altitude`.
26
+ uuid: Set specific UUID for the file.
27
+ date: Expected date as YYYY-MM-DD of all profiles in the file.
28
+
29
+ Returns:
30
+ UUID of the generated file.
31
+
32
+ Raises:
33
+ ValidTimeStampError: No valid timestamps found.
34
+
35
+ """
36
+ keymap = {
37
+ "Zh": "Zh",
38
+ "v": "v",
39
+ "width": "width",
40
+ "ldr": "ldr",
41
+ "kurt": "kurtosis",
42
+ "Skew": "skewness",
43
+ "SNR": "SNR",
44
+ "time": "time",
45
+ "range": "range",
46
+ "lwp": "lwp",
47
+ "SurfRelHum": "relative_humidity",
48
+ "rain": "rainfall_rate",
49
+ "Nyquist_velocity": "nyquist_velocity",
50
+ "range_offsets": "chirp_start_indices",
51
+ }
52
+
53
+ with Bowtie(bowtie_file, site_meta) as bowtie:
54
+ bowtie.init_data(keymap)
55
+ bowtie.add_time_and_range()
56
+ if date is not None:
57
+ bowtie.check_date(date)
58
+ bowtie.add_radar_specific_variables()
59
+ bowtie.add_site_geolocation()
60
+ bowtie.add_height()
61
+ bowtie.convert_units()
62
+ bowtie.test_if_all_masked()
63
+ attributes = output.add_time_attribute(ATTRIBUTES, bowtie.date)
64
+ output.update_attributes(bowtie.data, attributes)
65
+ return output.save_level1b(bowtie, output_file, uuid)
66
+
67
+
68
+ class Bowtie(NcRadar):
69
+ def __init__(self, full_path: str | PathLike, site_meta: dict):
70
+ super().__init__(full_path, site_meta)
71
+ self.instrument = FMCW94
72
+ self.date = self.get_date()
73
+
74
+ def convert_units(self):
75
+ self.data["lwp"].data *= G_TO_KG
76
+ self.data["rainfall_rate"].data *= MM_H_TO_M_S
77
+ self.data["relative_humidity"].data /= 100
78
+
79
+ def check_date(self, date: str):
80
+ if "-".join(self.date) != date:
81
+ raise ValidTimeStampError
82
+
83
+
84
+ ATTRIBUTES: dict = {
85
+ "v": MetaData(
86
+ long_name="Doppler velocity",
87
+ units="m s-1",
88
+ comment=(
89
+ "This parameter is the radial component of the velocity, with positive\n"
90
+ "velocities are away from the radar. It was corrected for the heave\n"
91
+ "motion of the ship. A rolling average over 3 time steps has been\n"
92
+ "applied to it."
93
+ ),
94
+ ),
95
+ }
@@ -105,6 +105,7 @@ def ceilo2nc(
105
105
  ceilo_obj.screen_invalid_values()
106
106
  ceilo_obj.prepare_data()
107
107
  ceilo_obj.data_to_cloudnet_arrays()
108
+ ceilo_obj.add_site_geolocation()
108
109
  attributes = output.add_time_attribute(ATTRIBUTES, ceilo_obj.date)
109
110
  output.update_attributes(ceilo_obj.data, attributes)
110
111
  for key in ("beta", "beta_smooth"):
@@ -93,9 +93,6 @@ class Ceilometer:
93
93
  msg = "Instrument wavelength not defined"
94
94
  raise RuntimeError(msg)
95
95
  self.data["wavelength"] = float(self.instrument.wavelength)
96
- for key in ("latitude", "longitude", "altitude"):
97
- if key in self.site_meta:
98
- self.data[key] = float(self.site_meta[key])
99
96
 
100
97
  def get_date_and_time(self, epoch: Epoch) -> None:
101
98
  if "time" not in self.data:
@@ -113,6 +110,9 @@ class Ceilometer:
113
110
  else:
114
111
  self.data[key] = CloudnetArray(array, key)
115
112
 
113
+ def add_site_geolocation(self):
114
+ utils.add_site_geolocation(self.data, gps=False, site_meta=self.site_meta)
115
+
116
116
  def screen_depol(self) -> None:
117
117
  key = "depolarisation"
118
118
  if key in self.data:
@@ -1,13 +1,16 @@
1
1
  import logging
2
+ from typing import TYPE_CHECKING
2
3
 
3
- import netCDF4
4
4
  import numpy as np
5
5
  from numpy import ma
6
6
 
7
7
  from cloudnetpy import utils
8
8
  from cloudnetpy.cloudnetarray import CloudnetArray
9
9
  from cloudnetpy.exceptions import ValidTimeStampError
10
- from cloudnetpy.instruments.instruments import BASTA, FMCW35, FMCW94, Instrument
10
+ from cloudnetpy.instruments.instruments import BASTA, FMCW35, FMCW94, HATPRO, Instrument
11
+
12
+ if TYPE_CHECKING:
13
+ import netCDF4
11
14
 
12
15
 
13
16
  class CloudnetInstrument:
@@ -20,77 +23,13 @@ class CloudnetInstrument:
20
23
  self.instrument: Instrument | None = None
21
24
 
22
25
  def add_site_geolocation(self) -> None:
23
- for key in ("latitude", "longitude", "altitude"):
24
- value = None
25
- source = None
26
- # From source data (BASTA, RPG-FMCW).
27
- # Should be the accurate GPS coordinates.
28
- # HATPRO is handled elsewhere.
29
- if (
30
- value is None
31
- and self.instrument is not None
32
- and self.instrument in (BASTA, FMCW94, FMCW35)
33
- ):
34
- data = None
35
- if (
36
- hasattr(self, "dataset")
37
- and isinstance(self.dataset, netCDF4.Dataset)
38
- and key in self.dataset.variables
39
- ):
40
- data = self.dataset[key][:]
41
- elif key in self.data:
42
- data = self.data[key].data
43
- if (
44
- data is not None
45
- and not np.all(ma.getmaskarray(data))
46
- and np.any(data != 0)
47
- ):
48
- value = data[data != 0]
49
- source = "GPS"
50
- # User-supplied site coordinate.
51
- if value is None and key in self.site_meta:
52
- value = self.site_meta[key]
53
- source = "site coordinates"
54
- # From source data (CHM15k, CL61, MRR-PRO, Copernicus, Galileo...).
55
- # Assume value is manually set, so cannot trust it.
56
- if (
57
- value is None
58
- and hasattr(self, "dataset")
59
- and isinstance(self.dataset, netCDF4.Dataset)
60
- and key in self.dataset.variables
61
- and not np.all(ma.getmaskarray(self.dataset[key][:]))
62
- ):
63
- value = self.dataset[key][:]
64
- source = "raw file"
65
- # From source global attributes (MIRA).
66
- # Seems to be manually set, so cannot trust it.
67
- if (
68
- value is None
69
- and hasattr(self, "dataset")
70
- and isinstance(self.dataset, netCDF4.Dataset)
71
- and hasattr(
72
- self.dataset,
73
- key.capitalize(),
74
- )
75
- ):
76
- value = self._parse_global_attribute_numeral(key.capitalize())
77
- source = "raw file"
78
- if value is not None:
79
- value = float(ma.mean(value))
80
- # Convert from 0...360 to -180...180
81
- if key == "longitude" and value > 180:
82
- value -= 360
83
- self.data[key] = CloudnetArray(value, key, source=source)
84
-
85
- def _parse_global_attribute_numeral(self, key: str) -> float | None:
86
- new_str = ""
87
- attr = getattr(self.dataset, key)
88
- if attr == "Unknown":
89
- return None
90
- for char in attr:
91
- if char.isdigit() or char == ".":
92
- new_str += char
93
- return float(new_str)
26
+ has_gps = self.instrument in (BASTA, FMCW94, FMCW35, HATPRO)
27
+ utils.add_site_geolocation(
28
+ self.data,
29
+ gps=has_gps,
30
+ site_meta=self.site_meta,
31
+ dataset=self.dataset if hasattr(self, "dataset") else None,
32
+ )
94
33
 
95
34
  def add_height(self) -> None:
96
35
  zenith_angle = self._get_zenith_angle()
@@ -106,21 +106,7 @@ def hatpro2l1c(
106
106
  if "irt" in hatpro.data:
107
107
  hatpro.data["irt"].dimensions = ("time", "ir_channel")
108
108
 
109
- lat = hatpro.data["latitude"].data
110
- lon = hatpro.data["longitude"].data
111
- valid_latlon = (lat != 0) | (lon != 0)
112
- if np.any(valid_latlon):
113
- lat = float(ma.mean(lat[valid_latlon]))
114
- lon = float(ma.mean(lon[valid_latlon]))
115
- latlon_source = "GPS"
116
- else:
117
- lat = site_meta["latitude"]
118
- lon = site_meta["longitude"]
119
- latlon_source = "site coordinates"
120
- alt = float(site_meta["altitude"])
121
- hatpro.data["latitude"] = CloudnetArray(lat, "latitude", source=latlon_source)
122
- hatpro.data["longitude"] = CloudnetArray(lon, "longitude", source=latlon_source)
123
- hatpro.data["altitude"] = CloudnetArray(alt, "altitude", source="site coordinates")
109
+ utils.add_site_geolocation(hatpro.data, gps=True, site_meta=site_meta)
124
110
 
125
111
  attrs_copy = ATTRIBUTES_1B01.copy()
126
112
  attributes = output.add_time_attribute(attrs_copy, hatpro.date)
@@ -60,6 +60,7 @@ def pollyxt2nc(
60
60
  polly.prepare_data()
61
61
  polly.screen_completely_masked_profiles()
62
62
  polly.data_to_cloudnet_arrays(time_dtype="f8")
63
+ polly.add_site_geolocation()
63
64
  attributes = output.add_time_attribute(ATTRIBUTES, polly.date)
64
65
  output.update_attributes(polly.data, attributes)
65
66
  polly.add_snr_info("beta", snr_limit)
@@ -445,10 +445,6 @@ RPG_ATTRIBUTES = {
445
445
  long_name="Number of spectral samples in each chirp sequence",
446
446
  units="1",
447
447
  ),
448
- "chirp_start_indices": MetaData(
449
- long_name="Chirp sequences start indices",
450
- units="1",
451
- ),
452
448
  "number_of_averaged_chirps": MetaData(
453
449
  long_name="Number of averaged chirps in sequence",
454
450
  units="1",
@@ -517,10 +513,6 @@ RPG_ATTRIBUTES = {
517
513
  long_name="PC temperature",
518
514
  units="K",
519
515
  ),
520
- "skewness": MetaData(
521
- long_name="Skewness of spectra",
522
- units="1",
523
- ),
524
516
  "kurtosis": MetaData(
525
517
  long_name="Kurtosis of spectra",
526
518
  units="1",
@@ -93,6 +93,10 @@ COMMON_ATTRIBUTES = {
93
93
  long_name="Kurtosis of spectra",
94
94
  units="1",
95
95
  ),
96
+ "skewness": MetaData(
97
+ long_name="Skewness of spectra",
98
+ units="1",
99
+ ),
96
100
  "nyquist_velocity": MetaData(long_name="Nyquist velocity", units="m s-1"),
97
101
  "radar_frequency": MetaData(long_name="Radar transmit frequency", units="GHz"),
98
102
  "beta": MetaData(
@@ -190,4 +194,8 @@ COMMON_ATTRIBUTES = {
190
194
  units="dB",
191
195
  comment="SNR threshold used in data screening.",
192
196
  ),
197
+ "chirp_start_indices": MetaData(
198
+ long_name="Chirp sequences start indices",
199
+ units="1",
200
+ ),
193
201
  }
@@ -352,7 +352,7 @@ def plot_colormesh(ax, data: np.ndarray, axes: tuple, variable_info) -> None:
352
352
  colorbar = init_colorbar(pl, ax)
353
353
  if variable_info.plot_scale == "logarithmic":
354
354
  tick_labels = get_log_cbar_tick_labels(vmin, vmax)
355
- colorbar.set_ticks(np.arange(vmin, vmax + 1).tolist())
355
+ colorbar.set_ticks(np.arange(vmin, vmax + 1).tolist()) # type: ignore[arg-type]
356
356
  colorbar.ax.set_yticklabels(tick_labels)
357
357
  ax.set_facecolor("white")
358
358
  colorbar.set_label(variable_info.clabel, fontsize=13)
@@ -589,7 +589,7 @@ class Plot2D(Plot):
589
589
  cbar.set_label(str(self._plot_meta.clabel), fontsize=13)
590
590
 
591
591
  if self._is_log:
592
- cbar.set_ticks(np.arange(vmin, vmax + 1).tolist())
592
+ cbar.set_ticks(np.arange(vmin, vmax + 1).tolist()) # type: ignore[arg-type]
593
593
  tick_labels = get_log_cbar_tick_labels(vmin, vmax)
594
594
  cbar.ax.set_yticklabels(tick_labels)
595
595
 
@@ -15,10 +15,12 @@ from typing import Literal, TypeVar
15
15
 
16
16
  import netCDF4
17
17
  import numpy as np
18
+ import numpy.typing as npt
18
19
  from numpy import ma
19
20
  from scipy import ndimage, stats
20
21
  from scipy.interpolate import RectBivariateSpline, RegularGridInterpolator, griddata
21
22
 
23
+ from cloudnetpy.cloudnetarray import CloudnetArray
22
24
  from cloudnetpy.constants import SEC_IN_DAY, SEC_IN_HOUR, SEC_IN_MINUTE
23
25
  from cloudnetpy.exceptions import ValidTimeStampError
24
26
 
@@ -1050,3 +1052,100 @@ def _calc_hash_sum(filename, method, *, is_base64: bool) -> str:
1050
1052
  if is_base64:
1051
1053
  return base64.encodebytes(hash_sum.digest()).decode("utf-8").strip()
1052
1054
  return hash_sum.hexdigest()
1055
+
1056
+
1057
+ def add_site_geolocation(
1058
+ data: dict,
1059
+ *,
1060
+ gps: bool,
1061
+ site_meta: dict | None = None,
1062
+ dataset: netCDF4.Dataset | None = None,
1063
+ ):
1064
+ tmp_data = {}
1065
+ tmp_source = {}
1066
+
1067
+ for key in ("latitude", "longitude", "altitude"):
1068
+ value = None
1069
+ source = None
1070
+ # Prefer accurate GPS coordinates.
1071
+ if gps:
1072
+ values = None
1073
+ if isinstance(dataset, netCDF4.Dataset) and key in dataset.variables:
1074
+ values = dataset[key][:]
1075
+ elif key in data:
1076
+ values = data[key].data
1077
+ if (
1078
+ values is not None
1079
+ and not np.all(ma.getmaskarray(values))
1080
+ and np.any(values != 0)
1081
+ ):
1082
+ value = ma.masked_where(values == 0, values)
1083
+ source = "GPS"
1084
+ # User-supplied site coordinate.
1085
+ if value is None and site_meta is not None and key in site_meta:
1086
+ value = float(site_meta[key])
1087
+ source = "site coordinates"
1088
+ # From source data (CHM15k, CL61, MRR-PRO, Copernicus, Galileo...).
1089
+ # Assume value is manually set, so cannot trust it.
1090
+ if (
1091
+ value is None
1092
+ and isinstance(dataset, netCDF4.Dataset)
1093
+ and key in dataset.variables
1094
+ and not np.all(ma.getmaskarray(dataset[key][:]))
1095
+ ):
1096
+ value = dataset[key][:]
1097
+ source = "raw file"
1098
+ # From source global attributes (MIRA).
1099
+ # Seems to be manually set, so cannot trust it.
1100
+ if (
1101
+ value is None
1102
+ and isinstance(dataset, netCDF4.Dataset)
1103
+ and hasattr(dataset, key.capitalize())
1104
+ ):
1105
+ value = _parse_global_attribute_numeral(dataset, key.capitalize())
1106
+ source = "raw file"
1107
+ if value is not None:
1108
+ tmp_data[key] = value
1109
+ tmp_source[key] = source
1110
+
1111
+ if "latitude" in tmp_data and "longitude" in tmp_data:
1112
+ lat = np.atleast_1d(tmp_data["latitude"])
1113
+ lon = np.atleast_1d(tmp_data["longitude"])
1114
+ lon[lon > 180] - 360
1115
+ if _are_stationary(lat, lon):
1116
+ tmp_data["latitude"] = float(ma.mean(lat))
1117
+ tmp_data["longitude"] = float(ma.mean(lon))
1118
+ else:
1119
+ tmp_data["latitude"] = lat
1120
+ tmp_data["longitude"] = lon
1121
+
1122
+ if "altitude" in tmp_data:
1123
+ alt = np.atleast_1d(tmp_data["altitude"])
1124
+ if ma.max(alt) - ma.min(alt) < 100:
1125
+ tmp_data["altitude"] = float(ma.mean(alt))
1126
+
1127
+ for key in ("latitude", "longitude", "altitude"):
1128
+ if key in tmp_data:
1129
+ data[key] = CloudnetArray(tmp_data[key], key, source=tmp_source[key])
1130
+
1131
+
1132
+ def _parse_global_attribute_numeral(dataset: netCDF4.Dataset, key: str) -> float | None:
1133
+ new_str = ""
1134
+ attr = getattr(dataset, key)
1135
+ if attr == "Unknown":
1136
+ return None
1137
+ for char in attr:
1138
+ if char.isdigit() or char == ".":
1139
+ new_str += char
1140
+ return float(new_str)
1141
+
1142
+
1143
+ def _are_stationary(latitude: npt.NDArray, longitude: npt.NDArray) -> bool:
1144
+ min_lat, max_lat = np.min(latitude), np.max(latitude)
1145
+ min_lon, max_lon = np.min(longitude), np.max(longitude)
1146
+ lat_threshold = 0.01 # deg, around 1 km
1147
+ avg_lat = (min_lat + max_lat) / 2
1148
+ lon_threshold = lat_threshold / np.cos(np.radians(avg_lat))
1149
+ lat_diff = max_lat - min_lat
1150
+ lon_diff = max_lon - min_lon
1151
+ return lat_diff <= lat_threshold and lon_diff <= lon_threshold
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 72
3
- PATCH = 3
2
+ MINOR = 73
3
+ PATCH = 1
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.72.3
3
+ Version: 1.73.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -44,6 +44,7 @@ cloudnetpy/categorize/attenuations/melting_attenuation.py
44
44
  cloudnetpy/categorize/attenuations/rain_attenuation.py
45
45
  cloudnetpy/instruments/__init__.py
46
46
  cloudnetpy/instruments/basta.py
47
+ cloudnetpy/instruments/bowtie.py
47
48
  cloudnetpy/instruments/ceilo.py
48
49
  cloudnetpy/instruments/ceilometer.py
49
50
  cloudnetpy/instruments/cl61d.py
File without changes
File without changes
File without changes
File without changes
File without changes