cloudnetpy 1.56.7__py3-none-any.whl → 1.56.9__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/plotting/__init__.py +1 -1
- cloudnetpy/plotting/plot_meta.py +62 -15
- cloudnetpy/plotting/plotting.py +189 -88
- cloudnetpy/products/mwr_tools.py +6 -1
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.56.7.dist-info → cloudnetpy-1.56.9.dist-info}/METADATA +1 -1
- {cloudnetpy-1.56.7.dist-info → cloudnetpy-1.56.9.dist-info}/RECORD +10 -10
- {cloudnetpy-1.56.7.dist-info → cloudnetpy-1.56.9.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.56.7.dist-info → cloudnetpy-1.56.9.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.56.7.dist-info → cloudnetpy-1.56.9.dist-info}/top_level.txt +0 -0
cloudnetpy/plotting/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
from .plot_meta import PlotMeta
|
2
|
-
from .plotting import PlotParameters, generate_figure, plot_2d
|
2
|
+
from .plotting import Dimensions, PlotParameters, generate_figure, plot_2d
|
cloudnetpy/plotting/plot_meta.py
CHANGED
@@ -4,11 +4,28 @@ from typing import NamedTuple
|
|
4
4
|
|
5
5
|
|
6
6
|
class PlotMeta(NamedTuple):
|
7
|
+
"""
|
8
|
+
A class representing the metadata for plotting.
|
9
|
+
|
10
|
+
Attributes:
|
11
|
+
cmap: The colormap to be used for the plot.
|
12
|
+
clabel: The label for the colorbar. It can be a single string, a sequence
|
13
|
+
of tuples containing the label and units for each colorbar, or None
|
14
|
+
if no colorbar is needed.
|
15
|
+
plot_range: The range of values to be plotted. It can be a tuple
|
16
|
+
containing the minimum and maximum values, or None if the range should
|
17
|
+
be automatically determined.
|
18
|
+
log_scale: Whether to plot data values in a logarithmic scale.
|
19
|
+
moving_average: Whether to plot a moving average in a 1d plot.
|
20
|
+
contour: Whether to plot contours on top of a filled colormap.
|
21
|
+
"""
|
22
|
+
|
7
23
|
cmap: str = "viridis"
|
8
24
|
clabel: str | Sequence[tuple[str, str]] | None = None
|
9
25
|
plot_range: tuple[float, float] | None = None
|
10
26
|
log_scale: bool = False
|
11
27
|
moving_average: bool = True
|
28
|
+
contour: bool = False
|
12
29
|
|
13
30
|
|
14
31
|
_COLORS = {
|
@@ -112,6 +129,51 @@ ATTRIBUTES = {
|
|
112
129
|
plot_range=(0, 50 / 3600000),
|
113
130
|
)
|
114
131
|
},
|
132
|
+
"mwr-single": {
|
133
|
+
"temperature": PlotMeta(
|
134
|
+
cmap="coolwarm",
|
135
|
+
plot_range=(223.15, 323.15),
|
136
|
+
contour=True,
|
137
|
+
),
|
138
|
+
"potential_temperature": PlotMeta(
|
139
|
+
cmap="coolwarm",
|
140
|
+
plot_range=(260, 320),
|
141
|
+
contour=True,
|
142
|
+
),
|
143
|
+
"equivalent_potential_temperature": PlotMeta(
|
144
|
+
cmap="coolwarm",
|
145
|
+
plot_range=(260, 320),
|
146
|
+
contour=True,
|
147
|
+
),
|
148
|
+
"relative_humidity": PlotMeta(
|
149
|
+
plot_range=(0, 120),
|
150
|
+
contour=True,
|
151
|
+
),
|
152
|
+
"absolute_humidity": PlotMeta(
|
153
|
+
plot_range=(1e-4, 1e-2),
|
154
|
+
log_scale=True,
|
155
|
+
contour=True,
|
156
|
+
),
|
157
|
+
},
|
158
|
+
"mwr-multi": {
|
159
|
+
"temperature": PlotMeta(
|
160
|
+
cmap="coolwarm",
|
161
|
+
plot_range=(223.15, 323.15),
|
162
|
+
contour=True,
|
163
|
+
),
|
164
|
+
"potential_temperature": PlotMeta(
|
165
|
+
cmap="coolwarm",
|
166
|
+
plot_range=(260, 320),
|
167
|
+
contour=True,
|
168
|
+
),
|
169
|
+
"equivalent_potential_temperature": PlotMeta(
|
170
|
+
cmap="coolwarm", plot_range=(260, 320), contour=True
|
171
|
+
),
|
172
|
+
"relative_humidity": PlotMeta(
|
173
|
+
plot_range=(0, 120),
|
174
|
+
contour=True,
|
175
|
+
),
|
176
|
+
},
|
115
177
|
"fallback": {
|
116
178
|
"ier": PlotMeta(
|
117
179
|
plot_range=(2e-5, 6e-5),
|
@@ -222,21 +284,6 @@ ATTRIBUTES = {
|
|
222
284
|
cmap="RdBu_r",
|
223
285
|
plot_range=(223.15, 323.15),
|
224
286
|
),
|
225
|
-
"potential_temperature": PlotMeta(
|
226
|
-
cmap="RdBu_r",
|
227
|
-
plot_range=(260, 320),
|
228
|
-
),
|
229
|
-
"equivalent_potential_temperature": PlotMeta(
|
230
|
-
cmap="RdBu_r",
|
231
|
-
plot_range=(260, 320),
|
232
|
-
),
|
233
|
-
"absolute_humidity": PlotMeta(
|
234
|
-
plot_range=(1e-4, 1e-2),
|
235
|
-
log_scale=True,
|
236
|
-
),
|
237
|
-
"relative_humidity": PlotMeta(
|
238
|
-
plot_range=(0, 120),
|
239
|
-
),
|
240
287
|
"cloud_fraction": PlotMeta(
|
241
288
|
cmap="Blues",
|
242
289
|
plot_range=(0, 1),
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Misc. plotting routines for Cloudnet products."""
|
2
2
|
import os.path
|
3
|
+
import textwrap
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from datetime import date
|
5
6
|
|
@@ -22,6 +23,23 @@ from cloudnetpy.plotting.plot_meta import ATTRIBUTES, PlotMeta
|
|
22
23
|
|
23
24
|
@dataclass
|
24
25
|
class PlotParameters:
|
26
|
+
"""
|
27
|
+
Class representing the parameters for plotting.
|
28
|
+
|
29
|
+
Attributes:
|
30
|
+
dpi: The resolution of the plot in dots per inch.
|
31
|
+
max_y: Maximum y-axis value (km) in 2D time / height plots.
|
32
|
+
title: Whether to display the title of the plot.
|
33
|
+
subtitle: Whether to display the subtitle of the plot.
|
34
|
+
mark_data_gaps: Whether to mark data gaps in the plot.
|
35
|
+
grid: Whether to display grid lines in the plot.
|
36
|
+
edge_tick_labels: Whether to display tick labels on the edges of the plot.
|
37
|
+
show_sources: Whether to display the sources of plotted data (i.e.
|
38
|
+
instruments and model).
|
39
|
+
footer_text: The text to display in the footer of the plot.
|
40
|
+
plot_meta: Additional metadata for the plot.
|
41
|
+
"""
|
42
|
+
|
25
43
|
dpi: float = 120
|
26
44
|
max_y: int = 12
|
27
45
|
title: bool = True
|
@@ -35,14 +53,20 @@ class PlotParameters:
|
|
35
53
|
|
36
54
|
|
37
55
|
class Dimensions:
|
38
|
-
"""
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
56
|
+
"""
|
57
|
+
Dimensions of a generated figure in pixels. Elements such as the figure
|
58
|
+
title, labels, colorbar and legend are exluded from the margins.
|
59
|
+
|
60
|
+
Attributes:
|
61
|
+
width (int): Figure width in pixels.
|
62
|
+
height (int): Figure height in pixels.
|
63
|
+
margin_top (int): Space between top edge of image and plotted data in pixels.
|
64
|
+
margin_right (int): Space between right edge of image and plotted data
|
65
|
+
in pixels.
|
66
|
+
margin_bottom (int): Space between bottom edge of image and plotted
|
67
|
+
data in pixels.
|
68
|
+
margin_left (int): Space between left edge of image and plotted data in pixels.
|
69
|
+
"""
|
46
70
|
|
47
71
|
def __init__(self, fig, axes, pad_inches: float | None = None):
|
48
72
|
if pad_inches is None:
|
@@ -133,7 +157,7 @@ class FigureData:
|
|
133
157
|
raise PlottingError(msg)
|
134
158
|
return valid_variables, variable_indices
|
135
159
|
|
136
|
-
def _get_height(self) ->
|
160
|
+
def _get_height(self) -> ndarray | None:
|
137
161
|
m2km = 1e-3
|
138
162
|
file_type = getattr(self.file, "cloudnet_file_type", "")
|
139
163
|
if file_type == "model":
|
@@ -194,10 +218,11 @@ class SubPlot:
|
|
194
218
|
self.ax.set_title(title, fontsize=14)
|
195
219
|
|
196
220
|
def add_grid(self) -> None:
|
221
|
+
zorder = _get_zorder("grid")
|
197
222
|
self.ax.xaxis.set_minor_locator(AutoMinorLocator(4))
|
198
|
-
self.ax.grid(which="major", axis="x", color="k", lw=0.1)
|
199
|
-
self.ax.grid(which="minor", axis="x", lw=0.1, color="k", ls=":")
|
200
|
-
self.ax.grid(which="major", axis="y", lw=0.1, color="k", ls=":")
|
223
|
+
self.ax.grid(which="major", axis="x", color="k", lw=0.1, zorder=zorder)
|
224
|
+
self.ax.grid(which="minor", axis="x", lw=0.1, color="k", ls=":", zorder=zorder)
|
225
|
+
self.ax.grid(which="major", axis="y", lw=0.1, color="k", ls=":", zorder=zorder)
|
201
226
|
|
202
227
|
def add_sources(self, figure_data: FigureData) -> None:
|
203
228
|
source = getattr(self.variable, "source", None) or (
|
@@ -226,11 +251,20 @@ class SubPlot:
|
|
226
251
|
def set_xlabel(self) -> None:
|
227
252
|
self.ax.set_xlabel("Time (UTC)", fontsize=13)
|
228
253
|
|
229
|
-
def show_footer(self, fig: Figure):
|
254
|
+
def show_footer(self, fig: Figure, ax: Axes) -> None:
|
230
255
|
if isinstance(self.options.footer_text, str):
|
256
|
+
n = 50
|
257
|
+
if len(self.options.footer_text) > n:
|
258
|
+
wrapped_text = textwrap.fill(self.options.footer_text, n)
|
259
|
+
self.options.footer_text = "\n".join(wrapped_text.splitlines())
|
260
|
+
|
261
|
+
n_lines = self.options.footer_text.count("\n") + 1
|
262
|
+
y0 = ax.get_position().y0
|
263
|
+
y1 = ax.get_position().y1
|
264
|
+
y = (y1 - y0) * (n_lines * 0.06 + 0.1)
|
231
265
|
fig.text(
|
232
266
|
0.06,
|
233
|
-
-
|
267
|
+
y0 - y,
|
234
268
|
self.options.footer_text,
|
235
269
|
fontsize=11,
|
236
270
|
ha="left",
|
@@ -257,7 +291,7 @@ class Plot:
|
|
257
291
|
self._is_log = sub_plot.plot_meta.log_scale
|
258
292
|
self._ax = sub_plot.ax
|
259
293
|
|
260
|
-
def _convert_units(self) -> str
|
294
|
+
def _convert_units(self) -> str:
|
261
295
|
multiply, add = "multiply", "add"
|
262
296
|
units_conversion = {
|
263
297
|
"rainfall_rate": (multiply, 360000, "mm h$^{-1}$"),
|
@@ -276,6 +310,7 @@ class Plot:
|
|
276
310
|
self._data += conversion
|
277
311
|
self._data_orig += conversion
|
278
312
|
if units is not None:
|
313
|
+
self._plot_meta = self._plot_meta._replace(clabel=units)
|
279
314
|
return units
|
280
315
|
units = getattr(self.sub_plot.variable, "units", "")
|
281
316
|
return _reformat_units(units)
|
@@ -297,10 +332,10 @@ class Plot:
|
|
297
332
|
batch,
|
298
333
|
*self._get_y_limits(),
|
299
334
|
hatch="//",
|
300
|
-
facecolor="
|
301
|
-
edgecolor="
|
302
|
-
alpha=0.15,
|
335
|
+
facecolor="whitesmoke",
|
336
|
+
edgecolor="lightgrey",
|
303
337
|
label="_nolegend_",
|
338
|
+
zorder=_get_zorder("data_gap"),
|
304
339
|
)
|
305
340
|
|
306
341
|
def _mark_gaps(self, figure_data: FigureData) -> None:
|
@@ -351,7 +386,7 @@ class Plot:
|
|
351
386
|
self._data = data_new
|
352
387
|
figure_data.time_including_gaps = time_new
|
353
388
|
|
354
|
-
def _read_flagged_data(self, figure_data: FigureData) ->
|
389
|
+
def _read_flagged_data(self, figure_data: FigureData) -> ndarray:
|
355
390
|
flag_names = [
|
356
391
|
f"{self.sub_plot.variable.name}_quality_flag",
|
357
392
|
"temperature_quality_flag",
|
@@ -392,18 +427,16 @@ class Plot2D(Plot):
|
|
392
427
|
self._ax.fill_between(
|
393
428
|
time_batch,
|
394
429
|
*self._get_y_limits(),
|
395
|
-
facecolor="
|
430
|
+
facecolor="white",
|
396
431
|
alpha=0.7,
|
397
|
-
edgecolor="grey",
|
398
432
|
label="_nolegend_",
|
433
|
+
zorder=_get_zorder("flags"),
|
399
434
|
)
|
400
435
|
|
401
436
|
def _plot_segment_data(self, figure_data: FigureData) -> None:
|
402
|
-
def _hide_segments(
|
403
|
-
data_in: ma.MaskedArray,
|
404
|
-
) -> tuple[ma.MaskedArray, list, list]:
|
437
|
+
def _hide_segments() -> tuple[list, list]:
|
405
438
|
if self._plot_meta.clabel is None:
|
406
|
-
msg =
|
439
|
+
msg = "Missing clabel"
|
407
440
|
raise ValueError(msg)
|
408
441
|
labels = [x[0] for x in self._plot_meta.clabel]
|
409
442
|
colors = [x[1] for x in self._plot_meta.clabel]
|
@@ -411,11 +444,11 @@ class Plot2D(Plot):
|
|
411
444
|
indices = np.where(segments_to_hide)[0]
|
412
445
|
for ind in np.flip(indices):
|
413
446
|
del labels[ind], colors[ind]
|
414
|
-
|
415
|
-
|
416
|
-
return
|
447
|
+
self._data[self._data == ind] = ma.masked
|
448
|
+
self._data[self._data > ind] -= 1
|
449
|
+
return colors, labels
|
417
450
|
|
418
|
-
|
451
|
+
cbar, clabel = _hide_segments()
|
419
452
|
alt = self._screen_data_by_max_y(figure_data)
|
420
453
|
image = self._ax.pcolorfast(
|
421
454
|
figure_data.time_including_gaps,
|
@@ -424,6 +457,7 @@ class Plot2D(Plot):
|
|
424
457
|
cmap=ListedColormap(cbar),
|
425
458
|
vmin=-0.5,
|
426
459
|
vmax=len(cbar) - 0.5,
|
460
|
+
zorder=_get_zorder("data"),
|
427
461
|
)
|
428
462
|
colorbar = self._init_colorbar(image)
|
429
463
|
colorbar.set_ticks(np.arange(len(clabel)).tolist())
|
@@ -438,6 +472,7 @@ class Plot2D(Plot):
|
|
438
472
|
self._data, vmin, vmax = lin2log(self._data, vmin, vmax)
|
439
473
|
|
440
474
|
alt = self._screen_data_by_max_y(figure_data)
|
475
|
+
|
441
476
|
image = self._ax.pcolorfast(
|
442
477
|
figure_data.time_including_gaps,
|
443
478
|
alt,
|
@@ -445,14 +480,30 @@ class Plot2D(Plot):
|
|
445
480
|
cmap=plt.get_cmap(str(self._plot_meta.cmap)),
|
446
481
|
vmin=vmin,
|
447
482
|
vmax=vmax,
|
483
|
+
zorder=_get_zorder("data"),
|
448
484
|
)
|
449
485
|
cbar = self._init_colorbar(image)
|
450
486
|
cbar.set_label(str(self._plot_meta.clabel), fontsize=13)
|
487
|
+
|
451
488
|
if self._is_log:
|
452
489
|
cbar.set_ticks(np.arange(vmin, vmax + 1).tolist())
|
453
490
|
tick_labels = get_log_cbar_tick_labels(vmin, vmax)
|
454
491
|
cbar.ax.set_yticklabels(tick_labels)
|
455
492
|
|
493
|
+
if self._plot_meta.contour:
|
494
|
+
time_length = len(figure_data.time_including_gaps)
|
495
|
+
step = max(1, time_length // 200)
|
496
|
+
ind_time = np.arange(0, time_length, step)
|
497
|
+
self._ax.contour(
|
498
|
+
figure_data.time_including_gaps[ind_time],
|
499
|
+
alt,
|
500
|
+
self._data[ind_time, :].T,
|
501
|
+
levels=np.linspace(vmin, vmax, num=10),
|
502
|
+
colors="black",
|
503
|
+
linewidths=0.5,
|
504
|
+
zorder=_get_zorder("contour"),
|
505
|
+
)
|
506
|
+
|
456
507
|
def _screen_data_by_max_y(self, figure_data: FigureData) -> ndarray:
|
457
508
|
if figure_data.height is None:
|
458
509
|
msg = "No height information in the file."
|
@@ -466,7 +517,29 @@ class Plot2D(Plot):
|
|
466
517
|
|
467
518
|
|
468
519
|
class Plot1D(Plot):
|
469
|
-
def
|
520
|
+
def plot(self, figure_data: FigureData) -> None:
|
521
|
+
units = self._convert_units()
|
522
|
+
self._mark_gaps(figure_data)
|
523
|
+
self._ax.plot(
|
524
|
+
figure_data.time_including_gaps,
|
525
|
+
self._data,
|
526
|
+
label="_nolegend_",
|
527
|
+
**self._get_plot_options(),
|
528
|
+
zorder=_get_zorder("data"),
|
529
|
+
)
|
530
|
+
if self._plot_meta.moving_average:
|
531
|
+
self._plot_moving_average(figure_data)
|
532
|
+
self._fill_between_data_gaps(figure_data)
|
533
|
+
self.sub_plot.set_yax(ylabel=units, y_limits=self._get_y_limits())
|
534
|
+
pos = self._ax.get_position()
|
535
|
+
self._ax.set_position((pos.x0, pos.y0, pos.width * 0.965, pos.height))
|
536
|
+
if figure_data.is_mwrpy_product():
|
537
|
+
flags = self._read_flagged_data(figure_data)
|
538
|
+
if np.any(flags):
|
539
|
+
self._plot_flag_data(figure_data.time[flags], self._data_orig[flags])
|
540
|
+
self._add_legend()
|
541
|
+
|
542
|
+
def plot_tb(self, figure_data: FigureData, freq_ind: int) -> None:
|
470
543
|
self._data = self._data[:, freq_ind]
|
471
544
|
self._data_orig = self._data_orig[:, freq_ind]
|
472
545
|
is_bad_zenith = self._get_bad_zenith_profiles(figure_data)
|
@@ -475,11 +548,28 @@ class Plot1D(Plot):
|
|
475
548
|
flags = self._read_flagged_data(figure_data)[:, freq_ind]
|
476
549
|
flags[is_bad_zenith] = False
|
477
550
|
if np.any(flags):
|
478
|
-
self.
|
479
|
-
self.
|
551
|
+
self._plot_flag_data(figure_data.time[flags], self._data_orig[flags])
|
552
|
+
self._add_legend()
|
480
553
|
self.plot(figure_data)
|
554
|
+
self._show_frequency(figure_data, freq_ind)
|
555
|
+
|
556
|
+
def _show_frequency(self, figure_data: FigureData, freq_ind: int) -> None:
|
557
|
+
frequency = figure_data.file.variables["frequency"][freq_ind]
|
558
|
+
self._ax.text(
|
559
|
+
0.0,
|
560
|
+
-0.13,
|
561
|
+
f"Freq: {frequency:.2f} GHz",
|
562
|
+
transform=self._ax.transAxes,
|
563
|
+
fontsize=12,
|
564
|
+
color="dimgrey",
|
565
|
+
bbox={
|
566
|
+
"facecolor": "white",
|
567
|
+
"linewidth": 0,
|
568
|
+
"boxstyle": "round",
|
569
|
+
},
|
570
|
+
)
|
481
571
|
|
482
|
-
def
|
572
|
+
def _plot_flag_data(self, time: ndarray, values: ndarray) -> None:
|
483
573
|
self._ax.plot(
|
484
574
|
time,
|
485
575
|
values,
|
@@ -487,10 +577,10 @@ class Plot1D(Plot):
|
|
487
577
|
marker=".",
|
488
578
|
lw=0,
|
489
579
|
markersize=3,
|
490
|
-
zorder=
|
580
|
+
zorder=_get_zorder("flags"),
|
491
581
|
)
|
492
582
|
|
493
|
-
def
|
583
|
+
def _add_legend(self) -> None:
|
494
584
|
self._ax.legend(
|
495
585
|
["Flagged data"],
|
496
586
|
markerscale=3,
|
@@ -498,27 +588,6 @@ class Plot1D(Plot):
|
|
498
588
|
frameon=False,
|
499
589
|
)
|
500
590
|
|
501
|
-
def plot(self, figure_data: FigureData):
|
502
|
-
units = self._convert_units()
|
503
|
-
self._mark_gaps(figure_data)
|
504
|
-
self._ax.plot(
|
505
|
-
figure_data.time_including_gaps,
|
506
|
-
self._data,
|
507
|
-
label="_nolegend_",
|
508
|
-
**self._get_plot_options(),
|
509
|
-
)
|
510
|
-
if self._plot_meta.moving_average:
|
511
|
-
self._plot_moving_average(figure_data)
|
512
|
-
self._fill_between_data_gaps(figure_data)
|
513
|
-
self.sub_plot.set_yax(ylabel=units, y_limits=self._get_y_limits())
|
514
|
-
pos = self._ax.get_position()
|
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()
|
521
|
-
|
522
591
|
def _get_y_limits(self) -> tuple[float, float]:
|
523
592
|
percent_gap = 0.05
|
524
593
|
fallback = (-percent_gap, percent_gap)
|
@@ -553,12 +622,7 @@ class Plot1D(Plot):
|
|
553
622
|
|
554
623
|
return default_options
|
555
624
|
|
556
|
-
|
557
|
-
def _get_line_width(time: np.ndarray) -> float:
|
558
|
-
line_width = np.median(np.diff(time)) * 1000
|
559
|
-
return min(max(line_width, 0.25), 0.9)
|
560
|
-
|
561
|
-
def _plot_moving_average(self, figure_data: FigureData):
|
625
|
+
def _plot_moving_average(self, figure_data: FigureData) -> None:
|
562
626
|
time = figure_data.time.copy()
|
563
627
|
data = self._data_orig.copy()
|
564
628
|
data, time = self._get_unmasked_values(data, time)
|
@@ -567,20 +631,32 @@ class Plot1D(Plot):
|
|
567
631
|
gaps = self._find_time_gap_indices(time, max_gap_min=gap_time)
|
568
632
|
sma[gaps] = np.nan
|
569
633
|
if len(sma) == len(time):
|
570
|
-
self._ax.plot(
|
634
|
+
self._ax.plot(
|
635
|
+
time,
|
636
|
+
sma,
|
637
|
+
color="slateblue",
|
638
|
+
lw=2,
|
639
|
+
label="_nolegend_",
|
640
|
+
zorder=_get_zorder("mean_curve"),
|
641
|
+
)
|
642
|
+
|
643
|
+
@staticmethod
|
644
|
+
def _get_line_width(time: ndarray) -> float:
|
645
|
+
line_width = np.median(np.diff(time)) * 1000
|
646
|
+
return min(max(line_width, 0.25), 0.9)
|
571
647
|
|
572
648
|
@staticmethod
|
573
649
|
def _get_unmasked_values(
|
574
650
|
data: ma.MaskedArray,
|
575
|
-
time:
|
576
|
-
) -> tuple[
|
651
|
+
time: ndarray,
|
652
|
+
) -> tuple[ndarray, ndarray]:
|
577
653
|
if not ma.is_masked(data):
|
578
654
|
return data, time
|
579
655
|
good_values = ~data.mask
|
580
656
|
return data[good_values], time[good_values]
|
581
657
|
|
582
658
|
@staticmethod
|
583
|
-
def _get_bad_zenith_profiles(figure_data: FigureData) ->
|
659
|
+
def _get_bad_zenith_profiles(figure_data: FigureData) -> ndarray:
|
584
660
|
zenith_limit = 5
|
585
661
|
valid_pointing_status = 0
|
586
662
|
if "pointing_flag" in figure_data.file.variables:
|
@@ -598,10 +674,12 @@ class Plot1D(Plot):
|
|
598
674
|
|
599
675
|
@staticmethod
|
600
676
|
def _calculate_moving_average(
|
601
|
-
data:
|
602
|
-
) ->
|
677
|
+
data: ndarray, time: ndarray, window: float = 5
|
678
|
+
) -> ndarray:
|
603
679
|
if len(data) == 0:
|
604
680
|
return np.array([])
|
681
|
+
if len(data) == 1:
|
682
|
+
return data
|
605
683
|
time_delta_hours = np.median(np.diff(time))
|
606
684
|
window_size = int(window / 60 / time_delta_hours)
|
607
685
|
if window_size < 1:
|
@@ -622,6 +700,20 @@ def generate_figure(
|
|
622
700
|
output_filename: os.PathLike | str | None = None,
|
623
701
|
options: PlotParameters | None = None,
|
624
702
|
) -> Dimensions:
|
703
|
+
"""
|
704
|
+
Generate a figure based on the given filename and variables.
|
705
|
+
|
706
|
+
Args:
|
707
|
+
filename: The path to the input file.
|
708
|
+
variables: A list of variable names to plot.
|
709
|
+
show: Whether to display the figure. Defaults to True.
|
710
|
+
output_filename: The path to save the figure. Defaults to None.
|
711
|
+
options: Additional plot parameters. Defaults to None.
|
712
|
+
|
713
|
+
Returns:
|
714
|
+
Dimensions: Dimensions of a generated figure in pixels.
|
715
|
+
|
716
|
+
"""
|
625
717
|
if options is None:
|
626
718
|
options = PlotParameters()
|
627
719
|
|
@@ -660,7 +752,7 @@ def generate_figure(
|
|
660
752
|
subplot.set_xlabel()
|
661
753
|
|
662
754
|
if options.footer_text is not None:
|
663
|
-
subplot.show_footer(fig)
|
755
|
+
subplot.show_footer(fig, ax)
|
664
756
|
|
665
757
|
if output_filename:
|
666
758
|
plt.savefig(output_filename, bbox_inches="tight")
|
@@ -711,6 +803,34 @@ def _get_max_gap_in_minutes(figure_data: FigureData) -> float:
|
|
711
803
|
return max_allowed_gap.get(file_type, 10)
|
712
804
|
|
713
805
|
|
806
|
+
def _get_zorder(name: str) -> int:
|
807
|
+
zorder = {
|
808
|
+
"contour": 2,
|
809
|
+
"data_gap": 2,
|
810
|
+
"flags": 2,
|
811
|
+
}
|
812
|
+
return zorder.get(name, -1)
|
813
|
+
|
814
|
+
|
815
|
+
def find_batches_of_ones(array: ndarray) -> list[tuple[int, int]]:
|
816
|
+
"""Find batches of ones in a binary array."""
|
817
|
+
starts = np.where(np.diff(np.hstack(([0], array))) == 1)[0]
|
818
|
+
stops = np.where(np.diff(np.hstack((array, [0]))) == -1)[0]
|
819
|
+
return list(zip(starts, stops, strict=True))
|
820
|
+
|
821
|
+
|
822
|
+
def screen_completely_masked_profiles(time: ndarray, data: ma.MaskedArray) -> tuple:
|
823
|
+
if not ma.is_masked(data):
|
824
|
+
return time, data
|
825
|
+
good_ind = np.where(np.any(~data.mask, axis=1))[0]
|
826
|
+
if len(good_ind) == 0:
|
827
|
+
msg = "All values masked in the file."
|
828
|
+
raise PlottingError(msg)
|
829
|
+
good_ind = np.append(good_ind, good_ind[-1] + 1)
|
830
|
+
good_ind = np.clip(good_ind, 0, len(time) - 1)
|
831
|
+
return time[good_ind], data[good_ind, :]
|
832
|
+
|
833
|
+
|
714
834
|
def plot_2d(
|
715
835
|
data: ma.MaskedArray,
|
716
836
|
cmap: str = "viridis",
|
@@ -741,22 +861,3 @@ def plot_2d(
|
|
741
861
|
if xlim is not None:
|
742
862
|
plt.xlim(xlim)
|
743
863
|
plt.show()
|
744
|
-
|
745
|
-
|
746
|
-
def find_batches_of_ones(array: np.ndarray) -> list[tuple[int, int]]:
|
747
|
-
"""Find batches of ones in a binary array."""
|
748
|
-
starts = np.where(np.diff(np.hstack(([0], array))) == 1)[0]
|
749
|
-
stops = np.where(np.diff(np.hstack((array, [0]))) == -1)[0]
|
750
|
-
return list(zip(starts, stops, strict=True))
|
751
|
-
|
752
|
-
|
753
|
-
def screen_completely_masked_profiles(time: np.ndarray, data: ma.MaskedArray) -> tuple:
|
754
|
-
if not ma.is_masked(data):
|
755
|
-
return time, data
|
756
|
-
good_ind = np.where(np.any(~data.mask, axis=1))[0]
|
757
|
-
if len(good_ind) == 0:
|
758
|
-
msg = "All values masked in the file."
|
759
|
-
raise PlottingError(msg)
|
760
|
-
good_ind = np.append(good_ind, good_ind[-1] + 1)
|
761
|
-
good_ind = np.clip(good_ind, 0, len(time) - 1)
|
762
|
-
return time[good_ind], data[good_ind, :]
|
cloudnetpy/products/mwr_tools.py
CHANGED
@@ -4,9 +4,11 @@ from typing import Literal
|
|
4
4
|
import netCDF4
|
5
5
|
from mwrpy.level2.lev2_collocated import generate_lev2_multi as gen_multi
|
6
6
|
from mwrpy.level2.lev2_collocated import generate_lev2_single as gen_single
|
7
|
+
from mwrpy.level2.write_lev2_nc import MissingInputData
|
7
8
|
from mwrpy.version import __version__ as mwrpy_version
|
8
9
|
|
9
10
|
from cloudnetpy import output, utils
|
11
|
+
from cloudnetpy.exceptions import ValidTimeStampError
|
10
12
|
from cloudnetpy.products import product_tools
|
11
13
|
|
12
14
|
|
@@ -59,7 +61,10 @@ def _generate_product(
|
|
59
61
|
fun = gen_multi if product == "multi" else gen_single
|
60
62
|
with tempfile.TemporaryDirectory() as temp_dir:
|
61
63
|
coeffs = product_tools.get_read_mwrpy_coeffs(mwr_l1c_file, temp_dir)
|
62
|
-
|
64
|
+
try:
|
65
|
+
fun(None, mwr_l1c_file, output_file, coeff_files=coeffs)
|
66
|
+
except MissingInputData as err:
|
67
|
+
raise ValidTimeStampError from err
|
63
68
|
with (
|
64
69
|
netCDF4.Dataset(mwr_l1c_file, "r") as nc_input,
|
65
70
|
netCDF4.Dataset(output_file, "r+") as nc_output,
|
cloudnetpy/version.py
CHANGED
@@ -8,7 +8,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=L5yTOc1IUQ52ak-q9sb2zqco0z_mepJx_hFuzl6k6JY,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
|
@@ -90,9 +90,9 @@ cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py,sha256=YeeJdS2JO5F645z
|
|
90
90
|
cloudnetpy/model_evaluation/tests/unit/test_plotting.py,sha256=h9V8JKmrO4v9bOvv-UjRa06sZJQPhDNVHGBSImDdtkI,3277
|
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
|
-
cloudnetpy/plotting/__init__.py,sha256=
|
94
|
-
cloudnetpy/plotting/plot_meta.py,sha256=
|
95
|
-
cloudnetpy/plotting/plotting.py,sha256=
|
93
|
+
cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
|
94
|
+
cloudnetpy/plotting/plot_meta.py,sha256=NWI8ECKMypN5YyM9XKCAp1WEthbFlKMvilxqXmYSEK4,14631
|
95
|
+
cloudnetpy/plotting/plotting.py,sha256=AU7WWEfemlXXQ17IgybagQMx8JRJBF4y9DSv5DF_2Ho,30683
|
96
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
|
@@ -103,11 +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/mwr_tools.py,sha256=
|
106
|
+
cloudnetpy/products/mwr_tools.py,sha256=PRm5aCULccUehU-Byk55wYhhEHseMjoAjGBu5TSyHao,4621
|
107
107
|
cloudnetpy/products/product_tools.py,sha256=E8CSijBY8cr70BH2JFa0lGQ-RzI9EcHQ0Fzt8CQ8rY4,10442
|
108
108
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
109
|
-
cloudnetpy-1.56.
|
110
|
-
cloudnetpy-1.56.
|
111
|
-
cloudnetpy-1.56.
|
112
|
-
cloudnetpy-1.56.
|
113
|
-
cloudnetpy-1.56.
|
109
|
+
cloudnetpy-1.56.9.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
110
|
+
cloudnetpy-1.56.9.dist-info/METADATA,sha256=HgNqhO700XNh1uBoHccsDiA73n4R48XgLctfsB8Qczs,5733
|
111
|
+
cloudnetpy-1.56.9.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
112
|
+
cloudnetpy-1.56.9.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
113
|
+
cloudnetpy-1.56.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|