cloudnetpy 1.71.1__py3-none-any.whl → 1.71.3__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  import binascii
2
+ import datetime
2
3
  import re
3
- from datetime import datetime, timezone
4
4
  from typing import NamedTuple
5
5
 
6
6
  import numpy as np
@@ -11,6 +11,34 @@ from cloudnetpy.instruments import instruments
11
11
  from cloudnetpy.instruments.ceilometer import Ceilometer
12
12
 
13
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
+
14
42
  class Cs135(Ceilometer):
15
43
  def __init__(
16
44
  self,
@@ -34,25 +62,34 @@ class Cs135(Ceilometer):
34
62
  tilt_angles = []
35
63
  range_resolutions = []
36
64
 
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)
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)
56
93
 
57
94
  if len(timestamps) == 0:
58
95
  msg = "No valid timestamps found in the file"
@@ -77,7 +114,7 @@ class Cs135(Ceilometer):
77
114
  self.data["time"] = utils.datetime2decimal_hours(timestamps)
78
115
  self.data["zenith_angle"] = np.median(tilt_angles)
79
116
 
80
- def _check_timestamp(self, timestamp: datetime) -> None:
117
+ def _check_timestamp(self, timestamp: datetime.datetime) -> None:
81
118
  timestamp_components = str(timestamp.date()).split("-")
82
119
  if (
83
120
  self.expected_date is not None
@@ -119,19 +156,33 @@ def _read_message(message: bytes) -> Message:
119
156
  f"got {actual_checksum:04x}"
120
157
  )
121
158
  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])}"
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}"
125
164
  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)
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)
135
186
 
136
187
 
137
188
  def _read_backscatter(data: bytes, n_gates: int) -> np.ndarray:
@@ -11,6 +11,7 @@ from doppy.product.turbulence import HorizontalWind, Options, Turbulence, Vertic
11
11
  from scipy.interpolate import LinearNDInterpolator, NearestNDInterpolator
12
12
 
13
13
  import cloudnetpy
14
+ from cloudnetpy.exceptions import ValidTimeStampError
14
15
  from cloudnetpy.output import copy_variables
15
16
  from cloudnetpy.utils import get_time, get_uuid
16
17
 
