cloudnetpy 1.71.2__tar.gz → 1.71.4__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 (132) hide show
  1. {cloudnetpy-1.71.2/cloudnetpy.egg-info → cloudnetpy-1.71.4}/PKG-INFO +6 -7
  2. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/ceilo.py +1 -2
  3. cloudnetpy-1.71.4/cloudnetpy/instruments/vaisala.py +130 -0
  4. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/output.py +3 -0
  5. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/ier.py +2 -2
  6. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/iwc.py +2 -2
  7. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/version.py +1 -1
  8. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4/cloudnetpy.egg-info}/PKG-INFO +6 -7
  9. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/SOURCES.txt +0 -1
  10. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/requires.txt +1 -0
  11. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/pyproject.toml +3 -5
  12. cloudnetpy-1.71.2/cloudnetpy/instruments/campbell_scientific.py +0 -152
  13. cloudnetpy-1.71.2/cloudnetpy/instruments/vaisala.py +0 -405
  14. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/LICENSE +0 -0
  15. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/MANIFEST.in +0 -0
  16. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/README.md +0 -0
  17. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/__init__.py +0 -0
  18. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/__init__.py +0 -0
  19. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/atmos_utils.py +0 -0
  20. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuation.py +0 -0
  21. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  22. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  23. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  24. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
  25. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  26. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/categorize.py +0 -0
  27. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/classify.py +0 -0
  28. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/containers.py +0 -0
  29. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/disdrometer.py +0 -0
  30. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/droplet.py +0 -0
  31. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/falling.py +0 -0
  32. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/freezing.py +0 -0
  33. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/insects.py +0 -0
  34. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/itu.py +0 -0
  35. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/lidar.py +0 -0
  36. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/melting.py +0 -0
  37. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/model.py +0 -0
  38. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/mwr.py +0 -0
  39. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/categorize/radar.py +0 -0
  40. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/cli.py +0 -0
  41. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/cloudnetarray.py +0 -0
  42. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/concat_lib.py +0 -0
  43. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/constants.py +0 -0
  44. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/datasource.py +0 -0
  45. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/exceptions.py +0 -0
  46. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/__init__.py +0 -0
  47. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/basta.py +0 -0
  48. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/ceilometer.py +0 -0
  49. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/cl61d.py +0 -0
  50. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
  51. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/copernicus.py +0 -0
  52. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
  53. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/common.py +0 -0
  54. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
  55. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
  56. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/galileo.py +0 -0
  57. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/hatpro.py +0 -0
  58. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/instruments.py +0 -0
  59. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/lufft.py +0 -0
  60. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/mira.py +0 -0
  61. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/mrr.py +0 -0
  62. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/nc_lidar.py +0 -0
  63. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/nc_radar.py +0 -0
  64. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/pollyxt.py +0 -0
  65. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/radiometrics.py +0 -0
  66. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  67. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rpg.py +0 -0
  68. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rpg_reader.py +0 -0
  69. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/toa5.py +0 -0
  70. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/instruments/weather_station.py +0 -0
  71. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/metadata.py +0 -0
  72. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/__init__.py +0 -0
  73. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/file_handler.py +0 -0
  74. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/metadata.py +0 -0
  75. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  76. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  77. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  78. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  79. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
  80. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  81. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
  82. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
  83. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  84. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
  85. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  86. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/tools.py +0 -0
  87. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  88. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  89. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  90. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  91. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  92. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  93. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  94. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  95. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  96. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  97. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  98. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  99. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  100. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  101. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  102. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  103. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
  104. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
  105. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  106. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
  107. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  108. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  109. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  110. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
  111. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/utils.py +0 -0
  112. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/plotting/__init__.py +0 -0
  113. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/plotting/plot_meta.py +0 -0
  114. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/plotting/plotting.py +0 -0
  115. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/__init__.py +0 -0
  116. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/classification.py +0 -0
  117. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/der.py +0 -0
  118. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle.py +0 -0
  119. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle_error.py +0 -0
  120. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle_tools.py +0 -0
  121. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/epsilon.py +0 -0
  122. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/lwc.py +0 -0
  123. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  124. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/mwr_tools.py +0 -0
  125. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/products/product_tools.py +0 -0
  126. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/py.typed +0 -0
  127. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy/utils.py +0 -0
  128. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  129. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/entry_points.txt +0 -0
  130. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/top_level.txt +0 -0
  131. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/docs/source/conf.py +0 -0
  132. {cloudnetpy-1.71.2 → cloudnetpy-1.71.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.71.2
3
+ Version: 1.71.4
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -33,14 +33,12 @@ Classifier: Development Status :: 5 - Production/Stable
33
33
  Classifier: Intended Audience :: Science/Research
34
34
  Classifier: License :: OSI Approved :: MIT License
35
35
  Classifier: Operating System :: OS Independent
36
- Classifier: Programming Language :: Python :: 3.10
37
- Classifier: Programming Language :: Python :: 3.11
38
- Classifier: Programming Language :: Python :: 3.12
39
- Classifier: Programming Language :: Python :: 3.13
40
- Classifier: Topic :: Scientific/Engineering
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
41
38
  Requires-Python: >=3.10
42
39
  Description-Content-Type: text/markdown
43
40
  License-File: LICENSE
41
+ Requires-Dist: ceilopyter
44
42
  Requires-Dist: cloudnetpy_qc>=1.15.0
45
43
  Requires-Dist: doppy>=0.5.0
46
44
  Requires-Dist: matplotlib
@@ -61,6 +59,7 @@ Requires-Dist: pre-commit; extra == "dev"
61
59
  Requires-Dist: release-version; extra == "dev"
62
60
  Provides-Extra: extras
63
61
  Requires-Dist: voodoonet>=0.1.7; extra == "extras"
62
+ Dynamic: license-file
64
63
 
65
64
  # CloudnetPy
66
65
 
@@ -6,10 +6,9 @@ import netCDF4
6
6
  from numpy import ma
7
7
 
8
8
  from cloudnetpy import output, utils
9
- from cloudnetpy.instruments.campbell_scientific import Cs135
10
9
  from cloudnetpy.instruments.cl61d import Cl61d
11
10
  from cloudnetpy.instruments.lufft import LufftCeilo
12
- from cloudnetpy.instruments.vaisala import ClCeilo, Ct25k
11
+ from cloudnetpy.instruments.vaisala import ClCeilo, Cs135, Ct25k
13
12
  from cloudnetpy.metadata import MetaData
14
13
 
15
14
 
@@ -0,0 +1,130 @@
1
+ """Module with classes for Vaisala ceilometers."""
2
+
3
+ import datetime
4
+ from collections.abc import Callable
5
+
6
+ import ceilopyter.version
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+ from ceilopyter import read_cl_file, read_cs_file, read_ct_file
10
+
11
+ from cloudnetpy.exceptions import ValidTimeStampError
12
+ from cloudnetpy.instruments import instruments
13
+ from cloudnetpy.instruments.ceilometer import Ceilometer, NoiseParam
14
+
15
+
16
+ class VaisalaCeilo(Ceilometer):
17
+ """Base class for Vaisala ceilometers."""
18
+
19
+ def __init__(
20
+ self,
21
+ reader: Callable,
22
+ full_path: str,
23
+ site_meta: dict,
24
+ expected_date: str | None = None,
25
+ ):
26
+ super().__init__(self.noise_param)
27
+ self.reader = reader
28
+ self.full_path = full_path
29
+ self.site_meta = site_meta
30
+ self.expected_date = expected_date
31
+ self.sane_date = (
32
+ datetime.date.fromisoformat(self.expected_date)
33
+ if self.expected_date
34
+ else None
35
+ )
36
+ self.software = {"ceilopyter": ceilopyter.version.__version__}
37
+
38
+ def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
39
+ """Read all lines of data from the file."""
40
+ time, data = self.reader(self.full_path)
41
+ range_res = data[0].range_resolution
42
+ n_gates = len(data[0].beta)
43
+ self.data["time"] = np.array(time)
44
+ self.data["range"] = np.arange(n_gates) * range_res + range_res / 2
45
+ self.data["beta_raw"] = np.stack([d.beta for d in data])
46
+ self.data["calibration_factor"] = calibration_factor or 1.0
47
+ self.data["beta_raw"] *= self.data["calibration_factor"]
48
+ self.data["zenith_angle"] = np.median([d.tilt_angle for d in data])
49
+ self._sort_time()
50
+ self._screen_date()
51
+ self._convert_to_fraction_hour()
52
+ self._store_ceilometer_info()
53
+
54
+ def _sort_time(self):
55
+ """Sorts timestamps and removes duplicates."""
56
+ time = self.data["time"]
57
+ _time, ind = np.unique(time, return_index=True)
58
+ self._screen_time_indices(ind)
59
+
60
+ def _screen_date(self):
61
+ time = self.data["time"]
62
+ if self.sane_date is None:
63
+ self.sane_date = time[0].date()
64
+ self.expected_date = self.sane_date.isoformat()
65
+ is_valid = np.array([t.date() == self.sane_date for t in time])
66
+ self._screen_time_indices(is_valid)
67
+
68
+ def _screen_time_indices(
69
+ self, valid_indices: npt.NDArray[np.intp] | npt.NDArray[np.bool]
70
+ ):
71
+ time = self.data["time"]
72
+ n_time = len(time)
73
+ if len(valid_indices) == 0 or (
74
+ valid_indices.dtype == np.bool and not np.any(valid_indices)
75
+ ):
76
+ msg = "All timestamps screened"
77
+ raise ValidTimeStampError(msg)
78
+ for key, array in self.data.items():
79
+ if hasattr(array, "shape") and array.shape[:1] == (n_time,):
80
+ self.data[key] = self.data[key][valid_indices]
81
+
82
+ def _convert_to_fraction_hour(self):
83
+ time = self.data["time"]
84
+ midnight = time[0].replace(hour=0, minute=0, second=0, microsecond=0)
85
+ hour = datetime.timedelta(hours=1)
86
+ self.data["time"] = (time - midnight) / hour
87
+ self.date = self.expected_date.split("-") # type: ignore[union-attr]
88
+
89
+ def _store_ceilometer_info(self):
90
+ raise NotImplementedError
91
+
92
+
93
+ class ClCeilo(VaisalaCeilo):
94
+ """Class for Vaisala CL31/CL51 ceilometers."""
95
+
96
+ noise_param = NoiseParam(noise_min=3.1e-8, noise_smooth_min=1.1e-8)
97
+
98
+ def __init__(self, full_path, site_meta, expected_date=None):
99
+ super().__init__(read_cl_file, full_path, site_meta, expected_date)
100
+
101
+ def _store_ceilometer_info(self):
102
+ n_gates = self.data["beta_raw"].shape[1]
103
+ if n_gates < 1540:
104
+ self.instrument = instruments.CL31
105
+ else:
106
+ self.instrument = instruments.CL51
107
+
108
+
109
+ class Ct25k(VaisalaCeilo):
110
+ """Class for Vaisala CT25k ceilometer."""
111
+
112
+ noise_param = NoiseParam(noise_min=0.7e-7, noise_smooth_min=1.2e-8)
113
+
114
+ def __init__(self, full_path, site_meta, expected_date=None):
115
+ super().__init__(read_ct_file, full_path, site_meta, expected_date)
116
+
117
+ def _store_ceilometer_info(self):
118
+ self.instrument = instruments.CT25K
119
+
120
+
121
+ class Cs135(VaisalaCeilo):
122
+ """Class for Campbell Scientific CS135 ceilometer."""
123
+
124
+ noise_param = NoiseParam()
125
+
126
+ def __init__(self, full_path, site_meta, expected_date=None):
127
+ super().__init__(read_cs_file, full_path, site_meta, expected_date)
128
+
129
+ def _store_ceilometer_info(self):
130
+ self.instrument = instruments.CS135
@@ -44,6 +44,9 @@ def save_level1b(
44
44
  nc.source = get_l1b_source(obj.instrument)
45
45
  if hasattr(obj, "serial_number") and obj.serial_number is not None:
46
46
  nc.serial_number = obj.serial_number
47
+ if hasattr(obj, "software"):
48
+ for software, version in obj.software.items():
49
+ nc.setncattr(f"{software}_version", version)
47
50
  nc.references = get_references()
48
51
  return file_uuid
49
52
 
@@ -83,9 +83,9 @@ class IerSource(IceSource):
83
83
 
84
84
 
85
85
  def _add_ier_comment(attributes: dict, ier: IerSource) -> dict:
86
- freq = ier.radar_frequency
86
+ freq = round(ier.radar_frequency, 3)
87
87
  coeffs = ier.coefficients
88
- factor = np.round((coeffs[0] / 0.93), 3)
88
+ factor = round(coeffs[0] / 0.93, 3)
89
89
  attributes["ier"] = attributes["ier"]._replace(
90
90
  comment=f"This variable was calculated from the {freq}-GHz radar\n"
91
91
  f"reflectivity factor after correction for gaseous attenuation,\n"
@@ -113,9 +113,9 @@ def _add_iwc_error_comment(attributes: dict, lwp_prior, uncertainty: float) -> d
113
113
 
114
114
 
115
115
  def _add_iwc_comment(attributes: dict, iwc: IwcSource) -> dict:
116
- freq = iwc.radar_frequency
116
+ freq = round(iwc.radar_frequency, 3)
117
117
  coeffs = iwc.coefficients
118
- factor = round((coeffs[0] / 0.93) * 1000) / 1000
118
+ factor = round(coeffs[0] / 0.93, 3)
119
119
  attributes["iwc"] = attributes["iwc"]._replace(
120
120
  comment=f"This variable was calculated from the {freq}-GHz radar reflectivity\n"
121
121
  "factor after correction for gaseous attenuation, and temperature taken from\n"
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 71
3
- PATCH = 2
3
+ PATCH = 4
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.71.2
3
+ Version: 1.71.4
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -33,14 +33,12 @@ Classifier: Development Status :: 5 - Production/Stable
33
33
  Classifier: Intended Audience :: Science/Research
34
34
  Classifier: License :: OSI Approved :: MIT License
35
35
  Classifier: Operating System :: OS Independent
36
- Classifier: Programming Language :: Python :: 3.10
37
- Classifier: Programming Language :: Python :: 3.11
38
- Classifier: Programming Language :: Python :: 3.12
39
- Classifier: Programming Language :: Python :: 3.13
40
- Classifier: Topic :: Scientific/Engineering
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
41
38
  Requires-Python: >=3.10
42
39
  Description-Content-Type: text/markdown
43
40
  License-File: LICENSE
41
+ Requires-Dist: ceilopyter
44
42
  Requires-Dist: cloudnetpy_qc>=1.15.0
45
43
  Requires-Dist: doppy>=0.5.0
46
44
  Requires-Dist: matplotlib
@@ -61,6 +59,7 @@ Requires-Dist: pre-commit; extra == "dev"
61
59
  Requires-Dist: release-version; extra == "dev"
62
60
  Provides-Extra: extras
63
61
  Requires-Dist: voodoonet>=0.1.7; extra == "extras"
62
+ Dynamic: license-file
64
63
 
65
64
  # CloudnetPy
66
65
 
@@ -44,7 +44,6 @@ 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/campbell_scientific.py
48
47
  cloudnetpy/instruments/ceilo.py
49
48
  cloudnetpy/instruments/ceilometer.py
50
49
  cloudnetpy/instruments/cl61d.py
@@ -1,3 +1,4 @@
1
+ ceilopyter
1
2
  cloudnetpy_qc>=1.15.0
2
3
  doppy>=0.5.0
3
4
  matplotlib
@@ -10,13 +10,11 @@ classifiers = [
10
10
  "Intended Audience :: Science/Research",
11
11
  "License :: OSI Approved :: MIT License",
12
12
  "Operating System :: OS Independent",
13
- "Programming Language :: Python :: 3.10",
14
- "Programming Language :: Python :: 3.11",
15
- "Programming Language :: Python :: 3.12",
16
- "Programming Language :: Python :: 3.13",
17
- "Topic :: Scientific/Engineering",
13
+ "Programming Language :: Python :: 3",
14
+ "Topic :: Scientific/Engineering :: Atmospheric Science",
18
15
  ]
19
16
  dependencies = [
17
+ "ceilopyter",
20
18
  "cloudnetpy_qc>=1.15.0",
21
19
  "doppy>=0.5.0",
22
20
  "matplotlib",
@@ -1,152 +0,0 @@
1
- import binascii
2
- import re
3
- from datetime import datetime, timezone
4
- from typing import NamedTuple
5
-
6
- import numpy as np
7
-
8
- from cloudnetpy import utils
9
- from cloudnetpy.exceptions import InconsistentDataError, ValidTimeStampError
10
- from cloudnetpy.instruments import instruments
11
- from cloudnetpy.instruments.ceilometer import Ceilometer
12
-
13
-
14
- class Cs135(Ceilometer):
15
- def __init__(
16
- self,
17
- full_path: str,
18
- site_meta: dict,
19
- expected_date: str | None = None,
20
- ):
21
- super().__init__()
22
- self.full_path = full_path
23
- self.site_meta = site_meta
24
- self.expected_date = expected_date
25
- self.data = {}
26
- self.metadata = {}
27
- self.instrument = instruments.CS135
28
-
29
- def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
30
- with open(self.full_path, mode="rb") as f:
31
- content = f.read()
32
- timestamps = []
33
- profiles = []
34
- tilt_angles = []
35
- range_resolutions = []
36
-
37
- parts = re.split(rb"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}),", content)
38
- for i in range(1, len(parts), 2):
39
- timestamp = datetime.strptime(
40
- parts[i].decode(),
41
- "%Y-%m-%dT%H:%M:%S.%f",
42
- ).replace(tzinfo=timezone.utc)
43
- try:
44
- self._check_timestamp(timestamp)
45
- except ValidTimeStampError:
46
- continue
47
- try:
48
- message = _read_message(parts[i + 1])
49
- except InvalidMessageError:
50
- continue
51
- profile = (message.data[:-2] * 1e-8) * (message.scale / 100)
52
- timestamps.append(timestamp)
53
- profiles.append(profile)
54
- tilt_angles.append(message.tilt_angle)
55
- range_resolutions.append(message.range_resolution)
56
-
57
- if len(timestamps) == 0:
58
- msg = "No valid timestamps found in the file"
59
- raise ValidTimeStampError(msg)
60
- range_resolution = range_resolutions[0]
61
- n_gates = len(profiles[0])
62
- if any(res != range_resolution for res in range_resolutions):
63
- msg = "Inconsistent range resolution"
64
- raise InconsistentDataError(msg)
65
- if any(len(profile) != n_gates for profile in profiles):
66
- msg = "Inconsistent number of gates"
67
- raise InconsistentDataError(msg)
68
-
69
- self.data["beta_raw"] = np.array(profiles)
70
- if calibration_factor is None:
71
- calibration_factor = 1.0
72
- self.data["beta_raw"] *= calibration_factor
73
- self.data["calibration_factor"] = calibration_factor
74
- self.data["range"] = (
75
- np.arange(n_gates) * range_resolution + range_resolution / 2
76
- )
77
- self.data["time"] = utils.datetime2decimal_hours(timestamps)
78
- self.data["zenith_angle"] = np.median(tilt_angles)
79
-
80
- def _check_timestamp(self, timestamp: datetime) -> None:
81
- timestamp_components = str(timestamp.date()).split("-")
82
- if (
83
- self.expected_date is not None
84
- and timestamp_components != self.expected_date.split("-")
85
- ):
86
- raise ValidTimeStampError
87
- if not self.date:
88
- self.date = timestamp_components
89
- if timestamp_components != self.date:
90
- msg = "Inconsistent dates in the file"
91
- raise RuntimeError(msg)
92
-
93
-
94
- class Message(NamedTuple):
95
- scale: int
96
- range_resolution: int
97
- laser_pulse_energy: int
98
- laser_temperature: int
99
- tilt_angle: int
100
- background_light: int
101
- pulse_quantity: int
102
- sample_rate: int
103
- data: np.ndarray
104
-
105
-
106
- class InvalidMessageError(Exception):
107
- pass
108
-
109
-
110
- def _read_message(message: bytes) -> Message:
111
- end_idx = message.index(3)
112
- content = message[1 : end_idx + 1]
113
- expected_checksum = int(message[end_idx + 1 : end_idx + 5], 16)
114
- actual_checksum = _crc16(content)
115
- if expected_checksum != actual_checksum:
116
- msg = (
117
- "Invalid checksum: "
118
- f"expected {expected_checksum:04x}, "
119
- f"got {actual_checksum:04x}"
120
- )
121
- raise InvalidMessageError(msg)
122
- lines = message.splitlines()
123
- if len(lines[0]) != 11:
124
- msg = f"Expected 11 characters in first line, got {len(lines[0])}"
125
- raise NotImplementedError(msg)
126
- if (msg_no := lines[0][-4:-1]) != b"002":
127
- msg = f"Message number {msg_no.decode()} not implemented"
128
- raise NotImplementedError(msg)
129
- if len(lines) != 5:
130
- msg = f"Expected 5 lines, got {len(lines)}"
131
- raise InvalidMessageError(msg)
132
- scale, res, n, energy, lt, ti, bl, pulse, rate, _sum = map(int, lines[2].split())
133
- data = _read_backscatter(lines[3].strip(), n)
134
- return Message(scale, res, energy, lt, ti, bl, pulse, rate, data)
135
-
136
-
137
- def _read_backscatter(data: bytes, n_gates: int) -> np.ndarray:
138
- """Read backscatter values from hex-encoded two's complement values."""
139
- n_chars = 5
140
- n_bits = n_chars * 4
141
- limit = (1 << (n_bits - 1)) - 1
142
- offset = 1 << n_bits
143
- out = np.array(
144
- [int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)],
145
- )
146
- out[out > limit] -= offset
147
- return out
148
-
149
-
150
- def _crc16(data: bytes) -> int:
151
- """Compute checksum similar to CRC-16-CCITT."""
152
- return binascii.crc_hqx(data, 0xFFFF) ^ 0xFFFF