cloudnetpy 1.66.12__py3-none-any.whl → 1.66.13__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.
@@ -4,6 +4,7 @@ import csv
4
4
  import datetime
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  from operator import attrgetter
8
9
  from typing import Any, NamedTuple
9
10
 
@@ -11,8 +12,9 @@ import numpy as np
11
12
 
12
13
  from cloudnetpy import output, utils
13
14
  from cloudnetpy.cloudnetarray import CloudnetArray
14
- from cloudnetpy.exceptions import ValidTimeStampError
15
+ from cloudnetpy.exceptions import InconsistentDataError, ValidTimeStampError
15
16
  from cloudnetpy.instruments import instruments
17
+ from cloudnetpy.metadata import MetaData
16
18
 
17
19
 
18
20
  def radiometrics2nc(
@@ -65,7 +67,7 @@ def radiometrics2nc(
65
67
  if radiometrics.date is None:
66
68
  msg = "Failed to find valid timestamps from Radiometrics file(s)."
67
69
  raise ValidTimeStampError(msg)
68
- attributes = output.add_time_attribute({}, radiometrics.date)
70
+ attributes = output.add_time_attribute(ATTRIBUTES, radiometrics.date)
69
71
  output.update_attributes(radiometrics.data, attributes)
70
72
  return output.save_level1b(radiometrics, output_file, uuid)
71
73
 
@@ -91,6 +93,7 @@ class Radiometrics:
91
93
  self.raw_data: list[Record] = []
92
94
  self.data: dict = {}
93
95
  self.instrument = instruments.RADIOMETRICS
96
+ self.ranges: list[str] = []
94
97
 
95
98
  def read_raw_data(self) -> None:
96
99
  """Reads Radiometrics raw data."""
@@ -98,14 +101,21 @@ class Radiometrics:
98
101
  unknown_record_types = set()
99
102
  rows = []
100
103
  with open(self.filename, encoding="utf8") as infile:
101
- reader = csv.reader(infile)
104
+ reader = csv.reader(infile, skipinitialspace=True)
102
105
  for row in reader:
103
106
  if row[0] == "Record":
104
107
  if row[1] != "Date/Time":
105
108
  msg = "Unexpected header in Radiometrics file"
106
109
  raise RuntimeError(msg)
107
110
  record_type = int(row[2])
108
- record_columns[record_type] = row[3:]
111
+ columns = row[3:]
112
+ record_columns[record_type] = columns
113
+ if record_type in (10, 400):
114
+ self.ranges = [
115
+ column
116
+ for column in columns
117
+ if re.fullmatch(r"\d+\.\d+", column)
118
+ ]
109
119
  else:
110
120
  record_type = int(row[2])
111
121
  block_type = record_type // 10 * 10
@@ -125,33 +135,64 @@ class Radiometrics:
125
135
  )
126
136
  rows.append(record)
127
137
 
128
- # The rows should be in sequence but sort them just to be sure.
129
- rows.sort(key=attrgetter("row_number"))
130
-
131
- for data_row in rows:
132
- # Use the first row of a block and skip the rest which should
133
- # contain the same values in the first columns.
134
- if data_row.block_index == 0:
135
- self.raw_data.append(data_row)
138
+ self.raw_data = sorted(rows, key=attrgetter("row_number"))
136
139
 
137
140
  def read_data(self) -> None:
138
141
  """Reads values."""
139
142
  times = []
140
143
  lwps = []
141
144
  iwvs = []
145
+ irts = []
146
+ temps = []
147
+ rhs = []
148
+ ahs = []
149
+ block_titles = {}
142
150
  for record in self.raw_data:
143
- lwp = record.values.get("Lqint(mm)", record.values.get("Int. Liquid(mm)"))
144
- iwv = record.values.get("Vint(cm)", record.values.get("Int. Vapor(cm)"))
145
- if lwp is None and iwv is None:
146
- continue
147
- times.append(record.timestamp)
148
- if lwp is not None:
149
- lwps.append(float(lwp)) # mm => kg m-2
150
- if iwv is not None:
151
- iwvs.append(float(iwv) * 10) # cm => kg m-2
151
+ if record.block_type == 100:
152
+ block_type = int(record.values["Record Type"]) - 1
153
+ title = record.values["Title"]
154
+ block_titles[block_type] = title
155
+ if title := block_titles.get(record.block_type + record.block_index):
156
+ if title == "Temperature (K)":
157
+ temps.append(
158
+ [float(record.values[column]) for column in self.ranges]
159
+ )
160
+ elif title == "Relative Humidity (%)":
161
+ rhs.append([float(record.values[column]) for column in self.ranges])
162
+ elif title == "Vapor Density (g/m^3)":
163
+ ahs.append([float(record.values[column]) for column in self.ranges])
164
+ elif record.block_type == 10:
165
+ if record.block_index == 0:
166
+ lwp = record.values["Lqint(mm)"]
167
+ iwv = record.values["Vint(cm)"]
168
+ irt = record.values["Tir(K)"]
169
+ times.append(record.timestamp)
170
+ lwps.append(float(lwp))
171
+ iwvs.append(float(iwv))
172
+ irts.append([float(irt)])
173
+ temps.append(
174
+ [float(record.values[column]) for column in self.ranges]
175
+ )
176
+ elif record.block_index == 1:
177
+ ahs.append([float(record.values[column]) for column in self.ranges])
178
+ elif record.block_index == 2:
179
+ rhs.append([float(record.values[column]) for column in self.ranges])
180
+ elif record.block_type == 200:
181
+ irt = record.values["Tir(K)"]
182
+ irts.append([float(irt)])
183
+ elif record.block_type == 300:
184
+ lwp = record.values["Int. Liquid(mm)"]
185
+ iwv = record.values["Int. Vapor(cm)"]
186
+ times.append(record.timestamp)
187
+ lwps.append(float(lwp))
188
+ iwvs.append(float(iwv))
152
189
  self.data["time"] = np.array(times, dtype="datetime64[s]")
153
- self.data["lwp"] = np.array(lwps, dtype=float)
154
- self.data["iwv"] = np.array(iwvs, dtype=float)
190
+ self.data["lwp"] = np.array(lwps) # mm => kg m-2
191
+ self.data["iwv"] = np.array(iwvs) * 10 # cm => kg m-2
192
+ self.data["irt"] = np.array(irts)
193
+ self.data["temperature"] = np.array(temps)
194
+ self.data["relative_humidity"] = np.array(rhs) / 100 # % => 1
195
+ self.data["absolute_humidity"] = np.array(ahs) / 1000 # g m-3 => kg m-3
155
196
 
156
197
 
157
198
  class RadiometricsCombined:
@@ -165,8 +206,14 @@ class RadiometricsCombined:
165
206
  self.data = {}
166
207
  self.date = None
167
208
  for obj in objs:
209
+ if obj.ranges != objs[0].ranges:
210
+ msg = "Inconsistent range between files"
211
+ raise InconsistentDataError(msg)
168
212
  for key in obj.data:
169
213
  self.data = utils.append_data(self.data, key, obj.data[key])
214
+ ranges = [float(x) for x in objs[0].ranges]
215
+ self.data["range"] = np.array(ranges) * 1000 # m => km
216
+ self.data["height"] = self.data["range"] + self.site_meta["altitude"]
170
217
  self.instrument = instruments.RADIOMETRICS
171
218
 
172
219
  def screen_time(self, expected_date: datetime.date | None) -> None:
@@ -179,6 +226,8 @@ class RadiometricsCombined:
179
226
  if np.count_nonzero(valid_mask) == 0:
180
227
  raise ValidTimeStampError
181
228
  for key in self.data:
229
+ if key in ("range", "height"):
230
+ continue
182
231
  self.data[key] = self.data[key][valid_mask]
183
232
 
184
233
  def time_to_fractional_hours(self) -> None:
@@ -188,7 +237,12 @@ class RadiometricsCombined:
188
237
  def data_to_cloudnet_arrays(self) -> None:
189
238
  """Converts arrays to CloudnetArrays."""
190
239
  for key, array in self.data.items():
191
- self.data[key] = CloudnetArray(array, key)
240
+ dimensions = (
241
+ ("time", "range")
242
+ if key in ("temperature", "relative_humidity", "absolute_humidity")
243
+ else None
244
+ )
245
+ self.data[key] = CloudnetArray(array, key, dimensions=dimensions)
192
246
 
193
247
  def add_meta(self) -> None:
194
248
  """Adds some metadata."""
@@ -213,3 +267,11 @@ def _parse_datetime(text: str) -> datetime.datetime:
213
267
  minute,
214
268
  second,
215
269
  )
