cloudnetpy 1.71.3__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.3/cloudnetpy.egg-info → cloudnetpy-1.71.4}/PKG-INFO +6 -7
  2. {cloudnetpy-1.71.3 → 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.3 → cloudnetpy-1.71.4}/cloudnetpy/output.py +3 -0
  5. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/version.py +1 -1
  6. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4/cloudnetpy.egg-info}/PKG-INFO +6 -7
  7. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/SOURCES.txt +0 -1
  8. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/requires.txt +1 -0
  9. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/pyproject.toml +3 -5
  10. cloudnetpy-1.71.3/cloudnetpy/instruments/campbell_scientific.py +0 -203
  11. cloudnetpy-1.71.3/cloudnetpy/instruments/vaisala.py +0 -405
  12. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/LICENSE +0 -0
  13. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/MANIFEST.in +0 -0
  14. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/README.md +0 -0
  15. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/__init__.py +0 -0
  16. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/__init__.py +0 -0
  17. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/atmos_utils.py +0 -0
  18. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuation.py +0 -0
  19. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  20. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  21. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  22. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
  23. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  24. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/categorize.py +0 -0
  25. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/classify.py +0 -0
  26. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/containers.py +0 -0
  27. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/disdrometer.py +0 -0
  28. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/droplet.py +0 -0
  29. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/falling.py +0 -0
  30. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/freezing.py +0 -0
  31. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/insects.py +0 -0
  32. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/itu.py +0 -0
  33. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/lidar.py +0 -0
  34. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/melting.py +0 -0
  35. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/model.py +0 -0
  36. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/mwr.py +0 -0
  37. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/categorize/radar.py +0 -0
  38. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/cli.py +0 -0
  39. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/cloudnetarray.py +0 -0
  40. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/concat_lib.py +0 -0
  41. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/constants.py +0 -0
  42. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/datasource.py +0 -0
  43. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/exceptions.py +0 -0
  44. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/__init__.py +0 -0
  45. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/basta.py +0 -0
  46. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/ceilometer.py +0 -0
  47. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/cl61d.py +0 -0
  48. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
  49. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/copernicus.py +0 -0
  50. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
  51. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/common.py +0 -0
  52. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
  53. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
  54. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/galileo.py +0 -0
  55. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/hatpro.py +0 -0
  56. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/instruments.py +0 -0
  57. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/lufft.py +0 -0
  58. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/mira.py +0 -0
  59. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/mrr.py +0 -0
  60. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/nc_lidar.py +0 -0
  61. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/nc_radar.py +0 -0
  62. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/pollyxt.py +0 -0
  63. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/radiometrics.py +0 -0
  64. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  65. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rpg.py +0 -0
  66. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/rpg_reader.py +0 -0
  67. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/toa5.py +0 -0
  68. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/instruments/weather_station.py +0 -0
  69. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/metadata.py +0 -0
  70. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/__init__.py +0 -0
  71. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/file_handler.py +0 -0
  72. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/metadata.py +0 -0
  73. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  74. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  75. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  76. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  77. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
  78. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  79. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
  80. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
  81. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  82. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
  83. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  84. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/products/tools.py +0 -0
  85. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  86. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  87. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  88. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  89. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  90. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  91. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  92. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  93. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  94. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  95. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  96. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  97. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  98. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  99. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  100. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  101. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
  102. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
  103. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  104. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
  105. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  106. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  107. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  108. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
  109. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/model_evaluation/utils.py +0 -0
  110. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/plotting/__init__.py +0 -0
  111. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/plotting/plot_meta.py +0 -0
  112. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/plotting/plotting.py +0 -0
  113. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/__init__.py +0 -0
  114. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/classification.py +0 -0
  115. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/der.py +0 -0
  116. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle.py +0 -0
  117. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle_error.py +0 -0
  118. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/drizzle_tools.py +0 -0
  119. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/epsilon.py +0 -0
  120. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/ier.py +0 -0
  121. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/iwc.py +0 -0
  122. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/lwc.py +0 -0
  123. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  124. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/mwr_tools.py +0 -0
  125. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/products/product_tools.py +0 -0
  126. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/py.typed +0 -0
  127. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy/utils.py +0 -0
  128. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  129. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/entry_points.txt +0 -0
  130. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/cloudnetpy.egg-info/top_level.txt +0 -0
  131. {cloudnetpy-1.71.3 → cloudnetpy-1.71.4}/docs/source/conf.py +0 -0
  132. {cloudnetpy-1.71.3 → 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.3
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
 
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 71
3
- PATCH = 3
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.3
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,203 +0,0 @@
1
- import binascii
2
- import datetime
3
- import re
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
- def _date_format_to_regex(fmt: bytes) -> bytes:
15
- """Converts a date format string to a regex pattern."""
16
- mapping = {
17
- b"%Y": rb"\d{4}",
18
- b"%m": rb"0[1-9]|1[0-2]",
19
- b"%d": rb"0[1-9]|[12]\d|3[01]",
20
- b"%H": rb"[01]\d|2[0-3]",
21
- b"%M": rb"[0-5]\d",
22
- b"%S": rb"[0-5]\d",
23
- b"%f": rb"\d{6}",
24
- }
25
- pattern = re.escape(fmt)
26
- for key, value in mapping.items():
27
- pattern = pattern.replace(
28
- re.escape(key), b"(?P<" + key[1:] + b">" + value + b")"
29
- )
30
- return pattern
31
-
32
-
33
- FORMATS = [
34
- re.compile(_date_format_to_regex(fmt))
35
- for fmt in [
36
- b"%Y-%m-%dT%H:%M:%S.%f,",
37
- b"%%% %Y/%m/%d %H:%M:%S %%%\n",
38
- ]
39
- ]
40
-
41
-
42
- class Cs135(Ceilometer):
43
- def __init__(
44
- self,
45
- full_path: str,
46
- site_meta: dict,
47
- expected_date: str | None = None,
48
- ):
49
- super().__init__()
50
- self.full_path = full_path
51
- self.site_meta = site_meta
52
- self.expected_date = expected_date
53
- self.data = {}
54
- self.metadata = {}
55
- self.instrument = instruments.CS135
56
-
57
- def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
58
- with open(self.full_path, mode="rb") as f:
59
- content = f.read()
60
- timestamps = []
61
- profiles = []
62
- tilt_angles = []
63
- range_resolutions = []
64
-
65
- for fmt in FORMATS:
66
- parts = re.split(fmt, content)
67
- for i in range(1, len(parts), fmt.groups + 1):
68
- timestamp = datetime.datetime(
69
- int(parts[i + fmt.groupindex["Y"] - 1]),
70
- int(parts[i + fmt.groupindex["m"] - 1]),
71
- int(parts[i + fmt.groupindex["d"] - 1]),
72
- int(parts[i + fmt.groupindex["H"] - 1]),
73
- int(parts[i + fmt.groupindex["M"] - 1]),
74
- int(parts[i + fmt.groupindex["S"] - 1]),
75
- int(parts[i + fmt.groupindex["f"] - 1])
76
- if "f" in fmt.groupindex
77
- else 0,
78
- tzinfo=datetime.timezone.utc,
79
- )
80
- try:
81
- self._check_timestamp(timestamp)
82
- except ValidTimeStampError:
83
- continue
84
- try:
85
- message = _read_message(parts[i + fmt.groups])
86
- except InvalidMessageError:
87
- continue
88
- profile = (message.data[:-2] * 1e-8) * (message.scale / 100)
89
- timestamps.append(timestamp)
90
- profiles.append(profile)
91
- tilt_angles.append(message.tilt_angle)
92
- range_resolutions.append(message.range_resolution)
93
-
94
- if len(timestamps) == 0:
95
- msg = "No valid timestamps found in the file"
96
- raise ValidTimeStampError(msg)
97
- range_resolution = range_resolutions[0]
98
- n_gates = len(profiles[0])
99
- if any(res != range_resolution for res in range_resolutions):
100
- msg = "Inconsistent range resolution"
101
- raise InconsistentDataError(msg)
102
- if any(len(profile) != n_gates for profile in profiles):
103
- msg = "Inconsistent number of gates"
104
- raise InconsistentDataError(msg)
105
-
106
- self.data["beta_raw"] = np.array(profiles)
107
- if calibration_factor is None:
108
- calibration_factor = 1.0
109
- self.data["beta_raw"] *= calibration_factor
110
- self.data["calibration_factor"] = calibration_factor
111
- self.data["range"] = (
112
- np.arange(n_gates) * range_resolution + range_resolution / 2
113
- )
114
- self.data["time"] = utils.datetime2decimal_hours(timestamps)
115
- self.data["zenith_angle"] = np.median(tilt_angles)
116
-
117
- def _check_timestamp(self, timestamp: datetime.datetime) -> None:
118
- timestamp_components = str(timestamp.date()).split("-")
119
- if (
120
- self.expected_date is not None
121
- and timestamp_components != self.expected_date.split("-")
122
- ):
123
- raise ValidTimeStampError
124
- if not self.date:
125
- self.date = timestamp_components
126
- if timestamp_components != self.date:
127
- msg = "Inconsistent dates in the file"
128
- raise RuntimeError(msg)
129
-
130
-
131
- class Message(NamedTuple):
132
- scale: int
133
- range_resolution: int
134
- laser_pulse_energy: int
135
- laser_temperature: int
136
- tilt_angle: int
137
- background_light: int
138
- pulse_quantity: int
139
- sample_rate: int
140
- data: np.ndarray
141
-
142
-
143
- class InvalidMessageError(Exception):
144
- pass
145
-
146
-
147
- def _read_message(message: bytes) -> Message:
148
- end_idx = message.index(3)
149
- content = message[1 : end_idx + 1]
150
- expected_checksum = int(message[end_idx + 1 : end_idx + 5], 16)
151
- actual_checksum = _crc16(content)
152
- if expected_checksum != actual_checksum:
153
- msg = (
154
- "Invalid checksum: "
155
- f"expected {expected_checksum:04x}, "
156
- f"got {actual_checksum:04x}"
157
- )
158
- raise InvalidMessageError(msg)
159
- lines = message[1 : end_idx - 1].splitlines()
160
- n_lines = len(lines) + 1
161
- n_first = len(lines[0]) + 1
162
- if n_first != 11:
163
- msg = f"Expected 11 characters in first line, got {n_first}"
164
- raise NotImplementedError(msg)
165
- msg_no = lines[0][-4:-1]
166
- if msg_no == b"002":
167
- if n_lines != 5:
168
- msg = f"Expected 5 lines, got {len(lines)}"
169
- raise InvalidMessageError(msg)
170
- scale, res, n, energy, lt, ti, bl, pulse, rate, _sum = map(
171
- int, lines[2].split()
172
- )
173
- data = _read_backscatter(lines[3].strip(), n)
174
- return Message(scale, res, energy, lt, ti, bl, pulse, rate, data)
175
- if msg_no == b"004":
176
- if n_lines != 6:
177
- msg = f"Expected 6 lines, got {len(lines)}"
178
- raise InvalidMessageError(msg)
179
- scale, res, n, energy, lt, ti, bl, pulse, rate, _sum = map(
180
- int, lines[3].split()
181
- )
182
- data = _read_backscatter(lines[4].strip(), n)
183
- return Message(scale, res, energy, lt, ti, bl, pulse, rate, data)
184
- msg = f"Message number {msg_no.decode()} not implemented"
185
- raise NotImplementedError(msg)
186
-
187
-
188
- def _read_backscatter(data: bytes, n_gates: int) -> np.ndarray:
189
- """Read backscatter values from hex-encoded two's complement values."""
190
- n_chars = 5
191
- n_bits = n_chars * 4
192
- limit = (1 << (n_bits - 1)) - 1
193
- offset = 1 << n_bits
194
- out = np.array(
195
- [int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)],
196
- )
197
- out[out > limit] -= offset
198
- return out
199
-
200
-
201
- def _crc16(data: bytes) -> int:
202
- """Compute checksum similar to CRC-16-CCITT."""
203
- return binascii.crc_hqx(data, 0xFFFF) ^ 0xFFFF