cloudnetpy 1.56.4__py3-none-any.whl → 1.56.6__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.
- cloudnetpy/exceptions.py +7 -0
- cloudnetpy/instruments/hatpro.py +3 -3
- cloudnetpy/plotting/plot_meta.py +9 -2
- cloudnetpy/plotting/plotting.py +126 -49
- cloudnetpy/products/__init__.py +1 -0
- cloudnetpy/products/mwr_tools.py +127 -0
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.56.4.dist-info → cloudnetpy-1.56.6.dist-info}/METADATA +1 -1
- {cloudnetpy-1.56.4.dist-info → cloudnetpy-1.56.6.dist-info}/RECORD +12 -13
- cloudnetpy/products/mwr_multi.py +0 -82
- cloudnetpy/products/mwr_single.py +0 -83
- {cloudnetpy-1.56.4.dist-info → cloudnetpy-1.56.6.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.56.4.dist-info → cloudnetpy-1.56.6.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.56.4.dist-info → cloudnetpy-1.56.6.dist-info}/top_level.txt +0 -0
cloudnetpy/exceptions.py
CHANGED
@@ -23,6 +23,13 @@ class RadarDataError(CloudnetException):
|
|
23
23
|
super().__init__(msg)
|
24
24
|
|
25
25
|
|
26
|
+
class PlottingError(CloudnetException):
|
27
|
+
"""Internal exception class."""
|
28
|
+
|
29
|
+
def __init__(self, msg: str):
|
30
|
+
super().__init__(msg)
|
31
|
+
|
32
|
+
|
26
33
|
class WeatherStationDataError(CloudnetException):
|
27
34
|
"""Internal exception class."""
|
28
35
|
|
cloudnetpy/instruments/hatpro.py
CHANGED
@@ -4,10 +4,10 @@ import logging
|
|
4
4
|
from collections import defaultdict
|
5
5
|
from pathlib import Path
|
6
6
|
|
7
|
-
import mwrpy
|
8
7
|
import netCDF4
|
9
8
|
import numpy as np
|
10
9
|
from mwrpy.level1.lev1_meta_nc import ATTRIBUTES_1B01
|
10
|
+
from mwrpy.level1.write_lev1_nc import lev1_to_nc
|
11
11
|
from mwrpy.version import __version__ as mwrpy_version
|
12
12
|
from numpy import ma
|
13
13
|
|
@@ -45,7 +45,7 @@ def hatpro2l1c(
|
|
45
45
|
"""
|
46
46
|
coeff_files = site_meta.get("coefficientFiles", None)
|
47
47
|
|
48
|
-
hatpro_raw =
|
48
|
+
hatpro_raw = lev1_to_nc(
|
49
49
|
"1C01",
|
50
50
|
mwr_dir,
|
51
51
|
output_file=output_file,
|
@@ -99,7 +99,7 @@ def hatpro2l1c(
|
|
99
99
|
|
100
100
|
|
101
101
|
class HatproL1c:
|
102
|
-
def __init__(self, hatpro
|
102
|
+
def __init__(self, hatpro, site_meta: dict):
|
103
103
|
self.raw_data = hatpro.raw_data
|
104
104
|
self.data = hatpro.data
|
105
105
|
self.date = hatpro.date.split("-")
|
cloudnetpy/plotting/plot_meta.py
CHANGED
@@ -224,12 +224,19 @@ ATTRIBUTES = {
|
|
224
224
|
),
|
225
225
|
"potential_temperature": PlotMeta(
|
226
226
|
cmap="RdBu_r",
|
227
|
-
plot_range=(260,
|
227
|
+
plot_range=(260, 320),
|
228
|
+
),
|
229
|
+
"equivalent_potential_temperature": PlotMeta(
|
230
|
+
cmap="RdBu_r",
|
231
|
+
plot_range=(260, 320),
|
228
232
|
),
|
229
233
|
"absolute_humidity": PlotMeta(
|
230
|
-
plot_range=(1e-
|
234
|
+
plot_range=(1e-4, 1e-2),
|
231
235
|
log_scale=True,
|
232
236
|
),
|
237
|
+
"relative_humidity": PlotMeta(
|
238
|
+
plot_range=(0, 120),
|
239
|
+
),
|
233
240
|
"cloud_fraction": PlotMeta(
|
234
241
|
cmap="Blues",
|
235
242
|
plot_range=(0, 1),
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -16,6 +16,7 @@ from matplotlib.transforms import Affine2D, Bbox
|
|
16
16
|
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
17
17
|
from numpy import ma, ndarray
|
18
18
|
|
19
|
+
from cloudnetpy.exceptions import PlottingError
|
19
20
|
from cloudnetpy.plotting.plot_meta import ATTRIBUTES, PlotMeta
|
20
21
|
|
21
22
|
|
@@ -129,7 +130,7 @@ class FigureData:
|
|
129
130
|
variable_indices.append(extracted_ind)
|
130
131
|
if not valid_variables:
|
131
132
|
msg = f"None of the variables {requested_variables} found in the file."
|
132
|
-
raise
|
133
|
+
raise PlottingError(msg)
|
133
134
|
return valid_variables, variable_indices
|
134
135
|
|
135
136
|
def _get_height(self) -> np.ndarray | None:
|
@@ -143,6 +144,10 @@ class FigureData:
|
|
143
144
|
return self.file.variables["range"][:] * m2km
|
144
145
|
return None
|
145
146
|
|
147
|
+
def is_mwrpy_product(self) -> bool:
|
148
|
+
cloudnet_file_type = getattr(self.file, "cloudnet_file_type", "")
|
149
|
+
return cloudnet_file_type in ("mwr-single", "mwr-multi")
|
150
|
+
|
146
151
|
def __len__(self) -> int:
|
147
152
|
return len(self.variables)
|
148
153
|
|
@@ -177,7 +182,7 @@ class SubPlot:
|
|
177
182
|
ylabel: str | None = None,
|
178
183
|
y_limits: tuple[float, float] | None = None,
|
179
184
|
) -> None:
|
180
|
-
label = ylabel
|
185
|
+
label = ylabel if ylabel is not None else "Height (km)"
|
181
186
|
self.ax.set_ylabel(label, fontsize=13)
|
182
187
|
if y_limits is not None:
|
183
188
|
self.ax.set_ylim(*y_limits)
|
@@ -236,9 +241,7 @@ class SubPlot:
|
|
236
241
|
if self.options.plot_meta is not None:
|
237
242
|
return self.options.plot_meta
|
238
243
|
fallback = ATTRIBUTES["fallback"].get(self.variable.name, PlotMeta())
|
239
|
-
|
240
|
-
return fallback
|
241
|
-
file_attributes = ATTRIBUTES.get(file_type, {})
|
244
|
+
file_attributes = ATTRIBUTES.get(file_type or "", {})
|
242
245
|
plot_meta = file_attributes.get(self.variable.name, fallback)
|
243
246
|
if plot_meta.clabel is None:
|
244
247
|
plot_meta = plot_meta._replace(clabel=_reformat_units(self.variable.units))
|
@@ -249,10 +252,34 @@ class Plot:
|
|
249
252
|
def __init__(self, sub_plot: SubPlot):
|
250
253
|
self.sub_plot = sub_plot
|
251
254
|
self._data = sub_plot.variable[:]
|
255
|
+
self._data_orig = self._data.copy()
|
252
256
|
self._plot_meta = sub_plot.plot_meta
|
253
257
|
self._is_log = sub_plot.plot_meta.log_scale
|
254
258
|
self._ax = sub_plot.ax
|
255
259
|
|
260
|
+
def _convert_units(self) -> str | None:
|
261
|
+
multiply, add = "multiply", "add"
|
262
|
+
units_conversion = {
|
263
|
+
"rainfall_rate": (multiply, 360000, "mm h$^{-1}$"),
|
264
|
+
"air_pressure": (multiply, 0.01, "hPa"),
|
265
|
+
"relative_humidity": (multiply, 100, "%"),
|
266
|
+
"rainfall_amount": (multiply, 1000, "mm"),
|
267
|
+
"air_temperature": (add, -273.15, "\u00B0C"),
|
268
|
+
}
|
269
|
+
conversion_method, conversion, units = units_conversion.get(
|
270
|
+
self.sub_plot.variable.name, (multiply, 1, None)
|
271
|
+
)
|
272
|
+
if conversion_method == multiply:
|
273
|
+
self._data *= conversion
|
274
|
+
self._data_orig *= conversion
|
275
|
+
elif conversion_method == add:
|
276
|
+
self._data += conversion
|
277
|
+
self._data_orig += conversion
|
278
|
+
if units is not None:
|
279
|
+
return units
|
280
|
+
units = getattr(self.sub_plot.variable, "units", "")
|
281
|
+
return _reformat_units(units)
|
282
|
+
|
256
283
|
def _get_y_limits(self) -> tuple[float, float]:
|
257
284
|
return 0, self.sub_plot.options.max_y
|
258
285
|
|
@@ -273,6 +300,7 @@ class Plot:
|
|
273
300
|
facecolor="grey",
|
274
301
|
edgecolor="black",
|
275
302
|
alpha=0.15,
|
303
|
+
label="_nolegend_",
|
276
304
|
)
|
277
305
|
|
278
306
|
def _mark_gaps(self, figure_data: FigureData) -> None:
|
@@ -282,6 +310,10 @@ class Plot:
|
|
282
310
|
msg = "Time values outside the range 0-24."
|
283
311
|
raise ValueError(msg)
|
284
312
|
max_gap_fraction_hour = _get_max_gap_in_minutes(figure_data) / 60
|
313
|
+
|
314
|
+
if figure_data.file.cloudnet_file_type == "model":
|
315
|
+
time, data = screen_completely_masked_profiles(time, data)
|
316
|
+
|
285
317
|
gap_indices = np.where(np.diff(time) > max_gap_fraction_hour)[0]
|
286
318
|
if not ma.is_masked(data):
|
287
319
|
mask_new = np.zeros(data.shape)
|
@@ -319,9 +351,21 @@ class Plot:
|
|
319
351
|
self._data = data_new
|
320
352
|
figure_data.time_including_gaps = time_new
|
321
353
|
|
354
|
+
def _read_flagged_data(self, figure_data: FigureData) -> np.ndarray:
|
355
|
+
flag_names = [
|
356
|
+
f"{self.sub_plot.variable.name}_quality_flag",
|
357
|
+
"temperature_quality_flag",
|
358
|
+
"quality_flag",
|
359
|
+
]
|
360
|
+
for flag_name in flag_names:
|
361
|
+
if flag_name in figure_data.file.variables:
|
362
|
+
return figure_data.file.variables[flag_name][:] > 0
|
363
|
+
return np.array([])
|
364
|
+
|
322
365
|
|
323
366
|
class Plot2D(Plot):
|
324
367
|
def plot(self, figure_data: FigureData):
|
368
|
+
self._convert_units()
|
325
369
|
self._mark_gaps(figure_data)
|
326
370
|
if self.sub_plot.variable.name == "cloud_fraction":
|
327
371
|
self._data[self._data == 0] = ma.masked
|
@@ -335,6 +379,25 @@ class Plot2D(Plot):
|
|
335
379
|
if figure_data.options.mark_data_gaps:
|
336
380
|
self._fill_between_data_gaps(figure_data)
|
337
381
|
|
382
|
+
if figure_data.is_mwrpy_product():
|
383
|
+
self._fill_flagged_data(figure_data)
|
384
|
+
|
385
|
+
def _fill_flagged_data(self, figure_data: FigureData) -> None:
|
386
|
+
flags = self._read_flagged_data(figure_data)
|
387
|
+
batches = find_batches_of_ones(flags)
|
388
|
+
for batch in batches:
|
389
|
+
if batch[0] == batch[1]:
|
390
|
+
continue
|
391
|
+
time_batch = figure_data.time[batch[0]], figure_data.time[batch[1]]
|
392
|
+
self._ax.fill_between(
|
393
|
+
time_batch,
|
394
|
+
*self._get_y_limits(),
|
395
|
+
facecolor="whitesmoke",
|
396
|
+
alpha=0.7,
|
397
|
+
edgecolor="grey",
|
398
|
+
label="_nolegend_",
|
399
|
+
)
|
400
|
+
|
338
401
|
def _plot_segment_data(self, figure_data: FigureData) -> None:
|
339
402
|
def _hide_segments(
|
340
403
|
data_in: ma.MaskedArray,
|
@@ -403,22 +466,35 @@ class Plot2D(Plot):
|
|
403
466
|
|
404
467
|
|
405
468
|
class Plot1D(Plot):
|
406
|
-
def plot_tb(self, figure_data: FigureData,
|
407
|
-
|
469
|
+
def plot_tb(self, figure_data: FigureData, freq_ind: int):
|
470
|
+
self._data = self._data[:, freq_ind]
|
471
|
+
self._data_orig = self._data_orig[:, freq_ind]
|
472
|
+
is_bad_zenith = self._get_bad_zenith_profiles(figure_data)
|
473
|
+
self._data[is_bad_zenith] = ma.masked
|
474
|
+
self._data_orig[is_bad_zenith] = ma.masked
|
475
|
+
flags = self._read_flagged_data(figure_data)[:, freq_ind]
|
476
|
+
flags[is_bad_zenith] = False
|
477
|
+
if np.any(flags):
|
478
|
+
self.plot_flag_data(figure_data.time[flags], self._data_orig[flags])
|
479
|
+
self.add_legend()
|
480
|
+
self.plot(figure_data)
|
481
|
+
|
482
|
+
def plot_flag_data(self, time: np.ndarray, values: np.ndarray) -> None:
|
408
483
|
self._ax.plot(
|
409
|
-
|
410
|
-
|
484
|
+
time,
|
485
|
+
values,
|
411
486
|
color="salmon",
|
412
487
|
marker=".",
|
413
488
|
lw=0,
|
414
489
|
markersize=3,
|
490
|
+
zorder=10,
|
415
491
|
)
|
416
|
-
|
492
|
+
|
493
|
+
def add_legend(self):
|
417
494
|
self._ax.legend(
|
418
|
-
["Flagged data"
|
495
|
+
["Flagged data"],
|
419
496
|
markerscale=3,
|
420
497
|
numpoints=1,
|
421
|
-
reverse=True,
|
422
498
|
frameon=False,
|
423
499
|
)
|
424
500
|
|
@@ -428,6 +504,7 @@ class Plot1D(Plot):
|
|
428
504
|
self._ax.plot(
|
429
505
|
figure_data.time_including_gaps,
|
430
506
|
self._data,
|
507
|
+
label="_nolegend_",
|
431
508
|
**self._get_plot_options(),
|
432
509
|
)
|
433
510
|
if self._plot_meta.moving_average:
|
@@ -436,6 +513,11 @@ class Plot1D(Plot):
|
|
436
513
|
self.sub_plot.set_yax(ylabel=units, y_limits=self._get_y_limits())
|
437
514
|
pos = self._ax.get_position()
|
438
515
|
self._ax.set_position((pos.x0, pos.y0, pos.width * 0.965, pos.height))
|
516
|
+
if figure_data.is_mwrpy_product():
|
517
|
+
flags = self._read_flagged_data(figure_data)
|
518
|
+
if np.any(flags):
|
519
|
+
self.plot_flag_data(figure_data.time[flags], self._data_orig[flags])
|
520
|
+
self.add_legend()
|
439
521
|
|
440
522
|
def _get_y_limits(self) -> tuple[float, float]:
|
441
523
|
percent_gap = 0.05
|
@@ -452,26 +534,6 @@ class Plot1D(Plot):
|
|
452
534
|
return fallback
|
453
535
|
return min_y, max_y
|
454
536
|
|
455
|
-
def _convert_units(self) -> str | None:
|
456
|
-
multiply, add = "multiply", "add"
|
457
|
-
units_conversion = {
|
458
|
-
"rainfall_rate": (multiply, 360000, "mm h$^{-1}$"),
|
459
|
-
"air_pressure": (multiply, 0.01, "hPa"),
|
460
|
-
"relative_humidity": (multiply, 100, "%"),
|
461
|
-
"rainfall_amount": (multiply, 1000, "mm"),
|
462
|
-
"air_temperature": (add, -273.15, "\u00B0C"),
|
463
|
-
}
|
464
|
-
conversion_method, conversion, units = units_conversion.get(
|
465
|
-
self.sub_plot.variable.name, (multiply, 1, None)
|
466
|
-
)
|
467
|
-
if conversion_method == multiply:
|
468
|
-
self._data *= conversion
|
469
|
-
elif conversion_method == add:
|
470
|
-
self._data += conversion
|
471
|
-
if units is not None:
|
472
|
-
return units
|
473
|
-
return _reformat_units(self.sub_plot.variable.units)
|
474
|
-
|
475
537
|
def _get_plot_options(self) -> dict:
|
476
538
|
default_options = {
|
477
539
|
"color": "lightblue",
|
@@ -497,13 +559,14 @@ class Plot1D(Plot):
|
|
497
559
|
return min(max(line_width, 0.25), 0.9)
|
498
560
|
|
499
561
|
def _plot_moving_average(self, figure_data: FigureData):
|
500
|
-
time = figure_data.
|
501
|
-
data
|
562
|
+
time = figure_data.time.copy()
|
563
|
+
data = self._data_orig.copy()
|
564
|
+
data, time = self._get_unmasked_values(data, time)
|
502
565
|
sma = self._calculate_moving_average(data, time, window=5)
|
503
566
|
gap_time = _get_max_gap_in_minutes(figure_data)
|
504
567
|
gaps = self._find_time_gap_indices(time, max_gap_min=gap_time)
|
505
568
|
sma[gaps] = np.nan
|
506
|
-
self._ax.plot(time, sma, color="slateblue", lw=2)
|
569
|
+
self._ax.plot(time, sma, color="slateblue", lw=2, label="_nolegend_")
|
507
570
|
|
508
571
|
@staticmethod
|
509
572
|
def _get_unmasked_values(
|
@@ -515,24 +578,17 @@ class Plot1D(Plot):
|
|
515
578
|
good_values = ~data.mask
|
516
579
|
return data[good_values], time[good_values]
|
517
580
|
|
518
|
-
|
581
|
+
@staticmethod
|
582
|
+
def _get_bad_zenith_profiles(figure_data: FigureData) -> np.ndarray:
|
519
583
|
zenith_limit = 5
|
520
|
-
|
521
|
-
self._data = self._data[:, ind]
|
522
|
-
flagged_data = ma.masked_all_like(figure_data.time)
|
584
|
+
valid_pointing_status = 0
|
523
585
|
if "pointing_flag" in figure_data.file.variables:
|
524
586
|
pointing_flag = figure_data.file.variables["pointing_flag"][:]
|
525
587
|
zenith_angle = figure_data.file.variables["zenith_angle"][:]
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
# Store flagged data points for visualization
|
531
|
-
valid_ind = np.where(quality_flag != status)[0]
|
532
|
-
if len(valid_ind) > 0:
|
533
|
-
flagged_data[valid_ind] = self._data[valid_ind]
|
534
|
-
self._data[quality_flag != status] = ma.masked
|
535
|
-
return flagged_data
|
588
|
+
is_bad_zenith = np.abs(zenith_angle) > zenith_limit
|
589
|
+
is_bad_pointing = pointing_flag != valid_pointing_status
|
590
|
+
return is_bad_zenith | is_bad_pointing
|
591
|
+
return np.zeros_like(figure_data.time, dtype=bool)
|
536
592
|
|
537
593
|
@staticmethod
|
538
594
|
def _find_time_gap_indices(time: ndarray, max_gap_min: float) -> ndarray:
|
@@ -633,6 +689,8 @@ def _reformat_units(unit: str) -> str:
|
|
633
689
|
"sr-1 m-1": "sr$^{-1}$ m$^{-1}$",
|
634
690
|
"kg m-2": "kg m$^{-2}$",
|
635
691
|
"kg m-3": "kg m$^{-3}$",
|
692
|
+
"g m-3": "g m$^{-3}$",
|
693
|
+
"g m-2": "g m$^{-2}$",
|
636
694
|
"kg m-2 s-1": "kg m$^{-2}$ s$^{-1}$",
|
637
695
|
"dB km-1": "dB km$^{-1}$",
|
638
696
|
"rad km-1": "rad km$^{-1}$",
|
@@ -682,3 +740,22 @@ def plot_2d(
|
|
682
740
|
if xlim is not None:
|
683
741
|
plt.xlim(xlim)
|
684
742
|
plt.show()
|
743
|
+
|
744
|
+
|
745
|
+
def find_batches_of_ones(array: np.ndarray) -> list[tuple[int, int]]:
|
746
|
+
"""Find batches of ones in a binary array."""
|
747
|
+
starts = np.where(np.diff(np.hstack(([0], array))) == 1)[0]
|
748
|
+
stops = np.where(np.diff(np.hstack((array, [0]))) == -1)[0]
|
749
|
+
return list(zip(starts, stops, strict=True))
|
750
|
+
|
751
|
+
|
752
|
+
def screen_completely_masked_profiles(time: np.ndarray, data: ma.MaskedArray) -> tuple:
|
753
|
+
if not ma.is_masked(data):
|
754
|
+
return time, data
|
755
|
+
good_ind = np.where(np.any(~data.mask, axis=1))[0]
|
756
|
+
if len(good_ind) == 0:
|
757
|
+
msg = "All values masked in the file."
|
758
|
+
raise PlottingError(msg)
|
759
|
+
good_ind = np.append(good_ind, good_ind[-1] + 1)
|
760
|
+
good_ind = np.clip(good_ind, 0, len(time) - 1)
|
761
|
+
return time[good_ind], data[good_ind, :]
|
cloudnetpy/products/__init__.py
CHANGED
@@ -0,0 +1,127 @@
|
|
1
|
+
import tempfile
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
import netCDF4
|
5
|
+
from mwrpy.level2.lev2_collocated import generate_lev2_multi as gen_multi
|
6
|
+
from mwrpy.level2.lev2_collocated import generate_lev2_single as gen_single
|
7
|
+
from mwrpy.version import __version__ as mwrpy_version
|
8
|
+
|
9
|
+
from cloudnetpy import output, utils
|
10
|
+
from cloudnetpy.products import product_tools
|
11
|
+
|
12
|
+
|
13
|
+
def generate_mwr_single(
|
14
|
+
mwr_l1c_file: str, output_file: str, uuid: str | None = None
|
15
|
+
) -> str:
|
16
|
+
"""
|
17
|
+
Generates MWR single-pointing product including liquid water path, integrated
|
18
|
+
water vapor, etc. from zenith measurements.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
mwr_l1c_file: The Level 1C MWR file to be processed.
|
22
|
+
output_file: The file path where the output file should be saved.
|
23
|
+
uuid: The UUID, if any, associated with the output file. Defaults to None.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
UUID of generated file.
|
27
|
+
|
28
|
+
Example:
|
29
|
+
>>> generate_mwr_single('input_mwr_l1c_file', 'output_file', 'abcdefg1234567')
|
30
|
+
"""
|
31
|
+
return _generate_product(mwr_l1c_file, output_file, uuid, "single")
|
32
|
+
|
33
|
+
|
34
|
+
def generate_mwr_multi(
|
35
|
+
mwr_l1c_file: str, output_file: str, uuid: str | None = None
|
36
|
+
) -> str:
|
37
|
+
"""
|
38
|
+
Generates MWR multiple-pointing product, including relative humidity profiles,
|
39
|
+
etc. from scanning measurements.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
mwr_l1c_file: The input file in MWR L1C format.
|
43
|
+
output_file: The location where the output file should be generated.
|
44
|
+
uuid: The UUID for the MWR multi product, defaults to None if
|
45
|
+
not provided.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
UUID of generated file.
|
49
|
+
"""
|
50
|
+
return _generate_product(mwr_l1c_file, output_file, uuid, "multi")
|
51
|
+
|
52
|
+
|
53
|
+
def _generate_product(
|
54
|
+
mwr_l1c_file: str,
|
55
|
+
output_file: str,
|
56
|
+
uuid: str | None,
|
57
|
+
product: Literal["multi", "single"],
|
58
|
+
) -> str:
|
59
|
+
fun = gen_multi if product == "multi" else gen_single
|
60
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
61
|
+
coeffs = product_tools.get_read_mwrpy_coeffs(mwr_l1c_file, temp_dir)
|
62
|
+
fun(None, mwr_l1c_file, output_file, coeff_files=coeffs)
|
63
|
+
with (
|
64
|
+
netCDF4.Dataset(mwr_l1c_file, "r") as nc_input,
|
65
|
+
netCDF4.Dataset(output_file, "r+") as nc_output,
|
66
|
+
):
|
67
|
+
mwr = Mwr(nc_input, nc_output, uuid)
|
68
|
+
return mwr.harmonize(product)
|
69
|
+
|
70
|
+
|
71
|
+
class Mwr:
|
72
|
+
def __init__(
|
73
|
+
self, nc_l1c: netCDF4.Dataset, nc_l2: netCDF4.Dataset, uuid: str | None
|
74
|
+
):
|
75
|
+
self.nc_l1c = nc_l1c
|
76
|
+
self.nc_l2 = nc_l2
|
77
|
+
self.uuid = uuid if uuid is not None else utils.get_uuid()
|
78
|
+
|
79
|
+
def harmonize(self, product: Literal["multi", "single"]) -> str:
|
80
|
+
self._truncate_global_attributes()
|
81
|
+
self._copy_variable_values()
|
82
|
+
self._copy_global_attributes()
|
83
|
+
self._fix_variable_attributes()
|
84
|
+
self._write_missing_global_attributes(product)
|
85
|
+
return self.uuid
|
86
|
+
|
87
|
+
def _truncate_global_attributes(self):
|
88
|
+
for attr in self.nc_l2.ncattrs():
|
89
|
+
delattr(self.nc_l2, attr)
|
90
|
+
|
91
|
+
def _copy_variable_values(self):
|
92
|
+
keys = ("latitude", "longitude", "altitude")
|
93
|
+
for var in keys:
|
94
|
+
if var in self.nc_l2.variables:
|
95
|
+
self.nc_l2.variables[var][:] = self.nc_l1c.variables[var][:]
|
96
|
+
|
97
|
+
def _copy_global_attributes(self):
|
98
|
+
keys = ("year", "month", "day", "location", "source")
|
99
|
+
output.copy_global(self.nc_l1c, self.nc_l2, keys)
|
100
|
+
|
101
|
+
def _fix_variable_attributes(self):
|
102
|
+
output.replace_attribute_with_standard_value(
|
103
|
+
self.nc_l2,
|
104
|
+
(
|
105
|
+
"lwp",
|
106
|
+
"iwv",
|
107
|
+
"temperature",
|
108
|
+
"azimuth_angle",
|
109
|
+
"latitude",
|
110
|
+
"longitude",
|
111
|
+
"altitude",
|
112
|
+
),
|
113
|
+
("units", "long_name", "standard_name"),
|
114
|
+
)
|
115
|
+
|
116
|
+
def _write_missing_global_attributes(self, product: Literal["multi", "single"]):
|
117
|
+
output.add_standard_global_attributes(self.nc_l2, self.uuid)
|
118
|
+
product_type = "multiple-pointing" if product == "multi" else "single-pointing"
|
119
|
+
self.nc_l2.title = f"MWR {product_type} from {self.nc_l1c.location}"
|
120
|
+
self.nc_l2.cloudnet_file_type = f"mwr-{product}"
|
121
|
+
output.fix_time_attributes(self.nc_l2)
|
122
|
+
self.nc_l2.history = (
|
123
|
+
f"{utils.get_time()} - MWR {product_type} file created \n"
|
124
|
+
f"{self.nc_l1c.history}"
|
125
|
+
)
|
126
|
+
self.nc_l2.source_file_uuids = self.nc_l1c.file_uuid
|
127
|
+
self.nc_l2.mwrpy_version = mwrpy_version
|
cloudnetpy/version.py
CHANGED
@@ -3,12 +3,12 @@ cloudnetpy/cloudnetarray.py,sha256=vSI6hqozhS1ntNIgY2kqTt7N6BF_xJtUii_vsuyxTno,6
|
|
3
3
|
cloudnetpy/concat_lib.py,sha256=YK5ho5msqwNxpPtPT8f2OewIJ8hTrbhCkaxHaBKLTI0,9809
|
4
4
|
cloudnetpy/constants.py,sha256=OMp3pKHCZmdKyRvfO35E7vE3FrS_DHIs_GJuexJLCQk,600
|
5
5
|
cloudnetpy/datasource.py,sha256=-6oLC5bsn9sIoaN0glV88owFyTeGRsW1ZVJSV8rM5rE,7813
|
6
|
-
cloudnetpy/exceptions.py,sha256=
|
6
|
+
cloudnetpy/exceptions.py,sha256=nA6YieylwKSp5KQOh3DhhcTmUBsd0tBZnedgmlWck-w,1415
|
7
7
|
cloudnetpy/metadata.py,sha256=Bcu1a9UyUq61jomuZ0_6hYIOzf61e5qCXeiwLm46ikw,5040
|
8
8
|
cloudnetpy/output.py,sha256=jD1pfBb4OQhVOrlhPEk-8FAi4bUW7zjAL468r6BPkJg,14586
|
9
9
|
cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
cloudnetpy/utils.py,sha256=yY5a5HLuAks2uzA4XbbqsGFEmXoyqECn_TjD3sMa0lI,27193
|
11
|
-
cloudnetpy/version.py,sha256=
|
11
|
+
cloudnetpy/version.py,sha256=KcLO5w70O8aXSbgK5nqMVq2YM5wAvubh8Uf9D1Q4FNc,72
|
12
12
|
cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
|
13
13
|
cloudnetpy/categorize/atmos.py,sha256=cax3iRmvr7S-VkUZqz0JCfAN3WEsUVbGfH4zSHy1APo,12384
|
14
14
|
cloudnetpy/categorize/atmos_utils.py,sha256=wndpwJxc2-QnNTkV8tc8I11Vs_WkNz9sVMX1fuGgUC4,3777
|
@@ -33,7 +33,7 @@ cloudnetpy/instruments/cl61d.py,sha256=ycJGvUqNU2KHhECbrSehtWRnvg1vKFHhvMeQpjpdC
|
|
33
33
|
cloudnetpy/instruments/cloudnet_instrument.py,sha256=RG5HJxGM6p0F-IGyr85fvOizcMmgx48OeD_XeIsrgSU,3367
|
34
34
|
cloudnetpy/instruments/copernicus.py,sha256=AT0AtMhGSKzPWEqXsfAda6zeaw4g0Jr5dqIyfeu4FP0,6327
|
35
35
|
cloudnetpy/instruments/galileo.py,sha256=FyFYh1JhWed1d6yvpsv3rdBzHUTjkfOPw5HEQz4Wssw,4545
|
36
|
-
cloudnetpy/instruments/hatpro.py,sha256=
|
36
|
+
cloudnetpy/instruments/hatpro.py,sha256=TEwPyUsT8J17uDPwcB5B8nPDo0f6UAvg5bDjG8tKLdA,8119
|
37
37
|
cloudnetpy/instruments/instruments.py,sha256=GcUEbQFPHUhRTnp300AZ2PK0O2d2EPIGtHqCX6dy99M,3133
|
38
38
|
cloudnetpy/instruments/lufft.py,sha256=nozeiMRMz7I6q_FwmlxDGhWeJlqTuNh6ru39-M4K3BI,3629
|
39
39
|
cloudnetpy/instruments/mira.py,sha256=TfozpYivQAThZ_rV3gLzZpz2QyJFWOF0RXdzA4521rM,9332
|
@@ -91,9 +91,9 @@ cloudnetpy/model_evaluation/tests/unit/test_plotting.py,sha256=h9V8JKmrO4v9bOvv-
|
|
91
91
|
cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V0qbqkpDuaTYvEIbaasl0nZ5gmTLR4eGC0glBQ,9724
|
92
92
|
cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
|
93
93
|
cloudnetpy/plotting/__init__.py,sha256=2-8x8d1AfAhfU15RwWhusD0Wot_g6Ob_jJoywbrTC7A,95
|
94
|
-
cloudnetpy/plotting/plot_meta.py,sha256=
|
95
|
-
cloudnetpy/plotting/plotting.py,sha256=
|
96
|
-
cloudnetpy/products/__init__.py,sha256=
|
94
|
+
cloudnetpy/plotting/plot_meta.py,sha256=xbiEU9Okx5L_WG7KtVnljxjpCYcgqyOLywBc49gibSQ,13033
|
95
|
+
cloudnetpy/plotting/plotting.py,sha256=NjeWF5u7fSvNVCwvOu8RyxQ7bApm7K74CF4I2sDCR40,26868
|
96
|
+
cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
|
97
97
|
cloudnetpy/products/classification.py,sha256=J_FOMUSyxvFaT-hvdKVVcKPtuQ0u3V9PsV5xaIKzMjg,7843
|
98
98
|
cloudnetpy/products/der.py,sha256=HAdPvbJySEqkIwDrdZDPnli_wnN2qwm72_D1a82ZWIs,12398
|
99
99
|
cloudnetpy/products/drizzle.py,sha256=BY2HvJeWt_ps6KKCGXwUUNRTy78q0cQM8bOCCoj8TWA,10803
|
@@ -103,12 +103,11 @@ cloudnetpy/products/ier.py,sha256=IcGPlQahbwJjp3vOOrxWSYW2FPzbSV0KQL5eYECc4kU,77
|
|
103
103
|
cloudnetpy/products/iwc.py,sha256=MUPuVKWgqOuuLRCGk3QY74uBZB_7P1qlinlP8nEvz9o,10124
|
104
104
|
cloudnetpy/products/lwc.py,sha256=TbIR6kMwjbm63ed5orB1pkqx9ZBm8C5TF2JmT8WKdKI,18794
|
105
105
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
106
|
-
cloudnetpy/products/
|
107
|
-
cloudnetpy/products/mwr_single.py,sha256=4KyxeFg7AphEJg5P7ey8SXacyNAG3PGDOvnksvZj3R8,3168
|
106
|
+
cloudnetpy/products/mwr_tools.py,sha256=TsVEqNwzoDv90TgzUSnJjMuc3C1KQ-hwsIZ8t0IdDJ4,4407
|
108
107
|
cloudnetpy/products/product_tools.py,sha256=E8CSijBY8cr70BH2JFa0lGQ-RzI9EcHQ0Fzt8CQ8rY4,10442
|
109
108
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
110
|
-
cloudnetpy-1.56.
|
111
|
-
cloudnetpy-1.56.
|
112
|
-
cloudnetpy-1.56.
|
113
|
-
cloudnetpy-1.56.
|
114
|
-
cloudnetpy-1.56.
|
109
|
+
cloudnetpy-1.56.6.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
110
|
+
cloudnetpy-1.56.6.dist-info/METADATA,sha256=kr-kst-d8xZSZA6N5IpUhsoRdab_rPEEXAmWlPS1pXo,5733
|
111
|
+
cloudnetpy-1.56.6.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
112
|
+
cloudnetpy-1.56.6.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
113
|
+
cloudnetpy-1.56.6.dist-info/RECORD,,
|
cloudnetpy/products/mwr_multi.py
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
import tempfile
|
2
|
-
from tempfile import NamedTemporaryFile
|
3
|
-
|
4
|
-
import netCDF4
|
5
|
-
from mwrpy.level2.write_lev2_nc import MissingInputData, lev2_to_nc
|
6
|
-
from mwrpy.version import __version__ as mwrpy_version
|
7
|
-
|
8
|
-
from cloudnetpy import output, utils
|
9
|
-
from cloudnetpy.exceptions import MissingInputFileError
|
10
|
-
from cloudnetpy.products import product_tools
|
11
|
-
|
12
|
-
|
13
|
-
def generate_mwr_multi(
|
14
|
-
mwr_l1c_file: str,
|
15
|
-
output_file: str,
|
16
|
-
uuid: str | None = None,
|
17
|
-
) -> str:
|
18
|
-
file_uuid = uuid if uuid is not None else utils.get_uuid()
|
19
|
-
|
20
|
-
with (
|
21
|
-
NamedTemporaryFile() as temp_file,
|
22
|
-
NamedTemporaryFile() as abs_hum_file,
|
23
|
-
NamedTemporaryFile() as rel_hum_file,
|
24
|
-
NamedTemporaryFile() as t_pot_file,
|
25
|
-
NamedTemporaryFile() as eq_temp_file,
|
26
|
-
tempfile.TemporaryDirectory() as temp_dir,
|
27
|
-
):
|
28
|
-
coeffs = product_tools.get_read_mwrpy_coeffs(mwr_l1c_file, temp_dir)
|
29
|
-
|
30
|
-
for prod, file in zip(
|
31
|
-
("2P02", "2P03", "2P04", "2P07", "2P08"),
|
32
|
-
(temp_file, abs_hum_file, rel_hum_file, t_pot_file, eq_temp_file),
|
33
|
-
strict=True,
|
34
|
-
):
|
35
|
-
try:
|
36
|
-
lev2_to_nc(
|
37
|
-
prod,
|
38
|
-
mwr_l1c_file,
|
39
|
-
file.name,
|
40
|
-
coeff_files=coeffs,
|
41
|
-
temp_file=temp_file.name if prod not in ("2P02", "2P03") else None,
|
42
|
-
hum_file=abs_hum_file.name
|
43
|
-
if prod not in ("2P02", "2P03")
|
44
|
-
else None,
|
45
|
-
)
|
46
|
-
except MissingInputData as err:
|
47
|
-
raise MissingInputFileError from err
|
48
|
-
|
49
|
-
with (
|
50
|
-
netCDF4.Dataset(output_file, "w", format="NETCDF4_CLASSIC") as nc_output,
|
51
|
-
netCDF4.Dataset(mwr_l1c_file, "r") as nc_l1c,
|
52
|
-
netCDF4.Dataset(temp_file.name, "r") as nc_temp,
|
53
|
-
netCDF4.Dataset(rel_hum_file.name, "r") as nc_rel_hum,
|
54
|
-
netCDF4.Dataset(t_pot_file.name, "r") as nc_t_pot,
|
55
|
-
netCDF4.Dataset(eq_temp_file.name, "r") as nc_eq_temp,
|
56
|
-
):
|
57
|
-
nc_output.createDimension("time", len(nc_temp.variables["time"][:]))
|
58
|
-
nc_output.createDimension("height", len(nc_temp.variables["height"][:]))
|
59
|
-
|
60
|
-
for source, variables in (
|
61
|
-
(nc_l1c, ("latitude", "longitude", "altitude")),
|
62
|
-
(nc_temp, ("time", "height", "temperature")),
|
63
|
-
(nc_rel_hum, ("relative_humidity",)),
|
64
|
-
(nc_t_pot, ("potential_temperature",)),
|
65
|
-
(nc_eq_temp, ("equivalent_potential_temperature",)),
|
66
|
-
):
|
67
|
-
output.copy_variables(source, nc_output, variables)
|
68
|
-
|
69
|
-
output.add_standard_global_attributes(nc_output, file_uuid)
|
70
|
-
output.copy_global(nc_l1c, nc_output, ("year", "month", "day", "location"))
|
71
|
-
nc_output.title = f"MWR multiple-pointing from {nc_l1c.location}"
|
72
|
-
nc_output.cloudnet_file_type = "mwr-multi"
|
73
|
-
nc_output.mwrpy_version = mwrpy_version
|
74
|
-
output.fix_time_attributes(nc_output)
|
75
|
-
nc_output.history = (
|
76
|
-
f"{utils.get_time()} - MWR multiple-pointing product created \n"
|
77
|
-
f"{nc_l1c.history}"
|
78
|
-
)
|
79
|
-
nc_output.source_file_uuids = nc_l1c.file_uuid
|
80
|
-
nc_output.source = nc_l1c.source
|
81
|
-
|
82
|
-
return file_uuid
|
@@ -1,83 +0,0 @@
|
|
1
|
-
import tempfile
|
2
|
-
from tempfile import NamedTemporaryFile
|
3
|
-
|
4
|
-
import netCDF4
|
5
|
-
from mwrpy.level2.write_lev2_nc import lev2_to_nc
|
6
|
-
from mwrpy.version import __version__ as mwrpy_version
|
7
|
-
|
8
|
-
from cloudnetpy import output, utils
|
9
|
-
from cloudnetpy.products import product_tools
|
10
|
-
|
11
|
-
|
12
|
-
def generate_mwr_single(
|
13
|
-
mwr_l1c_file: str,
|
14
|
-
output_file: str,
|
15
|
-
uuid: str | None = None,
|
16
|
-
) -> str:
|
17
|
-
file_uuid = uuid if uuid is not None else utils.get_uuid()
|
18
|
-
|
19
|
-
with (
|
20
|
-
NamedTemporaryFile() as lwp_file,
|
21
|
-
NamedTemporaryFile() as iwv_file,
|
22
|
-
NamedTemporaryFile() as t_prof_file,
|
23
|
-
NamedTemporaryFile() as abs_hum_file,
|
24
|
-
tempfile.TemporaryDirectory() as temp_dir,
|
25
|
-
):
|
26
|
-
coeffs = product_tools.get_read_mwrpy_coeffs(mwr_l1c_file, temp_dir)
|
27
|
-
|
28
|
-
for prod, file in zip(
|
29
|
-
("2I01", "2I02", "2P01", "2P03"),
|
30
|
-
(lwp_file, iwv_file, t_prof_file, abs_hum_file),
|
31
|
-
strict=True,
|
32
|
-
):
|
33
|
-
lev2_to_nc(prod, mwr_l1c_file, file.name, coeff_files=coeffs)
|
34
|
-
|
35
|
-
with (
|
36
|
-
netCDF4.Dataset(output_file, "w", format="NETCDF4_CLASSIC") as nc_output,
|
37
|
-
netCDF4.Dataset(lwp_file.name, "r") as nc_lwp,
|
38
|
-
netCDF4.Dataset(iwv_file.name, "r") as nc_iwv,
|
39
|
-
netCDF4.Dataset(abs_hum_file.name, "r") as nc_hum,
|
40
|
-
netCDF4.Dataset(t_prof_file.name, "r") as nc_t_prof,
|
41
|
-
netCDF4.Dataset(mwr_l1c_file, "r") as nc_l1c,
|
42
|
-
):
|
43
|
-
nc_output.createDimension("height", len(nc_t_prof.variables["height"][:]))
|
44
|
-
nc_output.createDimension("time", len(nc_lwp.variables["time"][:]))
|
45
|
-
|
46
|
-
for source, variables in (
|
47
|
-
(nc_iwv, ("iwv",)),
|
48
|
-
(nc_hum, ("absolute_humidity",)),
|
49
|
-
(nc_t_prof, ("temperature", "height")),
|
50
|
-
(nc_l1c, ("latitude", "longitude", "altitude")),
|
51
|
-
(
|
52
|
-
nc_lwp,
|
53
|
-
(
|
54
|
-
"time",
|
55
|
-
"lwp",
|
56
|
-
"lwp_random_error",
|
57
|
-
"lwp_offset",
|
58
|
-
"lwp_systematic_error",
|
59
|
-
"azimuth_angle",
|
60
|
-
),
|
61
|
-
),
|
62
|
-
):
|
63
|
-
output.copy_variables(source, nc_output, variables)
|
64
|
-
|
65
|
-
output.add_standard_global_attributes(nc_output, file_uuid)
|
66
|
-
output.copy_global(nc_l1c, nc_output, ("year", "month", "day", "location"))
|
67
|
-
nc_output.title = f"MWR single-pointing from {nc_l1c.location}"
|
68
|
-
nc_output.cloudnet_file_type = "mwr-single"
|
69
|
-
nc_output.mwrpy_version = mwrpy_version
|
70
|
-
output.fix_time_attributes(nc_output)
|
71
|
-
output.replace_attribute_with_standard_value(
|
72
|
-
nc_output,
|
73
|
-
("lwp", "iwv", "temperature", "azimuth_angle"),
|
74
|
-
("units", "long_name", "standard_name"),
|
75
|
-
)
|
76
|
-
nc_output.history = (
|
77
|
-
f"{utils.get_time()} - MWR single-pointing product created \n"
|
78
|
-
f"{nc_l1c.history}"
|
79
|
-
)
|
80
|
-
nc_output.source_file_uuids = nc_l1c.file_uuid
|
81
|
-
nc_output.source = nc_l1c.source
|
82
|
-
|
83
|
-
return file_uuid
|
File without changes
|
File without changes
|
File without changes
|