cloudnetpy 1.51.0__tar.gz → 1.51.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.
- {cloudnetpy-1.51.0/cloudnetpy.egg-info → cloudnetpy-1.51.1}/PKG-INFO +1 -1
- cloudnetpy-1.51.1/cloudnetpy/instruments/campbell_scientific.py +135 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/ceilometer.py +3 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/cl61d.py +5 -1
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/lufft.py +0 -3
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/nc_lidar.py +1 -1
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/pollyxt.py +6 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/version.py +1 -1
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1/cloudnetpy.egg-info}/PKG-INFO +1 -1
- cloudnetpy-1.51.0/cloudnetpy/instruments/campbell_scientific.py +0 -110
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/LICENSE +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/MANIFEST.in +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/README.md +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/atmos.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/atmos_utils.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/categorize.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/classify.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/containers.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/droplet.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/falling.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/freezing.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/insects.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/lidar.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/melting.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/model.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/mwr.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/categorize/radar.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/cloudnetarray.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/concat_lib.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/constants.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/datasource.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/exceptions.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/basta.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/ceilo.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/copernicus.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/disdrometer/common.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/galileo.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/hatpro.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/instruments.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/mira.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/nc_radar.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/radiometrics.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/rpg.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/rpg_reader.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/vaisala.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/instruments/weather_station.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/metadata.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/file_handler.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/metadata.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/utils.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/output.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/plotting/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/plotting/plotting.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/__init__.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/classification.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/der.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/drizzle.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/drizzle_error.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/drizzle_tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/ier.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/iwc.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/lwc.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/mie_lu_tables.nc +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/mwr_multi.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/mwr_single.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/products/product_tools.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/py.typed +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/utils.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy.egg-info/SOURCES.txt +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy.egg-info/dependency_links.txt +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy.egg-info/requires.txt +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy.egg-info/top_level.txt +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/docs/source/conf.py +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/pyproject.toml +0 -0
- {cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/setup.cfg +0 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
import binascii
|
2
|
+
import re
|
3
|
+
from datetime import datetime
|
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, full_path: str, site_meta: dict, expected_date: str | None = None
|
17
|
+
):
|
18
|
+
super().__init__()
|
19
|
+
self.full_path = full_path
|
20
|
+
self.site_meta = site_meta
|
21
|
+
self.expected_date = expected_date
|
22
|
+
self.data = {}
|
23
|
+
self.metadata = {}
|
24
|
+
self.instrument = instruments.CS135
|
25
|
+
|
26
|
+
def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
|
27
|
+
with open(self.full_path, mode="rb") as f:
|
28
|
+
content = f.read()
|
29
|
+
timestamps = []
|
30
|
+
profiles = []
|
31
|
+
tilt_angles = []
|
32
|
+
range_resolutions = []
|
33
|
+
|
34
|
+
parts = re.split(rb"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}),", content)
|
35
|
+
for i in range(1, len(parts), 2):
|
36
|
+
timestamp = datetime.strptime(parts[i].decode(), "%Y-%m-%dT%H:%M:%S.%f")
|
37
|
+
try:
|
38
|
+
self._check_timestamp(timestamp)
|
39
|
+
except ValidTimeStampError:
|
40
|
+
continue
|
41
|
+
try:
|
42
|
+
message = _read_message(parts[i + 1])
|
43
|
+
except InvalidMessageError:
|
44
|
+
continue
|
45
|
+
profile = (message.data[:-2] * 1e-8) * (message.scale / 100)
|
46
|
+
timestamps.append(timestamp)
|
47
|
+
profiles.append(profile)
|
48
|
+
tilt_angles.append(message.tilt_angle)
|
49
|
+
range_resolutions.append(message.range_resolution)
|
50
|
+
|
51
|
+
if len(timestamps) == 0:
|
52
|
+
raise ValidTimeStampError("No valid timestamps found in the file")
|
53
|
+
range_resolution = range_resolutions[0]
|
54
|
+
n_gates = len(profiles[0])
|
55
|
+
if any(res != range_resolution for res in range_resolutions):
|
56
|
+
raise InconsistentDataError("Inconsistent range resolution")
|
57
|
+
if any(len(profile) != n_gates for profile in profiles):
|
58
|
+
raise InconsistentDataError("Inconsistent number of gates")
|
59
|
+
|
60
|
+
self.data["beta_raw"] = np.array(profiles)
|
61
|
+
if calibration_factor is None:
|
62
|
+
calibration_factor = 1.0
|
63
|
+
self.data["beta_raw"] *= calibration_factor
|
64
|
+
self.data["calibration_factor"] = calibration_factor
|
65
|
+
self.data["range"] = (
|
66
|
+
np.arange(n_gates) * range_resolution + range_resolution / 2
|
67
|
+
)
|
68
|
+
self.data["time"] = utils.datetime2decimal_hours(timestamps)
|
69
|
+
self.data["zenith_angle"] = np.median(tilt_angles)
|
70
|
+
|
71
|
+
def _check_timestamp(self, timestamp: datetime):
|
72
|
+
timestamp_components = str(timestamp.date()).split("-")
|
73
|
+
if self.expected_date is not None:
|
74
|
+
if timestamp_components != self.expected_date.split("-"):
|
75
|
+
raise ValidTimeStampError
|
76
|
+
if not self.date:
|
77
|
+
self.date = timestamp_components
|
78
|
+
assert timestamp_components == self.date
|
79
|
+
|
80
|
+
|
81
|
+
class Message(NamedTuple):
|
82
|
+
scale: int
|
83
|
+
range_resolution: int
|
84
|
+
laser_pulse_energy: int
|
85
|
+
laser_temperature: int
|
86
|
+
tilt_angle: int
|
87
|
+
background_light: int
|
88
|
+
pulse_quantity: int
|
89
|
+
sample_rate: int
|
90
|
+
data: np.ndarray
|
91
|
+
|
92
|
+
|
93
|
+
class InvalidMessageError(Exception):
|
94
|
+
pass
|
95
|
+
|
96
|
+
|
97
|
+
def _read_message(message: bytes) -> Message:
|
98
|
+
end_idx = message.index(3)
|
99
|
+
content = message[1 : end_idx + 1]
|
100
|
+
expected_checksum = int(message[end_idx + 1 : end_idx + 5], 16)
|
101
|
+
actual_checksum = _crc16(content)
|
102
|
+
if expected_checksum != actual_checksum:
|
103
|
+
raise InvalidMessageError(
|
104
|
+
"Invalid checksum: "
|
105
|
+
f"expected {expected_checksum:04x}, "
|
106
|
+
f"got {actual_checksum:04x}"
|
107
|
+
)
|
108
|
+
lines = message.splitlines()
|
109
|
+
if len(lines[0]) != 11:
|
110
|
+
raise NotImplementedError("Unknown message format")
|
111
|
+
if (msg_no := lines[0][-4:-1]) != b"002":
|
112
|
+
raise NotImplementedError(f"Message number {msg_no.decode()} not implemented")
|
113
|
+
if len(lines) != 5:
|
114
|
+
raise InvalidMessageError("Invalid line count")
|
115
|
+
scale, res, n, energy, lt, ti, bl, pulse, rate, _sum = map(int, lines[2].split())
|
116
|
+
data = _read_backscatter(lines[3].strip(), n)
|
117
|
+
return Message(scale, res, energy, lt, ti, bl, pulse, rate, data)
|
118
|
+
|
119
|
+
|
120
|
+
def _read_backscatter(data: bytes, n_gates: int) -> np.ndarray:
|
121
|
+
"""Read backscatter values from hex-encoded two's complement values."""
|
122
|
+
n_chars = 5
|
123
|
+
n_bits = n_chars * 4
|
124
|
+
limit = (1 << (n_bits - 1)) - 1
|
125
|
+
offset = 1 << n_bits
|
126
|
+
out = np.array(
|
127
|
+
[int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)]
|
128
|
+
)
|
129
|
+
out[out > limit] -= offset
|
130
|
+
return out
|
131
|
+
|
132
|
+
|
133
|
+
def _crc16(data: bytes) -> int:
|
134
|
+
"""Compute checksum similar to CRC-16-CCITT."""
|
135
|
+
return binascii.crc_hqx(data, 0xFFFF) ^ 0xFFFF
|
@@ -21,6 +21,8 @@ class NoiseParam:
|
|
21
21
|
class Ceilometer:
|
22
22
|
"""Base class for all types of ceilometers and pollyxt."""
|
23
23
|
|
24
|
+
serial_number: str | None
|
25
|
+
|
24
26
|
def __init__(self, noise_param: NoiseParam = NoiseParam()):
|
25
27
|
self.noise_param = noise_param
|
26
28
|
self.data: dict = {} # Need to contain 'beta_raw', 'range' and 'time'
|
@@ -29,6 +31,7 @@ class Ceilometer:
|
|
29
31
|
self.site_meta: dict = {}
|
30
32
|
self.date: list[str] = []
|
31
33
|
self.instrument: Instrument | None = None
|
34
|
+
self.serial_number = None
|
32
35
|
|
33
36
|
def calc_screened_product(
|
34
37
|
self,
|
@@ -23,7 +23,8 @@ class Cl61d(NcLidar):
|
|
23
23
|
"""Reads data and metadata from concatenated Vaisala CL61d netCDF file."""
|
24
24
|
with netCDF4.Dataset(self.file_name) as dataset:
|
25
25
|
self.dataset = dataset
|
26
|
-
self.
|
26
|
+
self._fetch_attributes()
|
27
|
+
self._fetch_zenith_angle("tilt_angle", default=3.0)
|
27
28
|
self._fetch_range(reference="lower")
|
28
29
|
self._fetch_lidar_variables(calibration_factor)
|
29
30
|
self._fetch_time_and_date()
|
@@ -41,3 +42,6 @@ class Cl61d(NcLidar):
|
|
41
42
|
self.data["depolarisation"] = (
|
42
43
|
self.dataset.variables["x_pol"][:] / self.dataset.variables["p_pol"][:]
|
43
44
|
)
|
45
|
+
|
46
|
+
def _fetch_attributes(self):
|
47
|
+
self.serial_number = getattr(self.dataset, "instrument_serial_number", None)
|
@@ -12,8 +12,6 @@ from cloudnetpy.instruments.nc_lidar import NcLidar
|
|
12
12
|
class LufftCeilo(NcLidar):
|
13
13
|
"""Class for Lufft chm15k ceilometer."""
|
14
14
|
|
15
|
-
serial_number: str | None
|
16
|
-
|
17
15
|
def __init__(
|
18
16
|
self, file_name: str, site_meta: dict, expected_date: str | None = None
|
19
17
|
):
|
@@ -21,7 +19,6 @@ class LufftCeilo(NcLidar):
|
|
21
19
|
self.file_name = file_name
|
22
20
|
self.site_meta = site_meta
|
23
21
|
self.expected_date = expected_date
|
24
|
-
self.serial_number = None
|
25
22
|
|
26
23
|
def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
|
27
24
|
"""Reads data and metadata from Jenoptik netCDF file."""
|
@@ -30,7 +30,7 @@ class NcLidar(Ceilometer):
|
|
30
30
|
def _fetch_zenith_angle(self, key: str, default: float = 3.0) -> None:
|
31
31
|
assert self.dataset is not None
|
32
32
|
if key in self.dataset.variables:
|
33
|
-
zenith_angle = self.dataset.variables[key][:]
|
33
|
+
zenith_angle = np.median(self.dataset.variables[key][:])
|
34
34
|
else:
|
35
35
|
zenith_angle = float(default)
|
36
36
|
logging.warning(f"No zenith angle found, assuming {zenith_angle} degrees")
|
@@ -103,6 +103,7 @@ class PollyXt(Ceilometer):
|
|
103
103
|
raise InconsistentDataError(
|
104
104
|
"Inconsistent number of pollyxt bsc / depol files"
|
105
105
|
)
|
106
|
+
self._fetch_attributes(bsc_files[0])
|
106
107
|
self.data["range"] = _read_array_from_multiple_files(
|
107
108
|
bsc_files, depol_files, "height"
|
108
109
|
)
|
@@ -157,6 +158,11 @@ class PollyXt(Ceilometer):
|
|
157
158
|
return channel
|
158
159
|
raise ValidTimeStampError("No functional pollyXT backscatter channels found")
|
159
160
|
|
161
|
+
def _fetch_attributes(self, file: str) -> None:
|
162
|
+
with netCDF4.Dataset(file, "r") as nc:
|
163
|
+
if hasattr(nc, "source"):
|
164
|
+
self.serial_number = nc.source.lower()
|
165
|
+
|
160
166
|
|
161
167
|
def _read_array_from_multiple_files(files1: list, files2: list, key) -> np.ndarray:
|
162
168
|
array: np.ndarray = np.array([])
|
@@ -1,110 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import re
|
3
|
-
from datetime import datetime
|
4
|
-
|
5
|
-
import numpy as np
|
6
|
-
|
7
|
-
from cloudnetpy import utils
|
8
|
-
from cloudnetpy.exceptions import ValidTimeStampError
|
9
|
-
from cloudnetpy.instruments import instruments
|
10
|
-
from cloudnetpy.instruments.ceilometer import Ceilometer
|
11
|
-
|
12
|
-
|
13
|
-
class Cs135(Ceilometer):
|
14
|
-
def __init__(
|
15
|
-
self, full_path: str, site_meta: dict, expected_date: str | None = None
|
16
|
-
):
|
17
|
-
super().__init__()
|
18
|
-
self.full_path = full_path
|
19
|
-
self.site_meta = site_meta
|
20
|
-
self.expected_date = expected_date
|
21
|
-
self.data = {}
|
22
|
-
self.metadata = {}
|
23
|
-
self.instrument = instruments.CS135
|
24
|
-
|
25
|
-
def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
|
26
|
-
with open(self.full_path, mode="r", encoding="utf-8") as f:
|
27
|
-
lines = f.readlines()
|
28
|
-
timestamps, profiles, scales, tilt_angles = [], [], [], []
|
29
|
-
range_resolution, n_gates = 0, 0
|
30
|
-
for i, line in enumerate(lines):
|
31
|
-
line_splat = line.strip().split(",")
|
32
|
-
if is_timestamp(line_splat[0]):
|
33
|
-
timestamp = datetime.strptime(line_splat[0], "%Y-%m-%dT%H:%M:%S.%f")
|
34
|
-
try:
|
35
|
-
self._check_timestamp(timestamp)
|
36
|
-
except ValidTimeStampError:
|
37
|
-
continue
|
38
|
-
timestamps.append(timestamp)
|
39
|
-
_line1 = line_splat[1]
|
40
|
-
if len(_line1) != 11:
|
41
|
-
raise NotImplementedError("Unknown message number")
|
42
|
-
if (msg_no := _line1[-4:-1]) != "002":
|
43
|
-
raise NotImplementedError(
|
44
|
-
f"Message number {msg_no} not implemented"
|
45
|
-
)
|
46
|
-
_line3 = lines[i + 2].strip().split(" ")
|
47
|
-
scale, range_resolution, n_gates, tilt_angle = [
|
48
|
-
int(_line3[ind]) for ind in [0, 1, 2, 5]
|
49
|
-
]
|
50
|
-
scales.append(scale)
|
51
|
-
tilt_angles.append(tilt_angle)
|
52
|
-
_line4 = lines[i + 3].strip()
|
53
|
-
profiles.append(_hex2backscatter(_line4, n_gates))
|
54
|
-
|
55
|
-
if len(timestamps) == 0:
|
56
|
-
raise ValidTimeStampError("No valid timestamps found in the file")
|
57
|
-
array = self._handle_large_values(np.array(profiles))
|
58
|
-
self.data["beta_raw"] = _scale_backscatter(array, scales)
|
59
|
-
if calibration_factor is None:
|
60
|
-
calibration_factor = 1.0
|
61
|
-
self.data["beta_raw"] *= calibration_factor
|
62
|
-
self.data["calibration_factor"] = calibration_factor
|
63
|
-
self.data["range"] = (
|
64
|
-
np.arange(n_gates) * range_resolution + range_resolution / 2
|
65
|
-
)
|
66
|
-
self.data["time"] = utils.datetime2decimal_hours(timestamps)
|
67
|
-
self.data["zenith_angle"] = np.median(tilt_angles)
|
68
|
-
|
69
|
-
def _check_timestamp(self, timestamp: datetime):
|
70
|
-
timestamp_components = str(timestamp.date()).split("-")
|
71
|
-
if self.expected_date is not None:
|
72
|
-
if timestamp_components != self.expected_date.split("-"):
|
73
|
-
raise ValidTimeStampError
|
74
|
-
if not self.date:
|
75
|
-
self.date = timestamp_components
|
76
|
-
assert timestamp_components == self.date
|
77
|
-
|
78
|
-
@staticmethod
|
79
|
-
def _handle_large_values(array: np.ndarray) -> np.ndarray:
|
80
|
-
ind = np.where(array > 524287)
|
81
|
-
if ind[0].size > 0:
|
82
|
-
array[ind] -= 1048576
|
83
|
-
return array
|
84
|
-
|
85
|
-
|
86
|
-
def is_timestamp(timestamp: str) -> bool:
|
87
|
-
"""Tests if the input string is formatted as -yyyy-mm-dd hh:mm:ss"""
|
88
|
-
reg_exp = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}")
|
89
|
-
if reg_exp.match(timestamp) is not None:
|
90
|
-
return True
|
91
|
-
return False
|
92
|
-
|
93
|
-
|
94
|
-
def _hex2backscatter(data: str, n_gates: int):
|
95
|
-
"""Converts hex string to backscatter values."""
|
96
|
-
n_chars = 5
|
97
|
-
return [
|
98
|
-
int(data[i : i + n_chars], 16) for i in range(0, n_gates * n_chars, n_chars)
|
99
|
-
]
|
100
|
-
|
101
|
-
|
102
|
-
def _scale_backscatter(data: np.ndarray, scales: list) -> np.ndarray:
|
103
|
-
"""Scales backscatter values."""
|
104
|
-
unit_conversion_factor = 1e-8
|
105
|
-
scales_array = np.array(scales)
|
106
|
-
ind = np.where(scales_array != 100)
|
107
|
-
if ind[0].size > 0:
|
108
|
-
logging.info(f"{ind[0].size} profiles have not 100% scaling")
|
109
|
-
data[ind, :] *= scales_array[ind] / 100
|
110
|
-
return data * unit_conversion_factor
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/advance_methods.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/grid_methods.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/model_products.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/observation_products.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/products/product_resampling.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py
RENAMED
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py
RENAMED
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py
RENAMED
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py
RENAMED
File without changes
|
File without changes
|
{cloudnetpy-1.51.0 → cloudnetpy-1.51.1}/cloudnetpy/model_evaluation/tests/unit/test_tools.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|