cloudnetpy 1.49.9__py3-none-any.whl → 1.87.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.
- cloudnetpy/categorize/__init__.py +1 -2
- cloudnetpy/categorize/atmos_utils.py +297 -67
- cloudnetpy/categorize/attenuation.py +31 -0
- cloudnetpy/categorize/attenuations/__init__.py +37 -0
- cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +332 -156
- cloudnetpy/categorize/classify.py +127 -125
- cloudnetpy/categorize/containers.py +107 -76
- cloudnetpy/categorize/disdrometer.py +40 -0
- cloudnetpy/categorize/droplet.py +23 -21
- cloudnetpy/categorize/falling.py +53 -24
- cloudnetpy/categorize/freezing.py +25 -12
- cloudnetpy/categorize/insects.py +35 -23
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/lidar.py +36 -41
- cloudnetpy/categorize/melting.py +34 -26
- cloudnetpy/categorize/model.py +84 -37
- cloudnetpy/categorize/mwr.py +18 -14
- cloudnetpy/categorize/radar.py +215 -102
- cloudnetpy/cli.py +578 -0
- cloudnetpy/cloudnetarray.py +43 -89
- cloudnetpy/concat_lib.py +218 -78
- cloudnetpy/constants.py +28 -10
- cloudnetpy/datasource.py +61 -86
- cloudnetpy/exceptions.py +49 -20
- cloudnetpy/instruments/__init__.py +5 -0
- cloudnetpy/instruments/basta.py +29 -12
- cloudnetpy/instruments/bowtie.py +135 -0
- cloudnetpy/instruments/ceilo.py +138 -115
- cloudnetpy/instruments/ceilometer.py +164 -80
- cloudnetpy/instruments/cl61d.py +21 -5
- cloudnetpy/instruments/cloudnet_instrument.py +74 -36
- cloudnetpy/instruments/copernicus.py +108 -30
- cloudnetpy/instruments/da10.py +54 -0
- cloudnetpy/instruments/disdrometer/common.py +126 -223
- cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
- cloudnetpy/instruments/disdrometer/thies.py +254 -87
- cloudnetpy/instruments/fd12p.py +201 -0
- cloudnetpy/instruments/galileo.py +65 -23
- cloudnetpy/instruments/hatpro.py +123 -49
- cloudnetpy/instruments/instruments.py +113 -1
- cloudnetpy/instruments/lufft.py +39 -17
- cloudnetpy/instruments/mira.py +268 -61
- cloudnetpy/instruments/mrr.py +187 -0
- cloudnetpy/instruments/nc_lidar.py +19 -8
- cloudnetpy/instruments/nc_radar.py +109 -55
- cloudnetpy/instruments/pollyxt.py +135 -51
- cloudnetpy/instruments/radiometrics.py +313 -59
- cloudnetpy/instruments/rain_e_h3.py +171 -0
- cloudnetpy/instruments/rpg.py +321 -189
- cloudnetpy/instruments/rpg_reader.py +74 -40
- cloudnetpy/instruments/toa5.py +49 -0
- cloudnetpy/instruments/vaisala.py +95 -343
- cloudnetpy/instruments/weather_station.py +774 -105
- cloudnetpy/metadata.py +90 -19
- cloudnetpy/model_evaluation/file_handler.py +55 -52
- cloudnetpy/model_evaluation/metadata.py +46 -20
- cloudnetpy/model_evaluation/model_metadata.py +1 -1
- cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
- cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
- cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
- cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
- cloudnetpy/model_evaluation/products/model_products.py +43 -35
- cloudnetpy/model_evaluation/products/observation_products.py +41 -35
- cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
- cloudnetpy/model_evaluation/products/tools.py +29 -20
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
- cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
- cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
- cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
- cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
- cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
- cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
- cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
- cloudnetpy/model_evaluation/utils.py +2 -1
- cloudnetpy/output.py +170 -111
- cloudnetpy/plotting/__init__.py +2 -1
- cloudnetpy/plotting/plot_meta.py +562 -822
- cloudnetpy/plotting/plotting.py +1142 -704
- cloudnetpy/products/__init__.py +1 -0
- cloudnetpy/products/classification.py +370 -88
- cloudnetpy/products/der.py +85 -55
- cloudnetpy/products/drizzle.py +77 -34
- cloudnetpy/products/drizzle_error.py +15 -11
- cloudnetpy/products/drizzle_tools.py +79 -59
- cloudnetpy/products/epsilon.py +211 -0
- cloudnetpy/products/ier.py +27 -50
- cloudnetpy/products/iwc.py +55 -48
- cloudnetpy/products/lwc.py +96 -70
- cloudnetpy/products/mwr_tools.py +186 -0
- cloudnetpy/products/product_tools.py +170 -128
- cloudnetpy/utils.py +455 -240
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
- cloudnetpy-1.87.3.dist-info/RECORD +127 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
- cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
- docs/source/conf.py +2 -2
- cloudnetpy/categorize/atmos.py +0 -361
- cloudnetpy/products/mwr_multi.py +0 -68
- cloudnetpy/products/mwr_single.py +0 -75
- cloudnetpy-1.49.9.dist-info/RECORD +0 -112
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
+
from datetime import date
|
|
4
5
|
|
|
6
|
+
import matplotlib as mpl
|
|
5
7
|
import matplotlib.pyplot as plt
|
|
8
|
+
import netCDF4
|
|
6
9
|
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
from matplotlib.axes import Axes
|
|
12
|
+
from matplotlib.colorbar import Colorbar
|
|
13
|
+
from matplotlib.colorizer import ColorizingArtist
|
|
14
|
+
from matplotlib.colors import ListedColormap
|
|
7
15
|
from matplotlib.patches import Patch
|
|
16
|
+
from matplotlib.pyplot import Figure
|
|
8
17
|
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
9
18
|
from numpy import ma
|
|
10
19
|
|
|
11
20
|
import cloudnetpy.model_evaluation.plotting.plot_tools as p_tools
|
|
12
|
-
import cloudnetpy.plotting.plotting as cloud_plt
|
|
13
21
|
from cloudnetpy.model_evaluation.model_metadata import MODELS
|
|
14
|
-
from cloudnetpy.model_evaluation.plotting.plot_meta import ATTRIBUTES
|
|
22
|
+
from cloudnetpy.model_evaluation.plotting.plot_meta import ATTRIBUTES, PlotMeta
|
|
15
23
|
from cloudnetpy.model_evaluation.statistics.statistical_methods import DayStatistics
|
|
24
|
+
from cloudnetpy.plotting.plotting import Dimensions, get_log_cbar_tick_labels, lin2log
|
|
16
25
|
|
|
17
26
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
18
27
|
|
|
@@ -21,6 +30,7 @@ def generate_L3_day_plots(
|
|
|
21
30
|
nc_file: str,
|
|
22
31
|
product: str,
|
|
23
32
|
model: str,
|
|
33
|
+
*,
|
|
24
34
|
title: bool = True,
|
|
25
35
|
var_list: list | None = None,
|
|
26
36
|
fig_type: str | None = "group",
|
|
@@ -28,7 +38,7 @@ def generate_L3_day_plots(
|
|
|
28
38
|
save_path: str | None = None,
|
|
29
39
|
image_name: str | None = None,
|
|
30
40
|
show: bool | None = False,
|
|
31
|
-
):
|
|
41
|
+
) -> list[Dimensions]:
|
|
32
42
|
"""Generate visualizations for level 3 dayscale products.
|
|
33
43
|
With figure type visualizations can be subplot in group, pair, single or
|
|
34
44
|
statistic of given product. In group fig_type all different methods are plot
|
|
@@ -38,6 +48,7 @@ def generate_L3_day_plots(
|
|
|
38
48
|
Single fig_type will plot each product variable in a own figure.
|
|
39
49
|
Statistic fig_type will plot select statistical method of all product method
|
|
40
50
|
in same fig.
|
|
51
|
+
|
|
41
52
|
Args:
|
|
42
53
|
nc_file (str): Path to source file
|
|
43
54
|
product (str): Name of product wanted to plot
|
|
@@ -63,6 +74,7 @@ def generate_L3_day_plots(
|
|
|
63
74
|
In case of model cycles, cycles are visualized in their on figures same
|
|
64
75
|
way as an individual model run would be visualized in its own in a group
|
|
65
76
|
figure.
|
|
77
|
+
|
|
66
78
|
Examples:
|
|
67
79
|
>>> from cloudnetpy.model_evaluation.plotting.plotting
|
|
68
80
|
import generate_L3_day_plots
|
|
@@ -76,17 +88,25 @@ def generate_L3_day_plots(
|
|
|
76
88
|
>>> generate_L3_day_plots(l3_day_file, product, model,
|
|
77
89
|
>>> fig_type='statistic', stats=['error'])
|
|
78
90
|
"""
|
|
91
|
+
|
|
92
|
+
def _check_cycle_names() -> None:
|
|
93
|
+
if not c_names:
|
|
94
|
+
raise AttributeError
|
|
95
|
+
|
|
79
96
|
cls = __import__("plotting")
|
|
80
97
|
model_info = MODELS[model]
|
|
81
98
|
model_name = model_info.model_name
|
|
82
99
|
name_set = p_tools.parse_wanted_names(nc_file, product, model, var_list)
|
|
83
|
-
for
|
|
100
|
+
unique_tuples = {tuple(lst) for lst in name_set}
|
|
101
|
+
name_set_unique = tuple(list(tup) for tup in unique_tuples)
|
|
102
|
+
|
|
103
|
+
dimensions = []
|
|
104
|
+
for names in name_set_unique:
|
|
84
105
|
if len(names) > 0:
|
|
85
106
|
try:
|
|
86
107
|
cycle_names, cycles = p_tools.sort_cycles(names, model)
|
|
87
108
|
for i, c_names in enumerate(cycle_names):
|
|
88
|
-
|
|
89
|
-
raise AttributeError
|
|
109
|
+
_check_cycle_names()
|
|
90
110
|
params = [
|
|
91
111
|
product,
|
|
92
112
|
c_names,
|
|
@@ -95,10 +115,8 @@ def generate_L3_day_plots(
|
|
|
95
115
|
model_name,
|
|
96
116
|
save_path,
|
|
97
117
|
image_name,
|
|
98
|
-
show,
|
|
99
|
-
cycles[i],
|
|
100
|
-
title,
|
|
101
118
|
]
|
|
119
|
+
kwargs = {"show": show, "title": title, "cycle": cycles[i]}
|
|
102
120
|
if fig_type == "statistic":
|
|
103
121
|
params = [
|
|
104
122
|
product,
|
|
@@ -109,11 +127,12 @@ def generate_L3_day_plots(
|
|
|
109
127
|
stats,
|
|
110
128
|
save_path,
|
|
111
129
|
image_name,
|
|
112
|
-
show,
|
|
113
|
-
cycles[i],
|
|
114
|
-
title,
|
|
115
130
|
]
|
|
116
|
-
getattr(cls, f"get_{fig_type}_plots")(
|
|
131
|
+
figs, axes = getattr(cls, f"get_{fig_type}_plots")(
|
|
132
|
+
*params, **kwargs
|
|
133
|
+
)
|
|
134
|
+
for fig, ax in zip(figs, axes, strict=False):
|
|
135
|
+
dimensions.append(Dimensions(fig, ax))
|
|
117
136
|
except AttributeError:
|
|
118
137
|
params = [
|
|
119
138
|
product,
|
|
@@ -123,10 +142,8 @@ def generate_L3_day_plots(
|
|
|
123
142
|
model_name,
|
|
124
143
|
save_path,
|
|
125
144
|
image_name,
|
|
126
|
-
show,
|
|
127
|
-
"",
|
|
128
|
-
title,
|
|
129
145
|
]
|
|
146
|
+
kwargs = {"show": show, "title": title}
|
|
130
147
|
if fig_type == "statistic":
|
|
131
148
|
params = [
|
|
132
149
|
product,
|
|
@@ -137,11 +154,11 @@ def generate_L3_day_plots(
|
|
|
137
154
|
stats,
|
|
138
155
|
save_path,
|
|
139
156
|
image_name,
|
|
140
|
-
show,
|
|
141
|
-
"",
|
|
142
|
-
title,
|
|
143
157
|
]
|
|
144
|
-
getattr(cls, f"get_{fig_type}_plots")(*params)
|
|
158
|
+
figs, axes = getattr(cls, f"get_{fig_type}_plots")(*params, **kwargs)
|
|
159
|
+
for fig, ax in zip(figs, axes, strict=False):
|
|
160
|
+
dimensions.append(Dimensions(fig, ax))
|
|
161
|
+
return dimensions
|
|
145
162
|
|
|
146
163
|
|
|
147
164
|
def get_group_plots(
|
|
@@ -152,14 +169,17 @@ def get_group_plots(
|
|
|
152
169
|
model_name: str,
|
|
153
170
|
save_path: str,
|
|
154
171
|
image_name: str,
|
|
172
|
+
*,
|
|
155
173
|
show: bool,
|
|
156
174
|
cycle: str = "",
|
|
157
175
|
title: bool = True,
|
|
158
|
-
|
|
176
|
+
include_xlimits: bool = False,
|
|
177
|
+
) -> tuple:
|
|
159
178
|
"""Group subplot visualization for both standard and advection downsampling.
|
|
160
179
|
Generates group subplot figure for product with model and all different
|
|
161
180
|
downsampling methods. Generates separated figures for standard and advection
|
|
162
181
|
timegrids. All model cycles if any will be generated to their own figures.
|
|
182
|
+
|
|
163
183
|
Args:
|
|
164
184
|
product (str): Name of the product
|
|
165
185
|
names (list): List of variables to be visualized to same fig
|
|
@@ -171,6 +191,7 @@ def get_group_plots(
|
|
|
171
191
|
show (bool): Show figure before saving if True
|
|
172
192
|
cycle (str): Name of cycle if exists
|
|
173
193
|
title (bool): True or False if wanted to add title to subfig
|
|
194
|
+
include_xlimits (bool): Show labels at the ends of x-axis
|
|
174
195
|
"""
|
|
175
196
|
fig, ax = initialize_figure(len(names))
|
|
176
197
|
model_run = model
|
|
@@ -178,22 +199,28 @@ def get_group_plots(
|
|
|
178
199
|
j = 0
|
|
179
200
|
for j, name in enumerate(names):
|
|
180
201
|
variable_info = ATTRIBUTES[product]
|
|
181
|
-
|
|
202
|
+
set_yax(ax[j], 12, ylabel=None)
|
|
203
|
+
set_xax(ax[j], include_xlimits=include_xlimits)
|
|
182
204
|
if title:
|
|
183
205
|
_set_title(ax[j], name, product, variable_info)
|
|
184
206
|
if j == 0 and title:
|
|
185
207
|
_set_title(ax[j], model, product, variable_info, model_name)
|
|
186
208
|
data, x, y = p_tools.read_data_characters(nc_file, name, model)
|
|
187
209
|
plot_colormesh(ax[j], data, (x, y), variable_info)
|
|
188
|
-
casedate =
|
|
210
|
+
casedate = set_labels(fig, ax[j], nc_file)
|
|
189
211
|
if "adv" in name:
|
|
190
212
|
product = product + "_adv"
|
|
191
213
|
if len(cycle) > 1:
|
|
192
214
|
fig.text(0.64, 0.885, f"Cycle: {cycle}", fontsize=13)
|
|
193
215
|
model_run = f"{model}_{cycle}"
|
|
194
|
-
|
|
195
|
-
image_name,
|
|
216
|
+
handle_saving(
|
|
217
|
+
image_name,
|
|
218
|
+
save_path,
|
|
219
|
+
casedate,
|
|
220
|
+
[product, model_run, "group"],
|
|
221
|
+
show=show,
|
|
196
222
|
)
|
|
223
|
+
return fig, ax
|
|
197
224
|
|
|
198
225
|
|
|
199
226
|
def get_pair_plots(
|
|
@@ -204,14 +231,17 @@ def get_pair_plots(
|
|
|
204
231
|
model_name: str,
|
|
205
232
|
save_path: str,
|
|
206
233
|
image_name: str,
|
|
207
|
-
show: bool,
|
|
208
234
|
cycle: str = "",
|
|
235
|
+
*,
|
|
236
|
+
show: bool = False,
|
|
209
237
|
title: bool = True,
|
|
210
|
-
|
|
238
|
+
include_xlimits: bool = False,
|
|
239
|
+
) -> None:
|
|
211
240
|
"""Pair subplots of model and product method.
|
|
212
241
|
In upper subplot is model product and lower subplot one of the
|
|
213
242
|
downsampled method of select product. Function generates all product methods
|
|
214
243
|
in a given nc-file in loop.
|
|
244
|
+
|
|
215
245
|
Args:
|
|
216
246
|
product (str): Name of the product
|
|
217
247
|
names (list): List of variables to be visualized to same fig
|
|
@@ -223,6 +253,7 @@ def get_pair_plots(
|
|
|
223
253
|
show (bool): Show figure before saving if True
|
|
224
254
|
cycle (str): Name of cycle if exists
|
|
225
255
|
title (bool): True or False if wanted add title to subfig
|
|
256
|
+
include_xlimits (bool): Show labels at the ends of x-axis
|
|
226
257
|
"""
|
|
227
258
|
variable_info = ATTRIBUTES[product]
|
|
228
259
|
model_ax = names[0]
|
|
@@ -230,8 +261,11 @@ def get_pair_plots(
|
|
|
230
261
|
if i == 0:
|
|
231
262
|
continue
|
|
232
263
|
fig, ax = initialize_figure(2)
|
|
233
|
-
|
|
234
|
-
|
|
264
|
+
set_yax(ax[0], 12, ylabel=None)
|
|
265
|
+
set_yax(ax[-1], 12, ylabel=None)
|
|
266
|
+
set_xax(ax[0], include_xlimits=include_xlimits)
|
|
267
|
+
set_xax(ax[-1], include_xlimits=include_xlimits)
|
|
268
|
+
|
|
235
269
|
if title:
|
|
236
270
|
_set_title(ax[0], model, product, variable_info, model_name)
|
|
237
271
|
_set_title(ax[-1], name, product, variable_info)
|
|
@@ -239,10 +273,16 @@ def get_pair_plots(
|
|
|
239
273
|
data, x, y = p_tools.read_data_characters(nc_file, name, model)
|
|
240
274
|
plot_colormesh(ax[0], model_data, (mx, my), variable_info)
|
|
241
275
|
plot_colormesh(ax[-1], data, (x, y), variable_info)
|
|
242
|
-
casedate =
|
|
276
|
+
casedate = set_labels(fig, ax[-1], nc_file)
|
|
243
277
|
if len(cycle) > 1:
|
|
244
278
|
fig.text(0.64, 0.889, f"Cycle: {cycle}", fontsize=13)
|
|
245
|
-
|
|
279
|
+
handle_saving(
|
|
280
|
+
image_name,
|
|
281
|
+
save_path,
|
|
282
|
+
casedate,
|
|
283
|
+
[name, "pair"],
|
|
284
|
+
show=show,
|
|
285
|
+
)
|
|
246
286
|
|
|
247
287
|
|
|
248
288
|
def get_single_plots(
|
|
@@ -253,11 +293,14 @@ def get_single_plots(
|
|
|
253
293
|
model_name: str,
|
|
254
294
|
save_path: str,
|
|
255
295
|
image_name: str,
|
|
296
|
+
*,
|
|
256
297
|
show: bool,
|
|
257
298
|
cycle: str = "",
|
|
258
299
|
title: bool = True,
|
|
259
|
-
|
|
300
|
+
include_xlimits: bool = False,
|
|
301
|
+
) -> tuple[list, list]:
|
|
260
302
|
"""Generates figures of each product variable from given file in loop.
|
|
303
|
+
|
|
261
304
|
Args:
|
|
262
305
|
product (str): Name of the product
|
|
263
306
|
names (list): List of variables to be visualized to same fig
|
|
@@ -269,38 +312,55 @@ def get_single_plots(
|
|
|
269
312
|
show (bool): Show figure before saving if True
|
|
270
313
|
cycle (str): Name of cycle if exists
|
|
271
314
|
title (bool): True or False if wanted to add title to subfig
|
|
315
|
+
include_xlimits (bool): Show labels at the ends of x-axis
|
|
272
316
|
"""
|
|
317
|
+
figs = []
|
|
318
|
+
axes = []
|
|
273
319
|
variable_info = ATTRIBUTES[product]
|
|
274
|
-
for
|
|
320
|
+
for name in names:
|
|
275
321
|
fig, ax = initialize_figure(1)
|
|
276
|
-
|
|
322
|
+
figs.append(fig)
|
|
323
|
+
axes.append(ax)
|
|
324
|
+
set_yax(ax[0], 12, ylabel=None)
|
|
325
|
+
set_xax(ax[0], include_xlimits=include_xlimits)
|
|
326
|
+
|
|
277
327
|
if title:
|
|
278
328
|
_set_title(ax[0], name, product, variable_info)
|
|
279
329
|
data, x, y = p_tools.read_data_characters(nc_file, name, model)
|
|
280
330
|
plot_colormesh(ax[0], data, (x, y), variable_info)
|
|
281
|
-
casedate =
|
|
331
|
+
casedate = set_labels(fig, ax[0], nc_file, sub_title=title)
|
|
282
332
|
if title:
|
|
283
333
|
if len(cycle) > 1:
|
|
284
334
|
fig.text(0.64, 0.9, f"{model_name} cycle: {cycle}", fontsize=13)
|
|
285
335
|
else:
|
|
286
336
|
fig.text(0.64, 0.9, f"{model_name}", fontsize=13)
|
|
287
|
-
|
|
337
|
+
handle_saving(
|
|
338
|
+
image_name,
|
|
339
|
+
save_path,
|
|
340
|
+
casedate,
|
|
341
|
+
[name, "single"],
|
|
342
|
+
show=show,
|
|
343
|
+
)
|
|
344
|
+
return figs, axes
|
|
288
345
|
|
|
289
346
|
|
|
290
|
-
def plot_colormesh(
|
|
347
|
+
def plot_colormesh(
|
|
348
|
+
ax: Axes, data: npt.NDArray, axes: tuple, variable_info: PlotMeta
|
|
349
|
+
) -> None:
|
|
291
350
|
vmin, vmax = variable_info.plot_range
|
|
292
351
|
if variable_info.plot_scale == "logarithmic":
|
|
293
|
-
data, vmin, vmax =
|
|
352
|
+
data, vmin, vmax = lin2log(data, vmin, vmax)
|
|
294
353
|
cmap = plt.get_cmap(variable_info.cbar, 22)
|
|
295
354
|
data[data < vmin] = ma.masked
|
|
296
355
|
pl = ax.pcolormesh(*axes, data, vmin=vmin, vmax=vmax, cmap=cmap)
|
|
297
356
|
colorbar = init_colorbar(pl, ax)
|
|
298
357
|
if variable_info.plot_scale == "logarithmic":
|
|
299
|
-
tick_labels =
|
|
300
|
-
colorbar.set_ticks(np.arange(vmin, vmax + 1))
|
|
358
|
+
tick_labels = get_log_cbar_tick_labels(vmin, vmax)
|
|
359
|
+
colorbar.set_ticks(np.arange(vmin, vmax + 1).tolist()) # type: ignore[arg-type]
|
|
301
360
|
colorbar.ax.set_yticklabels(tick_labels)
|
|
302
361
|
ax.set_facecolor("white")
|
|
303
|
-
|
|
362
|
+
if variable_info.clabel is not None:
|
|
363
|
+
colorbar.set_label(variable_info.clabel, fontsize=13)
|
|
304
364
|
|
|
305
365
|
|
|
306
366
|
def get_statistic_plots(
|
|
@@ -312,10 +372,11 @@ def get_statistic_plots(
|
|
|
312
372
|
stats: list,
|
|
313
373
|
save_path: str,
|
|
314
374
|
image_name: str,
|
|
375
|
+
*,
|
|
315
376
|
show: bool,
|
|
316
377
|
cycle: str = "",
|
|
317
378
|
title: bool = True,
|
|
318
|
-
):
|
|
379
|
+
) -> tuple:
|
|
319
380
|
"""Statistical subplots for day scale products.
|
|
320
381
|
Statistical analysis can be done by day scale with relative error ('error'),
|
|
321
382
|
total data area analysis ('area'), histogram ('hist') or vertical profiles
|
|
@@ -323,6 +384,7 @@ def get_statistic_plots(
|
|
|
323
384
|
per statistical method for a select product. All different downsampled method
|
|
324
385
|
are in a same fig. Standard and advection timegrids are separated to own figs
|
|
325
386
|
as well as different cycle runs.
|
|
387
|
+
|
|
326
388
|
Args:
|
|
327
389
|
product (str): Name of the product
|
|
328
390
|
names (list): List of variables to be visualized to same fig
|
|
@@ -335,18 +397,34 @@ def get_statistic_plots(
|
|
|
335
397
|
image_name (str, optional): Saving name of generated fig
|
|
336
398
|
show (bool): Show figure before saving if True
|
|
337
399
|
cycle (str): Name of cycle if exists
|
|
400
|
+
title (bool): True or False if wanted to add title to subfig
|
|
338
401
|
"""
|
|
339
|
-
# pylint: disable=too-many-branches
|
|
340
|
-
# pylint: disable=too-many-nested-blocks
|
|
341
402
|
model_run = model
|
|
342
403
|
name = ""
|
|
343
404
|
j = 0
|
|
405
|
+
|
|
406
|
+
def _check_data() -> None:
|
|
407
|
+
if model_missing and obs_missing:
|
|
408
|
+
_raise()
|
|
409
|
+
|
|
410
|
+
def _check_data2() -> None:
|
|
411
|
+
if "error" in stat and np.all(day_stat.model_stat.mask is True):
|
|
412
|
+
_raise()
|
|
413
|
+
|
|
414
|
+
def _raise() -> None:
|
|
415
|
+
err_msg = f"No data in {model_name} or observation"
|
|
416
|
+
raise ValueError(err_msg)
|
|
417
|
+
|
|
418
|
+
figs = []
|
|
419
|
+
axes = []
|
|
344
420
|
for stat in stats:
|
|
345
421
|
try:
|
|
346
422
|
obs_missing = False
|
|
347
423
|
model_missing = False
|
|
348
424
|
variable_info = ATTRIBUTES[product]
|
|
349
425
|
fig, ax = initialize_figure(len(names) - 1, stat)
|
|
426
|
+
figs.append(fig)
|
|
427
|
+
axes.append(ax)
|
|
350
428
|
model_data, _, _ = p_tools.read_data_characters(nc_file, names[0], model)
|
|
351
429
|
if np.all(model_data.mask is True):
|
|
352
430
|
model_missing = True
|
|
@@ -354,65 +432,73 @@ def get_statistic_plots(
|
|
|
354
432
|
data, x, y = p_tools.read_data_characters(nc_file, name, model)
|
|
355
433
|
if np.all(data.mask is True):
|
|
356
434
|
obs_missing = True
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if product == "cf" and stat == "error":
|
|
360
|
-
stat = "aerror"
|
|
435
|
+
_check_data()
|
|
436
|
+
statistics = "aerror" if product == "cf" and stat == "error" else stat
|
|
361
437
|
if j > 0:
|
|
362
|
-
|
|
363
|
-
|
|
438
|
+
name_new = ""
|
|
439
|
+
name_new = _get_stat_titles(name_new, product, variable_info)
|
|
364
440
|
day_stat = DayStatistics(
|
|
365
|
-
|
|
441
|
+
statistics,
|
|
442
|
+
[product, model_name, name_new],
|
|
443
|
+
model_data,
|
|
444
|
+
data,
|
|
366
445
|
)
|
|
367
|
-
|
|
368
|
-
if np.all(day_stat.model_stat.mask is True):
|
|
369
|
-
raise ValueError("No data to calculate relative error")
|
|
446
|
+
_check_data2()
|
|
370
447
|
initialize_statistic_plots(
|
|
371
448
|
j,
|
|
372
449
|
len(names) - 1,
|
|
373
450
|
ax[j - 1],
|
|
374
|
-
|
|
451
|
+
statistics,
|
|
375
452
|
day_stat,
|
|
376
453
|
model_data,
|
|
377
454
|
data,
|
|
378
455
|
(x, y),
|
|
379
456
|
variable_info,
|
|
380
|
-
title,
|
|
457
|
+
title=title,
|
|
381
458
|
)
|
|
382
|
-
except ValueError
|
|
383
|
-
logging.
|
|
459
|
+
except ValueError:
|
|
460
|
+
logging.exception("Exception occurred")
|
|
384
461
|
if stat not in ("hist", "vertical"):
|
|
385
|
-
casedate =
|
|
462
|
+
casedate = set_labels(fig, ax[j - 1], nc_file, sub_title=title)
|
|
386
463
|
else:
|
|
387
|
-
casedate =
|
|
388
|
-
_name =
|
|
464
|
+
casedate = read_date(nc_file)
|
|
465
|
+
_name = read_location(nc_file)
|
|
389
466
|
if title:
|
|
390
|
-
|
|
467
|
+
add_subtitle(fig, casedate, _name.capitalize())
|
|
391
468
|
if len(cycle) > 1:
|
|
392
469
|
fig.text(0.64, 0.885, f"Cycle: {cycle}", fontsize=13)
|
|
393
470
|
model_run = f"{model}_{cycle}"
|
|
394
|
-
|
|
395
|
-
image_name,
|
|
471
|
+
handle_saving(
|
|
472
|
+
image_name,
|
|
473
|
+
save_path,
|
|
474
|
+
casedate,
|
|
475
|
+
[name, stat, model_run],
|
|
476
|
+
show=show,
|
|
396
477
|
)
|
|
478
|
+
return figs, axes
|
|
397
479
|
|
|
398
480
|
|
|
399
481
|
def initialize_statistic_plots(
|
|
400
482
|
j: int,
|
|
401
483
|
max_len: int,
|
|
402
|
-
ax,
|
|
484
|
+
ax: Axes,
|
|
403
485
|
method: str,
|
|
404
486
|
day_stat: DayStatistics,
|
|
405
487
|
model: ma.MaskedArray,
|
|
406
488
|
obs: ma.MaskedArray,
|
|
407
489
|
args: tuple,
|
|
408
|
-
variable_info,
|
|
490
|
+
variable_info: PlotMeta,
|
|
491
|
+
*,
|
|
409
492
|
title: bool = True,
|
|
410
|
-
|
|
493
|
+
include_xlimits: bool = False,
|
|
494
|
+
) -> None:
|
|
411
495
|
if method in ("error", "aerror"):
|
|
412
496
|
plot_relative_error(ax, day_stat.model_stat.T, args, method)
|
|
413
497
|
if title:
|
|
414
498
|
ax.set_title(day_stat.title, fontsize=14)
|
|
415
|
-
|
|
499
|
+
set_yax(ax, 12, ylabel=None)
|
|
500
|
+
set_xax(ax, include_xlimits=include_xlimits)
|
|
501
|
+
|
|
416
502
|
if method == "area":
|
|
417
503
|
plot_data_area(ax, day_stat, model, obs, args, title=title)
|
|
418
504
|
ax.text(
|
|
@@ -423,7 +509,9 @@ def initialize_statistic_plots(
|
|
|
423
509
|
ha="center",
|
|
424
510
|
transform=ax.transAxes,
|
|
425
511
|
)
|
|
426
|
-
|
|
512
|
+
set_yax(ax, 12, ylabel=None)
|
|
513
|
+
set_xax(ax, include_xlimits=include_xlimits)
|
|
514
|
+
|
|
427
515
|
if method == "hist":
|
|
428
516
|
plot_histogram(ax, day_stat, variable_info)
|
|
429
517
|
if j == max_len - 1 and (max_len % 2) == 0:
|
|
@@ -459,19 +547,19 @@ def initialize_statistic_plots(
|
|
|
459
547
|
)
|
|
460
548
|
|
|
461
549
|
|
|
462
|
-
def plot_relative_error(
|
|
550
|
+
def plot_relative_error(
|
|
551
|
+
ax: Axes, error: ma.MaskedArray, axes: tuple, method: str
|
|
552
|
+
) -> None:
|
|
463
553
|
pl = ax.pcolormesh(*axes, error[:-1, :-1].T, cmap="RdBu_r", vmin=-50, vmax=50)
|
|
464
554
|
colorbar = init_colorbar(pl, ax)
|
|
465
555
|
colorbar.set_label("%", fontsize=13)
|
|
466
556
|
error[np.isnan(error)] = ma.masked
|
|
467
|
-
error = ma.round(error, decimals=4)
|
|
468
557
|
median_error = ma.median(error.compressed())
|
|
469
|
-
median_error = np.round(median_error, 3)
|
|
470
558
|
if method == "aerror":
|
|
471
559
|
ax.text(
|
|
472
560
|
0.9,
|
|
473
561
|
-0.17,
|
|
474
|
-
f"Median absolute error: {median_error} %",
|
|
562
|
+
f"Median absolute error: {median_error:.2f} %",
|
|
475
563
|
size=12,
|
|
476
564
|
ha="center",
|
|
477
565
|
transform=ax.transAxes,
|
|
@@ -480,7 +568,7 @@ def plot_relative_error(ax, error: ma.MaskedArray, axes: tuple, method: str):
|
|
|
480
568
|
ax.text(
|
|
481
569
|
0.9,
|
|
482
570
|
-0.17,
|
|
483
|
-
f"Median relative error: {median_error} %",
|
|
571
|
+
f"Median relative error: {median_error:.2f} %",
|
|
484
572
|
size=12,
|
|
485
573
|
ha="center",
|
|
486
574
|
transform=ax.transAxes,
|
|
@@ -488,24 +576,39 @@ def plot_relative_error(ax, error: ma.MaskedArray, axes: tuple, method: str):
|
|
|
488
576
|
|
|
489
577
|
|
|
490
578
|
def plot_data_area(
|
|
491
|
-
ax,
|
|
579
|
+
ax: Axes,
|
|
492
580
|
day_stat: DayStatistics,
|
|
493
581
|
model: ma.MaskedArray,
|
|
494
582
|
obs: ma.MaskedArray,
|
|
495
583
|
axes: tuple,
|
|
584
|
+
*,
|
|
496
585
|
title: bool = True,
|
|
497
|
-
):
|
|
498
|
-
data
|
|
499
|
-
|
|
586
|
+
) -> None:
|
|
587
|
+
data = p_tools.create_segment_values(model, obs)
|
|
588
|
+
|
|
589
|
+
colors = mpl.colormaps["YlGnBu"]
|
|
590
|
+
newcolors = colors(np.linspace(0, 1, 256))
|
|
591
|
+
c_map = {
|
|
592
|
+
0: "white",
|
|
593
|
+
1: "khaki",
|
|
594
|
+
2: newcolors[90],
|
|
595
|
+
3: newcolors[140],
|
|
596
|
+
}
|
|
597
|
+
unique_values = sorted(np.unique(data))
|
|
598
|
+
c_list = [c_map[value] for value in unique_values if value in c_map]
|
|
599
|
+
cmap = ListedColormap(c_list)
|
|
600
|
+
|
|
601
|
+
ax.pcolormesh(*axes, data, cmap=cmap)
|
|
602
|
+
|
|
500
603
|
if title:
|
|
501
|
-
colorbar = init_colorbar(pl, ax)
|
|
502
|
-
colorbar.set_ticks(np.arange(1, 1, 3))
|
|
503
604
|
ax.set_title(f"{day_stat.title}", fontsize=14)
|
|
605
|
+
|
|
504
606
|
ax.set_facecolor("black")
|
|
607
|
+
|
|
505
608
|
legend_elements = [
|
|
506
609
|
Patch(facecolor="khaki", edgecolor="k", label="Model"),
|
|
507
|
-
Patch(facecolor=
|
|
508
|
-
Patch(facecolor=
|
|
610
|
+
Patch(facecolor=newcolors[90], edgecolor="k", label="Common"),
|
|
611
|
+
Patch(facecolor=newcolors[140], edgecolor="k", label="Observation"),
|
|
509
612
|
]
|
|
510
613
|
ax.legend(
|
|
511
614
|
handles=legend_elements,
|
|
@@ -516,13 +619,13 @@ def plot_data_area(
|
|
|
516
619
|
)
|
|
517
620
|
|
|
518
621
|
|
|
519
|
-
def plot_histogram(ax, day_stat: DayStatistics, variable_info):
|
|
622
|
+
def plot_histogram(ax: Axes, day_stat: DayStatistics, variable_info: PlotMeta) -> None:
|
|
520
623
|
weights = np.ones_like(day_stat.model_stat) / float(len(day_stat.model_stat))
|
|
521
624
|
hist_bins = np.histogram(day_stat.observation_stat, density=True)[-1]
|
|
522
625
|
ax.hist(
|
|
523
626
|
day_stat.model_stat,
|
|
524
627
|
weights=weights,
|
|
525
|
-
bins=hist_bins,
|
|
628
|
+
bins=list(hist_bins),
|
|
526
629
|
alpha=0.7,
|
|
527
630
|
facecolor="khaki",
|
|
528
631
|
edgecolor="k",
|
|
@@ -530,32 +633,35 @@ def plot_histogram(ax, day_stat: DayStatistics, variable_info):
|
|
|
530
633
|
)
|
|
531
634
|
|
|
532
635
|
weights = np.ones_like(day_stat.observation_stat) / float(
|
|
533
|
-
len(day_stat.observation_stat)
|
|
636
|
+
len(day_stat.observation_stat),
|
|
534
637
|
)
|
|
535
638
|
ax.hist(
|
|
536
639
|
day_stat.observation_stat,
|
|
537
640
|
weights=weights,
|
|
538
|
-
bins=hist_bins,
|
|
641
|
+
bins=list(hist_bins),
|
|
539
642
|
alpha=0.7,
|
|
540
643
|
facecolor="steelblue",
|
|
541
644
|
edgecolor="k",
|
|
542
645
|
label="Observation",
|
|
543
646
|
)
|
|
544
|
-
|
|
647
|
+
if variable_info.x_title is not None:
|
|
648
|
+
ax.set_xlabel(variable_info.x_title, fontsize=13)
|
|
545
649
|
if variable_info.plot_scale == "logarithmic":
|
|
546
650
|
ax.ticklabel_format(axis="x", style="sci", scilimits=(0, 0))
|
|
547
651
|
ax.set_ylabel("Relative frequency %", fontsize=13)
|
|
548
|
-
ax.yaxis.grid(
|
|
652
|
+
ax.yaxis.grid(which="major")
|
|
549
653
|
ax.set_title(f"{day_stat.title[-1]}", fontsize=14)
|
|
550
654
|
|
|
551
655
|
|
|
552
|
-
def plot_vertical_profile(
|
|
656
|
+
def plot_vertical_profile(
|
|
657
|
+
ax: Axes,
|
|
658
|
+
day_stat: DayStatistics,
|
|
659
|
+
axes: tuple,
|
|
660
|
+
variable_info: PlotMeta,
|
|
661
|
+
) -> None:
|
|
553
662
|
mrm = p_tools.rolling_mean(day_stat.model_stat)
|
|
554
663
|
orm = p_tools.rolling_mean(day_stat.observation_stat)
|
|
555
|
-
if len(axes[-1].shape) > 1
|
|
556
|
-
axes = axes[-1][0]
|
|
557
|
-
else:
|
|
558
|
-
axes = axes[-1]
|
|
664
|
+
axes = axes[-1][0] if len(axes[-1].shape) > 1 else axes[-1]
|
|
559
665
|
ax.plot(day_stat.model_stat, axes, "o", markersize=5.5, color="k")
|
|
560
666
|
ax.plot(day_stat.observation_stat, axes, "o", markersize=5.5, color="k")
|
|
561
667
|
ax.plot(
|
|
@@ -588,15 +694,16 @@ def plot_vertical_profile(ax, day_stat: DayStatistics, axes: tuple, variable_inf
|
|
|
588
694
|
ax.plot(orm, axes, "-", color="green", lw=2, label="Mean of observation")
|
|
589
695
|
|
|
590
696
|
ax.set_title(f"{day_stat.title[-1]}", fontsize=14)
|
|
591
|
-
|
|
697
|
+
if variable_info.x_title is not None:
|
|
698
|
+
ax.set_xlabel(variable_info.x_title, fontsize=13)
|
|
592
699
|
if variable_info.plot_scale == "logarithmic":
|
|
593
700
|
ax.ticklabel_format(axis="x", style="sci", scilimits=(0, 0))
|
|
594
|
-
ax.yaxis.grid(
|
|
595
|
-
ax.xaxis.grid(
|
|
701
|
+
ax.yaxis.grid(which="major")
|
|
702
|
+
ax.xaxis.grid(which="major")
|
|
596
703
|
|
|
597
704
|
|
|
598
|
-
def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
|
|
599
|
-
"""Set up fig and ax object, if subplot"""
|
|
705
|
+
def initialize_figure(n_subplots: int, stat: str = "") -> tuple[Figure, list[Axes]]:
|
|
706
|
+
"""Set up fig and ax object, if subplot."""
|
|
600
707
|
if n_subplots <= 0:
|
|
601
708
|
n_subplots = 1
|
|
602
709
|
fig, axes = plt.subplots(n_subplots, 1, figsize=(16, 4 + (n_subplots - 1) * 4.8))
|
|
@@ -622,7 +729,6 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
|
|
|
622
729
|
right=0.75,
|
|
623
730
|
hspace=0.2,
|
|
624
731
|
)
|
|
625
|
-
axes = axes.flatten()
|
|
626
732
|
if stat == "vertical" and n_subplots > 1:
|
|
627
733
|
fig, axes = plt.subplots(
|
|
628
734
|
int(n_subplots / 2),
|
|
@@ -639,20 +745,24 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
|
|
|
639
745
|
right=0.75,
|
|
640
746
|
hspace=0.16,
|
|
641
747
|
)
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
axes = [axes]
|
|
645
|
-
return fig, axes
|
|
748
|
+
axes_list = [axes] if isinstance(axes, Axes) else axes.flatten().tolist()
|
|
749
|
+
return fig, axes_list
|
|
646
750
|
|
|
647
751
|
|
|
648
|
-
def init_colorbar(plot, axis):
|
|
752
|
+
def init_colorbar(plot: ColorizingArtist, axis: Axes) -> Colorbar:
|
|
649
753
|
divider = make_axes_locatable(axis)
|
|
650
754
|
cax = divider.append_axes("right", size="1%", pad=0.25)
|
|
651
755
|
return plt.colorbar(plot, fraction=1.0, ax=axis, cax=cax)
|
|
652
756
|
|
|
653
757
|
|
|
654
|
-
def _set_title(
|
|
655
|
-
|
|
758
|
+
def _set_title(
|
|
759
|
+
ax: Axes,
|
|
760
|
+
field_name: str,
|
|
761
|
+
product: str,
|
|
762
|
+
variable_info: PlotMeta,
|
|
763
|
+
model_name: str = "",
|
|
764
|
+
) -> None:
|
|
765
|
+
"""Generates subtitles for different product types."""
|
|
656
766
|
parts = field_name.split("_")
|
|
657
767
|
if parts[0] == product:
|
|
658
768
|
title = _get_product_title(variable_info)
|
|
@@ -672,14 +782,14 @@ def _set_title(ax, field_name: str, product: str, variable_info, model_name: str
|
|
|
672
782
|
ax.set_title(f"Simulated {name}")
|
|
673
783
|
|
|
674
784
|
|
|
675
|
-
def _get_cf_title(field_name: str, variable_info) -> str:
|
|
785
|
+
def _get_cf_title(field_name: str, variable_info: PlotMeta) -> str:
|
|
676
786
|
title = f"{variable_info.name}, Area"
|
|
677
787
|
if "V" in field_name:
|
|
678
788
|
title = f"{variable_info.name}, Volume"
|
|
679
789
|
return title
|
|
680
790
|
|
|
681
791
|
|
|
682
|
-
def _get_iwc_title(field_name: str, variable_info) -> str:
|
|
792
|
+
def _get_iwc_title(field_name: str, variable_info: PlotMeta) -> str:
|
|
683
793
|
name = variable_info.name
|
|
684
794
|
if "att" in field_name:
|
|
685
795
|
title = f"{name} with good attenuation"
|
|
@@ -690,12 +800,11 @@ def _get_iwc_title(field_name: str, variable_info) -> str:
|
|
|
690
800
|
return title
|
|
691
801
|
|
|
692
802
|
|
|
693
|
-
def _get_product_title(variable_info) -> str:
|
|
694
|
-
|
|
695
|
-
return title
|
|
803
|
+
def _get_product_title(variable_info: PlotMeta) -> str:
|
|
804
|
+
return f"{variable_info.name}"
|
|
696
805
|
|
|
697
806
|
|
|
698
|
-
def _get_stat_titles(field_name: str, product: str, variable_info) -> str:
|
|
807
|
+
def _get_stat_titles(field_name: str, product: str, variable_info: PlotMeta) -> str:
|
|
699
808
|
title = _get_product_title_stat(variable_info)
|
|
700
809
|
if product == "cf":
|
|
701
810
|
title = _get_cf_title_stat(field_name, variable_info)
|
|
@@ -707,7 +816,7 @@ def _get_stat_titles(field_name: str, product: str, variable_info) -> str:
|
|
|
707
816
|
return title
|
|
708
817
|
|
|
709
818
|
|
|
710
|
-
def _get_cf_title_stat(field_name: str, variable_info) -> str:
|
|
819
|
+
def _get_cf_title_stat(field_name: str, variable_info: PlotMeta) -> str:
|
|
711
820
|
name = variable_info.name
|
|
712
821
|
title = f"{name} area"
|
|
713
822
|
if "V" in field_name:
|
|
@@ -715,7 +824,7 @@ def _get_cf_title_stat(field_name: str, variable_info) -> str:
|
|
|
715
824
|
return title
|
|
716
825
|
|
|
717
826
|
|
|
718
|
-
def _get_iwc_title_stat(field_name: str, variable_info) -> str:
|
|
827
|
+
def _get_iwc_title_stat(field_name: str, variable_info: PlotMeta) -> str:
|
|
719
828
|
name = variable_info.name
|
|
720
829
|
if "att" in field_name:
|
|
721
830
|
title = f"{name} with good attenuation"
|
|
@@ -726,7 +835,108 @@ def _get_iwc_title_stat(field_name: str, variable_info) -> str:
|
|
|
726
835
|
return title
|
|
727
836
|
|
|
728
837
|
|
|
729
|
-
def _get_product_title_stat(variable_info) -> str:
|
|
838
|
+
def _get_product_title_stat(variable_info: PlotMeta) -> str:
|
|
730
839
|
name = variable_info.name
|
|
731
|
-
|
|
732
|
-
|
|
840
|
+
return f"{name}"
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def set_yax(ax: Axes, max_y: float, ylabel: str | None, min_y: float = 0.0) -> None:
|
|
844
|
+
"""Sets yticks, ylim and ylabel for yaxis of axis."""
|
|
845
|
+
ax.set_ylim(min_y, max_y)
|
|
846
|
+
ax.set_ylabel("Height (km)", fontsize=13)
|
|
847
|
+
if ylabel is not None:
|
|
848
|
+
ax.set_ylabel(ylabel, fontsize=13)
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def set_xax(ax: Axes, *, include_xlimits: bool = False) -> None:
|
|
852
|
+
"""Sets xticks and xtick labels for plt.imshow()."""
|
|
853
|
+
ticks_x_labels = _get_standard_time_ticks(include_xlimits=include_xlimits)
|
|
854
|
+
ax.set_xticks(np.arange(0, 25, 4, dtype=int))
|
|
855
|
+
ax.set_xticklabels(ticks_x_labels, fontsize=12)
|
|
856
|
+
ax.set_xlim(0, 24)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _get_standard_time_ticks(
|
|
860
|
+
resolution: int = 4,
|
|
861
|
+
*,
|
|
862
|
+
include_xlimits: bool = False,
|
|
863
|
+
) -> list:
|
|
864
|
+
"""Returns typical ticks / labels for a time vector between 0-24h."""
|
|
865
|
+
if include_xlimits:
|
|
866
|
+
return [
|
|
867
|
+
f"{int(i):02d}:00" if 24 >= i >= 0 else ""
|
|
868
|
+
for i in np.arange(0, 24.01, resolution)
|
|
869
|
+
]
|
|
870
|
+
return [
|
|
871
|
+
f"{int(i):02d}:00" if 24 > i > 0 else ""
|
|
872
|
+
for i in np.arange(0, 24.01, resolution)
|
|
873
|
+
]
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def set_labels(fig: Figure, ax: Axes, nc_file: str, *, sub_title: bool = True) -> date:
|
|
877
|
+
ax.set_xlabel("Time (UTC)", fontsize=13)
|
|
878
|
+
case_date = read_date(nc_file)
|
|
879
|
+
site_name = read_location(nc_file)
|
|
880
|
+
if sub_title:
|
|
881
|
+
add_subtitle(fig, case_date, site_name)
|
|
882
|
+
return case_date
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def read_location(nc_file: str) -> str:
|
|
886
|
+
"""Returns site name."""
|
|
887
|
+
with netCDF4.Dataset(nc_file) as nc:
|
|
888
|
+
return nc.location
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def read_date(nc_file: str) -> date:
|
|
892
|
+
"""Returns measurement date."""
|
|
893
|
+
with netCDF4.Dataset(nc_file) as nc:
|
|
894
|
+
return date(int(nc.year), int(nc.month), int(nc.day))
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def add_subtitle(fig: Figure, case_date: date, site_name: str) -> None:
|
|
898
|
+
"""Adds subtitle into figure."""
|
|
899
|
+
text = _get_subtitle_text(case_date, site_name)
|
|
900
|
+
fig.suptitle(
|
|
901
|
+
text,
|
|
902
|
+
fontsize=13,
|
|
903
|
+
y=0.885,
|
|
904
|
+
x=0.07,
|
|
905
|
+
horizontalalignment="left",
|
|
906
|
+
verticalalignment="bottom",
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _get_subtitle_text(case_date: date, site_name: str) -> str:
|
|
911
|
+
site_name = site_name.replace("-", " ")
|
|
912
|
+
return f"{site_name}, {case_date.strftime('%d %b %Y').lstrip('0')}"
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _create_save_name(
|
|
916
|
+
save_path: str,
|
|
917
|
+
case_date: date,
|
|
918
|
+
field_names: list,
|
|
919
|
+
fix: str = "",
|
|
920
|
+
) -> str:
|
|
921
|
+
"""Creates file name for saved images."""
|
|
922
|
+
date_string = case_date.strftime("%Y%m%d")
|
|
923
|
+
return f"{save_path}{date_string}_{'_'.join(field_names)}{fix}.png"
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def handle_saving(
|
|
927
|
+
image_name: str | None,
|
|
928
|
+
save_path: str | None,
|
|
929
|
+
case_date: date,
|
|
930
|
+
field_names: list,
|
|
931
|
+
fix: str = "",
|
|
932
|
+
*,
|
|
933
|
+
show: bool = False,
|
|
934
|
+
) -> None:
|
|
935
|
+
if image_name:
|
|
936
|
+
plt.savefig(image_name, bbox_inches="tight")
|
|
937
|
+
elif save_path:
|
|
938
|
+
file_name = _create_save_name(save_path, case_date, field_names, fix)
|
|
939
|
+
plt.savefig(file_name, bbox_inches="tight")
|
|
940
|
+
if show:
|
|
941
|
+
plt.show()
|
|
942
|
+
plt.close()
|