270
+
271
+
272
+ ATTRIBUTES = {
273
+ "irt": MetaData(
274
+ long_name="Infrared brightness temperatures",
275
+ units="K",
276
+ ),
277
+ }
cloudnetpy/metadata.py CHANGED
@@ -144,6 +144,11 @@ COMMON_ATTRIBUTES = {
144
144
  standard_name="relative_humidity",
145
145
  units="1",
146
146
  ),
147
+ "absolute_humidity": MetaData(
148
+ long_name="Absolute humidity",
149
+ standard_name="mass_concentration_of_water_vapor_in_air",
150
+ units="kg m-3",
151
+ ),
147
152
  "wind_speed": MetaData(
148
153
  long_name="Wind speed",
149
154
  standard_name="wind_speed",
@@ -137,6 +137,25 @@ ATTRIBUTES = {
137
137
  plot_range=(0, 50 / 3600000),
138
138
  )
139
139
  },
140
+ "mwr": {
141
+ "temperature": PlotMeta(
142
+ cmap="coolwarm",
143
+ plot_range=(223.15, 323.15),
144
+ contour=True,
145
+ time_smoothing_duration=_MWR_SINGLE_SMOOTHING,
146
+ ),
147
+ "relative_humidity": PlotMeta(
148
+ plot_range=(0, 120),
149
+ contour=True,
150
+ time_smoothing_duration=_MWR_SINGLE_SMOOTHING,
151
+ ),
152
+ "absolute_humidity": PlotMeta(
153
+ plot_range=(1e-4, 1e-2),
154
+ log_scale=True,
155
+ contour=True,
156
+ time_smoothing_duration=_MWR_SINGLE_SMOOTHING,
157
+ ),
158
+ },
140
159
  "mwr-single": {
141
160
  "temperature": PlotMeta(
142
161
  cmap="coolwarm",
@@ -627,13 +627,15 @@ class Plot1D(Plot):
627
627
  label = "Freq"
628
628
  value = figure_data.file.variables["frequency"][freq_ind]
629
629
  unit = "GHz"
630
- else:
630
+ elif "ir_wavelength" in figure_data.file.variables:
631
631
  label = "WL"
632
632
  variable = figure_data.file.variables["ir_wavelength"]
633
633
  # `ir_wavelength` is scalar in old files
634
634
  value = variable[:] if len(variable.shape) == 0 else variable[freq_ind]
635
635
  value /= 1e-6 # m to μm
636
636
  unit = "μm"
637
+ else:
638
+ return
637
639
  self._ax.text(
638
640
  0.0,
639
641
  -0.13,
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 66
3
- PATCH = 12
3
+ PATCH = 13
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.66.12
3
+ Version: 1.66.13
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -75,7 +75,7 @@ classified as ice, liquid, aerosol, insects, and so on.
75
75
  Subsequently, geophysical products such as ice water content can be
76
76
  retrieved in further processing steps. See [Illingworth et al. (2007)](https://doi.org/10.1175/BAMS-88-6-883) for more details about the concept.
77
77
 
78
- CloudnetPy is a rewritten version of the original Cloudnet Matlab code. It features several revised methods, extensive documentation, and more.
78
+ CloudnetPy is a rewritten version of the original Cloudnet MATLAB code. It features several revised methods, extensive documentation, and more.
79
79
 
80
80
  - CloudnetPy documentation: <https://actris-cloudnet.github.io/cloudnetpy/>
81
81
  - Cloudnet data portal: <https://cloudnet.fmi.fi>
@@ -84,13 +84,13 @@ CloudnetPy is a rewritten version of the original Cloudnet Matlab code. It featu
84
84
 
85
85
  ## Installation
86
86
 
87
- ### From PyPI
87
+ ### Option 1: From PyPI
88
88
 
89
89
  ```
90
90
  python3 -m pip install cloudnetpy
91
91
  ```
92
92
 
93
- ### From the source
93
+ ### Option 2: From the source
94
94
 
95
95
  ```sh
96
96
  git clone https://github.com/actris-cloudnet/cloudnetpy
@@ -101,6 +101,14 @@ python3 -m pip install --upgrade pip
101
101
  python3 -m pip install .
102
102
  ```
103
103
 
104
+ ## Verification
105
+
106
+ To verify the installation:
107
+
108
+ ```sh
109
+ cloudnetpy --help
110
+ ```
111
+
104
112
  ## Citing
105
113
 
106
114
  If you wish to acknowledge CloudnetPy in your publication, please cite:
@@ -123,12 +131,7 @@ pre-commit install
123
131
  Run unit tests:
124
132
 
125
133
  ```sh
126
- python3 -m pytest --flake-finder --flake-runs=2
127
- ```
128
-
129
- Run single unit test:
130
-
131
- ```sh
134
+ python3 -m pytest
132
135
  python3 -m pytest tests/unit/test_hatpro.py
133
136
  ```
134
137
 
@@ -136,9 +139,6 @@ Run end-to-end tests:
136
139
 
137
140
  ```sh
138
141
  python3 tests/e2e_test.py
139
- ```
140
-
141
- ```sh
142
142
  for f in cloudnetpy/model_evaluation/tests/e2e/*/main.py; do $f; done
143
143
  ```
144
144
 
@@ -5,11 +5,11 @@ cloudnetpy/concat_lib.py,sha256=RiL6fgaKjX2YyXl0BonbCjLXV2voIKPcQcdR9ZkQ8QA,1188
5
5
  cloudnetpy/constants.py,sha256=RDB9aqpBRztk3QVCFgsmi9fwhtLuit_0WJrt0D6sDcc,736
6
6
  cloudnetpy/datasource.py,sha256=FcWS77jz56gIzwnbafDLdj-HjAyu0P_VtY7gkeVZThU,7952
7
7
  cloudnetpy/exceptions.py,sha256=ns48useL9RN3mPh7CqIiLA19VI9OmVbyRsKTkwbThF8,1760
8
- cloudnetpy/metadata.py,sha256=v_VDo2vbdTxB0zIsfP69IcrwSKiRlLpsGdq6JPI4CoA,5306
8
+ cloudnetpy/metadata.py,sha256=DOGt7EQLS-AVJEszrUrpXr3gHVQv655FzeCzKrOPvoU,5477
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=JksYOwf9ORiR_QpoKrTe1JJwXpPYJj-wlwaZKCHoh3o,29744
12
- cloudnetpy/version.py,sha256=hDP4BM9G04Ifg9QoprxFNbcmG2SmWWByNxAAuXOyoDg,73
12
+ cloudnetpy/version.py,sha256=e037Rt3zV1cUMxgFOwb_aKwQpz6AqErxFNOUMI-uF4k,73
13
13
  cloudnetpy/categorize/__init__.py,sha256=s-SJaysvVpVVo5kidiruWQO6p3gv2TXwY1wEHYO5D6I,44
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=9-ymI6i1xASf-XAFyO87FaTfvq6bF89N1i_27OkUp-M,10104
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -49,7 +49,7 @@ cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo
49
49
  cloudnetpy/instruments/nc_lidar.py,sha256=5gQG9PApnNPrHmS9_zanl8HEYIQuGRpbnzC3wfTcOyQ,1705
50
50
  cloudnetpy/instruments/nc_radar.py,sha256=ctmb0tJHkRoz-Ic8UAw4_v4VfS27r22_4X_1s4mUAas,6990
51
51
  cloudnetpy/instruments/pollyxt.py,sha256=YuVEHr-BX31rtVOFsWGU-SQFAmcxpXL26eyCVMz_9hw,8933
52
- cloudnetpy/instruments/radiometrics.py,sha256=ECN_bSfcV8Evdgfho9-Dl8RThXkAhHzIEj4DPOawSTc,7626
52
+ cloudnetpy/instruments/radiometrics.py,sha256=7LeqfW3YTDOfhTV48hiGofBWw5_qlHjd-Lsjd9WDYho,10393
53
53
  cloudnetpy/instruments/rpg.py,sha256=IozvBJ8_qXTPqtp58FQwRsoI5_aI3-kycpXugZkS0d4,17462
54
54
  cloudnetpy/instruments/rpg_reader.py,sha256=ThztFuVrWxhmWVAfZTfQDeUiKK1XMTbtv08IBe8GK98,11364
55
55
  cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
@@ -100,8 +100,8 @@ cloudnetpy/model_evaluation/tests/unit/test_plotting.py,sha256=h9V8JKmrO4v9bOvv-
100
100
  cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V0qbqkpDuaTYvEIbaasl0nZ5gmTLR4eGC0glBQ,9724
101
101
  cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
102
102
  cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
103
- cloudnetpy/plotting/plot_meta.py,sha256=ZvaKU3eXy1KFxQomnsEu3mCYpwwBYKAYk7oAwOzAGSg,16143
104
- cloudnetpy/plotting/plotting.py,sha256=RGtRMfrZ6wFsFCXZ540I18a7p_O0x3S7FGIAoyuyOxw,35425
103
+ cloudnetpy/plotting/plot_meta.py,sha256=d7CIT4ltUsjKw0HNnOXPkaLdZbkSmTuq6AbKdbODfZE,16730
104
+ cloudnetpy/plotting/plotting.py,sha256=ufFKChNK9Z6uRRBtMLT5AvZiyWiakPLGgC9OHWaC8Z0,35504
105
105
  cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
106
106
  cloudnetpy/products/classification.py,sha256=KwAiBSgFwDqhM114NIgYiUjj8HoYc7gAlc8E1QgcSig,8207
107
107
  cloudnetpy/products/der.py,sha256=soypE7uSEP4uHUCCQVEhyXsKY6e9mzV9B_2S5GUizqk,12729
@@ -115,9 +115,9 @@ cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe5
115
115
  cloudnetpy/products/mwr_tools.py,sha256=rd7UC67O4fsIE5SaHVZ4qWvUJTj41ZGwgQWPwZzOM14,5377
116
116
  cloudnetpy/products/product_tools.py,sha256=01Zc6xV8CSuYcIcLpchFf5POL3_c629-YMNDZJ51udA,10853
117
117
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
118
- cloudnetpy-1.66.12.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
119
- cloudnetpy-1.66.12.dist-info/METADATA,sha256=D2tfe8TI2gBUjDEXvdYb3pICu6pJtoxcGQ0zuvhVXGg,5785
120
- cloudnetpy-1.66.12.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
121
- cloudnetpy-1.66.12.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
122
- cloudnetpy-1.66.12.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
123
- cloudnetpy-1.66.12.dist-info/RECORD,,
118
+ cloudnetpy-1.66.13.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
119
+ cloudnetpy-1.66.13.dist-info/METADATA,sha256=aySMKfVCWVBn0lC2BmrMk5akY14h2NlTe_WXUfjsMbw,5805
120
+ cloudnetpy-1.66.13.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
121
+ cloudnetpy-1.66.13.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
122
+ cloudnetpy-1.66.13.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
123
+ cloudnetpy-1.66.13.dist-info/RECORD,,