@@ -131,6 +132,8 @@ def _horizontal_wind_from_doppler_lidar_file(
131
132
  vmask = np.array(nc["vwind"][:].mask, dtype=np.bool_)
132
133
  V = np.sqrt(uwind**2 + vwind**2)
133
134
  mask = umask | vmask
135
+ if np.all(mask):
136
+ raise ValidTimeStampError
134
137
  t = np.broadcast_to(time[:, None], mask.shape)[~mask]
135
138
  h = np.broadcast_to(height[None, :], mask.shape)[~mask]
136
139
  interp_linear = LinearNDInterpolator(list(zip(t, h, strict=False)), V[~mask])
@@ -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"
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 71
3
- PATCH = 1
3
+ PATCH = 3
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudnetpy
3
- Version: 1.71.1
3
+ Version: 1.71.3
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -9,7 +9,7 @@ cloudnetpy/metadata.py,sha256=BDEpgwZ58PHznc1gi11gtNNV4kFiMAmlHnF4huTy7nw,5982
9
9
  cloudnetpy/output.py,sha256=lq4YSeMT_d-j4rlQkKm9KIZ8boupTBBBKV1eUawpmCI,15672
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cloudnetpy/utils.py,sha256=U0iMIKPiKLrLVAfs_u9pPuoWYW1RJHcM8dbLF9a4yIA,29796
12
- cloudnetpy/version.py,sha256=Jpj6HUmEXjg2ZHK10S2ho3h_9Khi4cQ_qHcHcBrM7AU,72
12
+ cloudnetpy/version.py,sha256=tsjo_vDV3Q5KrCVFxMEpgs-KBRK1bdqml45ItHKQVio,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=s-SJaysvVpVVo5kidiruWQO6p3gv2TXwY1wEHYO5D6I,44
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=RcmbKxm2COkE7WEya0mK3yX5rzUbrewRVh3ekm01RtM,10598
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -34,7 +34,7 @@ cloudnetpy/categorize/attenuations/melting_attenuation.py,sha256=9c9xoZHtGUbjFYJ
34
34
  cloudnetpy/categorize/attenuations/rain_attenuation.py,sha256=qazJzRyXf9vbjJhh4yiFmABI4L57j5W_6YZ-6qjRiBI,2839
35
35
  cloudnetpy/instruments/__init__.py,sha256=2vAdceXCNxZhTujhArLf4NjYOfUdkhLpGM-NQa_LXdg,470
36
36
  cloudnetpy/instruments/basta.py,sha256=Lb_EhQTI93S5Bd9osDbCE_tC8gZreRsHz7D2_dFOjmE,3793
37
- cloudnetpy/instruments/campbell_scientific.py,sha256=2WHfBKQjtRSl0AqvtPeX7G8Hdi3Dn0WbvoAppFOMbA8,5270
37
+ cloudnetpy/instruments/campbell_scientific.py,sha256=c3f2W9aA52ghJ35_J994y7FShA3W-Bxk52BCbEm_gt0,6975
38
38
  cloudnetpy/instruments/ceilo.py,sha256=xrI7iYNftKvGZf-3C_ESUNsu-QhXV43iWkDuKp3biZU,9552
39
39
  cloudnetpy/instruments/ceilometer.py,sha256=pdmLVljsuciyKpaGxWxJ_f1IrJK-UrkBC0lSeuirLlU,12095
40
40
  cloudnetpy/instruments/cl61d.py,sha256=g6DNBFju3wYhLFl32DKmC8pUup7y-EupXoUU0fuoGGA,1990
@@ -109,17 +109,17 @@ cloudnetpy/products/der.py,sha256=soypE7uSEP4uHUCCQVEhyXsKY6e9mzV9B_2S5GUizqk,12
109
109
  cloudnetpy/products/drizzle.py,sha256=58C9Mo6oRXR8KpbVPghbJvHvFX9GfS3xUp058pbf0qw,10804
110
110
  cloudnetpy/products/drizzle_error.py,sha256=4GwlHRtNbk9ks7bGtXCco-wXbcDOKeAQwKmbhzut6Qk,6132
111
111
  cloudnetpy/products/drizzle_tools.py,sha256=HLxUQ89mFNo6IIe6Cj3ZH-TPkJdpMxKCOt4cOOmcLs0,11002
112
- cloudnetpy/products/epsilon.py,sha256=fthijjUAa7bEhoObRiTOGr4_uRgCpXSfqxIOPD2NGT4,7617
113
- cloudnetpy/products/ier.py,sha256=70jyYrhO-kAHuQ3CpDVcyKKuHT3c9Eby6ADHHlPjAFY,5986
114
- cloudnetpy/products/iwc.py,sha256=FQtYFYnMNuDnjuK1ee3zOfzPtKsoWH95CXtEz1iplZI,9462
112
+ cloudnetpy/products/epsilon.py,sha256=sVtOcl-tckvZCmM34etRQCzLg5NjvbHlt_5InRCjm1E,7734
113
+ cloudnetpy/products/ier.py,sha256=XW4gg_H-JWMWKToMqLVl6v8kx1S65GBwclWDCn1EfSk,5991
114
+ cloudnetpy/products/iwc.py,sha256=WcPdAZx3zW0zaNJNp2vpAD4JnG0NJjFmCUAhDWzNxMg,9459
115
115
  cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18962
116
116
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
117
117
  cloudnetpy/products/mwr_tools.py,sha256=rd7UC67O4fsIE5SaHVZ4qWvUJTj41ZGwgQWPwZzOM14,5377
118
118
  cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
119
119
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
120
- cloudnetpy-1.71.1.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
- cloudnetpy-1.71.1.dist-info/METADATA,sha256=-4IIIVCrDs5UmXIrXQmlWzZ-Q9sV5SVodXEjgh_kdqc,5872
122
- cloudnetpy-1.71.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
123
- cloudnetpy-1.71.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
- cloudnetpy-1.71.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
- cloudnetpy-1.71.1.dist-info/RECORD,,
120
+ cloudnetpy-1.71.3.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
+ cloudnetpy-1.71.3.dist-info/METADATA,sha256=kMugQZKRTpx-hY86nY2NMhW5Q6oYE5p4a0eeM6H8vPo,5872
122
+ cloudnetpy-1.71.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
123
+ cloudnetpy-1.71.3.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
+ cloudnetpy-1.71.3.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
+ cloudnetpy-1.71.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5