tobac 1.6.2__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.
- tobac/__init__.py +112 -0
- tobac/analysis/__init__.py +31 -0
- tobac/analysis/cell_analysis.py +628 -0
- tobac/analysis/feature_analysis.py +212 -0
- tobac/analysis/spatial.py +619 -0
- tobac/centerofgravity.py +226 -0
- tobac/feature_detection.py +1758 -0
- tobac/merge_split.py +324 -0
- tobac/plotting.py +2321 -0
- tobac/segmentation/__init__.py +10 -0
- tobac/segmentation/watershed_segmentation.py +1316 -0
- tobac/testing.py +1179 -0
- tobac/tests/segmentation_tests/test_iris_xarray_segmentation.py +0 -0
- tobac/tests/segmentation_tests/test_segmentation.py +1183 -0
- tobac/tests/segmentation_tests/test_segmentation_time_pad.py +104 -0
- tobac/tests/test_analysis_spatial.py +1109 -0
- tobac/tests/test_convert.py +265 -0
- tobac/tests/test_datetime.py +216 -0
- tobac/tests/test_decorators.py +148 -0
- tobac/tests/test_feature_detection.py +1321 -0
- tobac/tests/test_generators.py +273 -0
- tobac/tests/test_import.py +24 -0
- tobac/tests/test_iris_xarray_match_utils.py +244 -0
- tobac/tests/test_merge_split.py +351 -0
- tobac/tests/test_pbc_utils.py +497 -0
- tobac/tests/test_sample_data.py +197 -0
- tobac/tests/test_testing.py +747 -0
- tobac/tests/test_tracking.py +714 -0
- tobac/tests/test_utils.py +650 -0
- tobac/tests/test_utils_bulk_statistics.py +789 -0
- tobac/tests/test_utils_coordinates.py +328 -0
- tobac/tests/test_utils_internal.py +97 -0
- tobac/tests/test_xarray_utils.py +232 -0
- tobac/tracking.py +613 -0
- tobac/utils/__init__.py +27 -0
- tobac/utils/bulk_statistics.py +360 -0
- tobac/utils/datetime.py +184 -0
- tobac/utils/decorators.py +540 -0
- tobac/utils/general.py +753 -0
- tobac/utils/generators.py +87 -0
- tobac/utils/internal/__init__.py +2 -0
- tobac/utils/internal/coordinates.py +430 -0
- tobac/utils/internal/iris_utils.py +462 -0
- tobac/utils/internal/label_props.py +82 -0
- tobac/utils/internal/xarray_utils.py +439 -0
- tobac/utils/mask.py +364 -0
- tobac/utils/periodic_boundaries.py +419 -0
- tobac/wrapper.py +244 -0
- tobac-1.6.2.dist-info/METADATA +154 -0
- tobac-1.6.2.dist-info/RECORD +53 -0
- tobac-1.6.2.dist-info/WHEEL +5 -0
- tobac-1.6.2.dist-info/licenses/LICENSE +29 -0
- tobac-1.6.2.dist-info/top_level.txt +1 -0
tobac/plotting.py
ADDED
|
@@ -0,0 +1,2321 @@
|
|
|
1
|
+
"""Provide methods for plotting analyzed data.
|
|
2
|
+
|
|
3
|
+
Plotting routines including both visualizations for
|
|
4
|
+
the entire dataset including all tracks, and detailed
|
|
5
|
+
visualizations for individual cells and their properties.
|
|
6
|
+
|
|
7
|
+
References
|
|
8
|
+
----------
|
|
9
|
+
.. Heikenfeld, M., Marinescu, P. J., Christensen, M.,
|
|
10
|
+
Watson-Parris, D., Senf, F., van den Heever, S. C.
|
|
11
|
+
& Stier, P. (2019). tobac 1.2: towards a flexible
|
|
12
|
+
framework for tracking and analysis of clouds in
|
|
13
|
+
diverse datasets. Geoscientific Model Development,
|
|
14
|
+
12(11), 4551-4570.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import warnings
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import matplotlib as mpl
|
|
23
|
+
|
|
24
|
+
from tobac.analysis.cell_analysis import (
|
|
25
|
+
lifetime_histogram,
|
|
26
|
+
histogram_cellwise,
|
|
27
|
+
)
|
|
28
|
+
from tobac.analysis.feature_analysis import histogram_featurewise
|
|
29
|
+
from tobac.utils import decorators
|
|
30
|
+
from tobac.utils.internal.coordinates import find_dataframe_horizontal_coords
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def plot_tracks_mask_field_loop(
|
|
34
|
+
track,
|
|
35
|
+
field,
|
|
36
|
+
mask,
|
|
37
|
+
features,
|
|
38
|
+
axes=None,
|
|
39
|
+
name=None,
|
|
40
|
+
plot_dir="./",
|
|
41
|
+
figsize=(10.0 / 2.54, 10.0 / 2.54),
|
|
42
|
+
dpi=300,
|
|
43
|
+
margin_left=0.05,
|
|
44
|
+
margin_right=0.05,
|
|
45
|
+
margin_bottom=0.05,
|
|
46
|
+
margin_top=0.05,
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
"""Plot field, feature positions and segments
|
|
50
|
+
onto individual maps for all timeframes and
|
|
51
|
+
save them as pngs.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
track : pandas.DataFrame
|
|
56
|
+
Output of linking_trackpy.
|
|
57
|
+
|
|
58
|
+
field : iris.cube.Cube
|
|
59
|
+
Original input data.
|
|
60
|
+
|
|
61
|
+
mask : iris.cube.Cube
|
|
62
|
+
Cube containing mask (int id for tacked volumes, 0
|
|
63
|
+
everywhere else). Output of the segmentation step.
|
|
64
|
+
|
|
65
|
+
features : pandas.DataFrame
|
|
66
|
+
Output of the feature detection.
|
|
67
|
+
|
|
68
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional
|
|
69
|
+
Not used. Default is None.
|
|
70
|
+
|
|
71
|
+
name : str, optional
|
|
72
|
+
Filename without file extension. Same for all pngs. If None,
|
|
73
|
+
the name of the field is used. Default is None.
|
|
74
|
+
|
|
75
|
+
plot_dir : str, optional
|
|
76
|
+
Path where the plots will be saved. Default is './'.
|
|
77
|
+
|
|
78
|
+
figsize : tuple of floats, optional
|
|
79
|
+
Width, height of the plot in inches.
|
|
80
|
+
Default is (10/2.54, 10/2.54).
|
|
81
|
+
|
|
82
|
+
dpi : int, optional
|
|
83
|
+
Plot resolution. Default is 300.
|
|
84
|
+
|
|
85
|
+
margin_left : float, optional
|
|
86
|
+
The position of the left edge of the axes, as a
|
|
87
|
+
fraction of the figure width. Default is 0.05.
|
|
88
|
+
|
|
89
|
+
margin_right : float, optional
|
|
90
|
+
The position of the right edge of the axes, as a
|
|
91
|
+
fraction of the figure width. Default is 0.05.
|
|
92
|
+
|
|
93
|
+
margin_bottom : float, optional
|
|
94
|
+
The position of the bottom edge of the axes, as a
|
|
95
|
+
fraction of the figure width. Default is 0.05.
|
|
96
|
+
|
|
97
|
+
margin_top : float, optional
|
|
98
|
+
The position of the top edge of the axes, as a
|
|
99
|
+
fraction of the figure width. Default is 0.05.
|
|
100
|
+
|
|
101
|
+
**kwargs
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
None
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
mpl_backend = mpl.get_backend()
|
|
109
|
+
if mpl_backend != "agg":
|
|
110
|
+
warnings.warn(
|
|
111
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
112
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
113
|
+
)
|
|
114
|
+
import matplotlib.pyplot as plt
|
|
115
|
+
import cartopy.crs as ccrs
|
|
116
|
+
import os
|
|
117
|
+
from iris import Constraint
|
|
118
|
+
|
|
119
|
+
os.makedirs(plot_dir, exist_ok=True)
|
|
120
|
+
time = mask.coord("time")
|
|
121
|
+
if name is None:
|
|
122
|
+
name = field.name()
|
|
123
|
+
for time_i in time.points:
|
|
124
|
+
datetime_i = time.units.num2date(time_i)
|
|
125
|
+
constraint_time = Constraint(time=datetime_i)
|
|
126
|
+
fig1, ax1 = plt.subplots(
|
|
127
|
+
ncols=1,
|
|
128
|
+
nrows=1,
|
|
129
|
+
figsize=figsize,
|
|
130
|
+
subplot_kw={"projection": ccrs.PlateCarree()},
|
|
131
|
+
)
|
|
132
|
+
datestring_file = datetime_i.strftime("%Y-%m-%d_%H:%M:%S")
|
|
133
|
+
field_i = field.extract(constraint_time)
|
|
134
|
+
mask_i = mask.extract(constraint_time)
|
|
135
|
+
track_i = track[track["time"] == datetime_i]
|
|
136
|
+
features_i = features[features["time"] == datetime_i]
|
|
137
|
+
ax1 = plot_tracks_mask_field(
|
|
138
|
+
track=track_i,
|
|
139
|
+
field=field_i,
|
|
140
|
+
mask=mask_i,
|
|
141
|
+
features=features_i,
|
|
142
|
+
axes=ax1,
|
|
143
|
+
**kwargs,
|
|
144
|
+
)
|
|
145
|
+
fig1.subplots_adjust(
|
|
146
|
+
left=margin_left,
|
|
147
|
+
bottom=margin_bottom,
|
|
148
|
+
right=1 - margin_right,
|
|
149
|
+
top=1 - margin_top,
|
|
150
|
+
)
|
|
151
|
+
os.makedirs(plot_dir, exist_ok=True)
|
|
152
|
+
savepath_png = os.path.join(plot_dir, name + "_" + datestring_file + ".png")
|
|
153
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
154
|
+
logging.debug("Figure plotted to " + str(savepath_png))
|
|
155
|
+
|
|
156
|
+
plt.close()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@decorators.xarray_to_iris()
|
|
160
|
+
def plot_tracks_mask_field(
|
|
161
|
+
track,
|
|
162
|
+
field,
|
|
163
|
+
mask,
|
|
164
|
+
features,
|
|
165
|
+
axes=None,
|
|
166
|
+
axis_extent=None,
|
|
167
|
+
plot_outline=True,
|
|
168
|
+
plot_marker=True,
|
|
169
|
+
marker_track="x",
|
|
170
|
+
markersize_track=4,
|
|
171
|
+
plot_number=True,
|
|
172
|
+
plot_features=False,
|
|
173
|
+
marker_feature=None,
|
|
174
|
+
markersize_feature=None,
|
|
175
|
+
title=None,
|
|
176
|
+
title_str=None,
|
|
177
|
+
vmin=None,
|
|
178
|
+
vmax=None,
|
|
179
|
+
n_levels=50,
|
|
180
|
+
cmap="viridis",
|
|
181
|
+
extend="neither",
|
|
182
|
+
orientation_colorbar="horizontal",
|
|
183
|
+
pad_colorbar=0.05,
|
|
184
|
+
label_colorbar=None,
|
|
185
|
+
fraction_colorbar=0.046,
|
|
186
|
+
rasterized=True,
|
|
187
|
+
linewidth_contour=1,
|
|
188
|
+
):
|
|
189
|
+
"""Plot field, features and segments of a timeframe and
|
|
190
|
+
on a map projection. It is required to pass vmin, vmax,
|
|
191
|
+
axes and axis_extent as keyword arguments.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
track : pandas.DataFrame
|
|
196
|
+
One or more timeframes of a dataframe generated by
|
|
197
|
+
linking_trackpy.
|
|
198
|
+
|
|
199
|
+
field : iris.cube.Cube
|
|
200
|
+
One frame/time step of the original input data.
|
|
201
|
+
|
|
202
|
+
mask : iris.cube.Cube
|
|
203
|
+
One frame/time step of the Cube containing mask (int id
|
|
204
|
+
for tracked volumes 0 everywhere else), output of the
|
|
205
|
+
segmentation step.
|
|
206
|
+
|
|
207
|
+
features : pandas.DataFrame
|
|
208
|
+
Output of the feature detection, one or more frames/time steps.
|
|
209
|
+
|
|
210
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot
|
|
211
|
+
GeoAxesSubplot to use for plotting. Default is None.
|
|
212
|
+
|
|
213
|
+
axis_extent : ndarray
|
|
214
|
+
Array containing the bounds of the longitude and latitude
|
|
215
|
+
values. The structure is
|
|
216
|
+
[long_min, long_max, lat_min, lat_max]. Default is None.
|
|
217
|
+
|
|
218
|
+
plot_outline : bool, optional
|
|
219
|
+
Boolean defining whether the outlines of the segments are
|
|
220
|
+
plotted. Default is True.
|
|
221
|
+
|
|
222
|
+
plot_marker : bool, optional
|
|
223
|
+
Boolean defining whether the positions of the features from
|
|
224
|
+
the track dataframe are plotted. Default is True.
|
|
225
|
+
|
|
226
|
+
marker_track : str, optional
|
|
227
|
+
String defining the shape of the marker for the feature
|
|
228
|
+
positions from the track dataframe. Default is 'x'.
|
|
229
|
+
|
|
230
|
+
markersize_track : int, optional
|
|
231
|
+
Int defining the size of the marker for the feature
|
|
232
|
+
positions from the track dataframe. Default is 4.
|
|
233
|
+
|
|
234
|
+
plot_number : bool, optional
|
|
235
|
+
Boolean defining wether the index of the cells
|
|
236
|
+
is plotted next to the individual feature position.
|
|
237
|
+
Default is True.
|
|
238
|
+
|
|
239
|
+
plot_features : bool, optional
|
|
240
|
+
Boolean defining wether the positions of the features from
|
|
241
|
+
the features dataframe are plotted. Default is True.
|
|
242
|
+
|
|
243
|
+
marker_feature : optional
|
|
244
|
+
String defining the shape of the marker for the feature
|
|
245
|
+
positions from the features dataframe. Default is None.
|
|
246
|
+
|
|
247
|
+
markersize_feature : optional
|
|
248
|
+
Int defining the size of the marker for the feature
|
|
249
|
+
positions from the features dataframe. Default is None.
|
|
250
|
+
|
|
251
|
+
title : str, optional
|
|
252
|
+
Flag determining the title of the plot. 'datestr' uses
|
|
253
|
+
date and time of the field. None sets not title.
|
|
254
|
+
Default is None.
|
|
255
|
+
|
|
256
|
+
title_str : str, optional
|
|
257
|
+
Additional string added to the beginning of the title.
|
|
258
|
+
Default is None.
|
|
259
|
+
|
|
260
|
+
vmin : float
|
|
261
|
+
Lower bound of the colorbar. Default is None.
|
|
262
|
+
|
|
263
|
+
vmax : float
|
|
264
|
+
Upper bound of the colorbar. Default is None.
|
|
265
|
+
|
|
266
|
+
n_levels : int, optional
|
|
267
|
+
Number of levels of the contour plot of the field.
|
|
268
|
+
Default is 50.
|
|
269
|
+
|
|
270
|
+
cmap : {'viridis',...}, optional
|
|
271
|
+
Colormap of the countour plot of the field.
|
|
272
|
+
matplotlib.colors. Default is 'viridis'.
|
|
273
|
+
|
|
274
|
+
extend : str, optional
|
|
275
|
+
Determines the coloring of values that are
|
|
276
|
+
outside the levels range. If 'neither', values outside
|
|
277
|
+
the levels range are not colored. If 'min', 'max' or
|
|
278
|
+
'both', color the values below, above or below and above
|
|
279
|
+
the levels range. Values below min(levels) and above
|
|
280
|
+
max(levels) are mapped to the under/over values of the
|
|
281
|
+
Colormap. Default is 'neither'.
|
|
282
|
+
|
|
283
|
+
orientation_colorbar : str, optional
|
|
284
|
+
Orientation of the colorbar, 'horizontal' or 'vertical'
|
|
285
|
+
Default is 'horizontal'.
|
|
286
|
+
|
|
287
|
+
pad_colorbar : float, optional
|
|
288
|
+
Fraction of original axes between colorbar and new
|
|
289
|
+
image axes. Default is 0.05.
|
|
290
|
+
|
|
291
|
+
label_colorbar : str, optional
|
|
292
|
+
Label of the colorbar. If none, name and unit of
|
|
293
|
+
the field are used. Default is None.
|
|
294
|
+
|
|
295
|
+
fraction_colorbar : float, optional
|
|
296
|
+
Fraction of original axes to use for colorbar.
|
|
297
|
+
Default is 0.046.
|
|
298
|
+
|
|
299
|
+
rasterized : bool, optional
|
|
300
|
+
True enables, False disables rasterization.
|
|
301
|
+
Default is True.
|
|
302
|
+
|
|
303
|
+
linewidth_contour : int, optional
|
|
304
|
+
Linewidth of the contour plot of the segments.
|
|
305
|
+
Default is 1.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot
|
|
310
|
+
Axes with the plot.
|
|
311
|
+
|
|
312
|
+
Raises
|
|
313
|
+
------
|
|
314
|
+
ValueError
|
|
315
|
+
If axes are not cartopy.mpl.geoaxes.GeoAxesSubplot.
|
|
316
|
+
|
|
317
|
+
If mask.ndim is neither 2 nor 3.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
import matplotlib.pyplot as plt
|
|
321
|
+
|
|
322
|
+
import cartopy
|
|
323
|
+
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
|
|
324
|
+
import iris.plot as iplt
|
|
325
|
+
from matplotlib.ticker import MaxNLocator
|
|
326
|
+
import cartopy.feature as cfeature
|
|
327
|
+
from .utils import mask_features, mask_features_surface
|
|
328
|
+
from matplotlib import ticker
|
|
329
|
+
|
|
330
|
+
if type(axes) is not cartopy.mpl.geoaxes.GeoAxesSubplot:
|
|
331
|
+
raise ValueError("axes had to be cartopy.mpl.geoaxes.GeoAxesSubplot")
|
|
332
|
+
|
|
333
|
+
datestr = (
|
|
334
|
+
field.coord("time")
|
|
335
|
+
.units.num2date(field.coord("time").points[0])
|
|
336
|
+
.strftime("%Y-%m-%d %H:%M:%S")
|
|
337
|
+
)
|
|
338
|
+
if title == "datestr":
|
|
339
|
+
if title_str is None:
|
|
340
|
+
titlestring = datestr
|
|
341
|
+
elif type(title_str is str):
|
|
342
|
+
titlestring = title + " " + datestr
|
|
343
|
+
axes.set_title(titlestring, horizontalalignment="left", loc="left")
|
|
344
|
+
|
|
345
|
+
gl = axes.gridlines(draw_labels=True)
|
|
346
|
+
majorLocator = MaxNLocator(nbins=5, steps=[1, 2, 5, 10])
|
|
347
|
+
gl.xlocator = majorLocator
|
|
348
|
+
gl.ylocator = majorLocator
|
|
349
|
+
gl.xformatter = LONGITUDE_FORMATTER
|
|
350
|
+
axes.tick_params(axis="both", which="major")
|
|
351
|
+
gl.yformatter = LATITUDE_FORMATTER
|
|
352
|
+
gl.xlabels_top = False
|
|
353
|
+
gl.ylabels_right = False
|
|
354
|
+
axes.coastlines("10m")
|
|
355
|
+
# rivers=cfeature.NaturalEarthFeature(category='physical', name='rivers_lake_centerlines',scale='10m',facecolor='none')
|
|
356
|
+
lakes = cfeature.NaturalEarthFeature(
|
|
357
|
+
category="physical", name="lakes", scale="10m", facecolor="none"
|
|
358
|
+
)
|
|
359
|
+
axes.add_feature(lakes, edgecolor="black")
|
|
360
|
+
axes.set_xlabel("longitude")
|
|
361
|
+
axes.set_ylabel("latitude")
|
|
362
|
+
|
|
363
|
+
# Plot the background field
|
|
364
|
+
if np.any(
|
|
365
|
+
~np.isnan(field.data)
|
|
366
|
+
): # check if field to plot is not only nan, which causes error:
|
|
367
|
+
plot_field = iplt.contourf(
|
|
368
|
+
field,
|
|
369
|
+
coords=["longitude", "latitude"],
|
|
370
|
+
levels=np.linspace(vmin, vmax, num=n_levels),
|
|
371
|
+
extend=extend,
|
|
372
|
+
axes=axes,
|
|
373
|
+
cmap=cmap,
|
|
374
|
+
vmin=vmin,
|
|
375
|
+
vmax=vmax,
|
|
376
|
+
zorder=1,
|
|
377
|
+
)
|
|
378
|
+
if rasterized:
|
|
379
|
+
axes.set_rasterization_zorder(1)
|
|
380
|
+
# create colorbar for background field:
|
|
381
|
+
cbar = plt.colorbar(
|
|
382
|
+
plot_field,
|
|
383
|
+
orientation=orientation_colorbar,
|
|
384
|
+
pad=pad_colorbar,
|
|
385
|
+
fraction=fraction_colorbar,
|
|
386
|
+
ax=axes,
|
|
387
|
+
)
|
|
388
|
+
if label_colorbar is None:
|
|
389
|
+
label_colorbar = field.name() + "(" + field.units.symbol + ")"
|
|
390
|
+
if orientation_colorbar == "horizontal":
|
|
391
|
+
cbar.ax.set_xlabel(label_colorbar)
|
|
392
|
+
elif orientation_colorbar == "vertical":
|
|
393
|
+
cbar.ax.set_ylabel(label_colorbar)
|
|
394
|
+
tick_locator = ticker.MaxNLocator(nbins=5)
|
|
395
|
+
cbar.locator = tick_locator
|
|
396
|
+
cbar.update_ticks()
|
|
397
|
+
|
|
398
|
+
colors_mask = ["darkred", "orange", "crimson", "red", "darkorange"]
|
|
399
|
+
|
|
400
|
+
# if marker_feature is not explicitly given, set it to marker_track (will then be overwritten by the coloured markers)
|
|
401
|
+
if marker_feature is None:
|
|
402
|
+
maker_feature = marker_track
|
|
403
|
+
if markersize_feature is None:
|
|
404
|
+
makersize_feature = markersize_track
|
|
405
|
+
|
|
406
|
+
# Plot the identified features by looping over rows of DataFrame:
|
|
407
|
+
features_lat_dim, features_lon_dim, _ = find_dataframe_horizontal_coords(
|
|
408
|
+
features, coord_type="latlon"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if plot_features:
|
|
412
|
+
for _, row in features.iterrows():
|
|
413
|
+
axes.plot(
|
|
414
|
+
row[features_lon_dim],
|
|
415
|
+
row[features_lat_dim],
|
|
416
|
+
color="grey",
|
|
417
|
+
marker=maker_feature,
|
|
418
|
+
markersize=makersize_feature,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# restrict features to featues inside axis extent
|
|
422
|
+
track_lat_dim, track_lon_dim, _ = find_dataframe_horizontal_coords(
|
|
423
|
+
features, coord_type="latlon"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
track = track.loc[
|
|
427
|
+
(track[track_lon_dim] > axis_extent[0])
|
|
428
|
+
& (track[track_lon_dim] < axis_extent[1])
|
|
429
|
+
& (track[track_lat_dim] > axis_extent[2])
|
|
430
|
+
& (track[track_lat_dim] < axis_extent[3])
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
# Plot tracked features by looping over rows of Dataframe
|
|
434
|
+
for i_row, row in track.iterrows():
|
|
435
|
+
feature = row["feature"]
|
|
436
|
+
cell = row["cell"]
|
|
437
|
+
if not np.isnan(cell):
|
|
438
|
+
color = colors_mask[int(cell % len(colors_mask))]
|
|
439
|
+
|
|
440
|
+
if plot_number:
|
|
441
|
+
cell_string = " " + str(int(row["cell"]))
|
|
442
|
+
axes.text(
|
|
443
|
+
row[features_lon_dim],
|
|
444
|
+
row[features_lat_dim],
|
|
445
|
+
cell_string,
|
|
446
|
+
color=color,
|
|
447
|
+
fontsize=6,
|
|
448
|
+
clip_on=True,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
else:
|
|
452
|
+
color = "grey"
|
|
453
|
+
|
|
454
|
+
if plot_outline:
|
|
455
|
+
mask_i = None
|
|
456
|
+
# if mask is 3D, create surface projection, if mask is 2D keep the mask
|
|
457
|
+
if mask.ndim == 2:
|
|
458
|
+
mask_i = mask_features(mask, feature, masked=False)
|
|
459
|
+
elif mask.ndim == 3:
|
|
460
|
+
mask_i = mask_features_surface(
|
|
461
|
+
mask, feature, masked=False, z_coord="model_level_number"
|
|
462
|
+
)
|
|
463
|
+
else:
|
|
464
|
+
raise ValueError("mask has shape that cannot be understood")
|
|
465
|
+
# plot countour lines around the edges of the mask
|
|
466
|
+
iplt.contour(
|
|
467
|
+
mask_i,
|
|
468
|
+
coords=["longitude", "latitude"],
|
|
469
|
+
levels=[0, feature],
|
|
470
|
+
colors=color,
|
|
471
|
+
linewidths=linewidth_contour,
|
|
472
|
+
axes=axes,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if plot_marker:
|
|
476
|
+
axes.plot(
|
|
477
|
+
row[features_lon_dim],
|
|
478
|
+
row[features_lat_dim],
|
|
479
|
+
color=color,
|
|
480
|
+
marker=marker_track,
|
|
481
|
+
markersize=markersize_track,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
axes.set_extent(axis_extent)
|
|
485
|
+
return axes
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@decorators.iris_to_xarray()
|
|
489
|
+
def animation_mask_field(
|
|
490
|
+
track, features, field, mask, interval=500, figsize=(10, 10), **kwargs
|
|
491
|
+
):
|
|
492
|
+
"""Create animation of field, features and segments of
|
|
493
|
+
all timeframes.
|
|
494
|
+
|
|
495
|
+
Parameters
|
|
496
|
+
----------
|
|
497
|
+
track : pandas.DataFrame
|
|
498
|
+
Output of linking_trackpy.
|
|
499
|
+
|
|
500
|
+
features : pandas.DataFrame
|
|
501
|
+
Output of the feature detection.
|
|
502
|
+
|
|
503
|
+
field : iris.cube.Cube
|
|
504
|
+
Original input data.
|
|
505
|
+
|
|
506
|
+
mask : iris.cube.Cube
|
|
507
|
+
Cube containing mask (int id for tacked volumes 0
|
|
508
|
+
everywhere else), output of the segmentation step.
|
|
509
|
+
|
|
510
|
+
interval : int, optional
|
|
511
|
+
Delay between frames in milliseconds.
|
|
512
|
+
Default is 500.
|
|
513
|
+
|
|
514
|
+
figsize : tupel of float, optional
|
|
515
|
+
Width, height of the plot in inches.
|
|
516
|
+
Default is (10, 10).
|
|
517
|
+
|
|
518
|
+
**kwargs
|
|
519
|
+
|
|
520
|
+
Returns
|
|
521
|
+
-------
|
|
522
|
+
animation : matplotlib.animation.FuncAnimation
|
|
523
|
+
Created animation as object.
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
mpl_backend = mpl.get_backend()
|
|
527
|
+
if mpl_backend != "agg":
|
|
528
|
+
warnings.warn(
|
|
529
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
530
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
531
|
+
)
|
|
532
|
+
import matplotlib.pyplot as plt
|
|
533
|
+
|
|
534
|
+
import cartopy.crs as ccrs
|
|
535
|
+
import matplotlib.pyplot as plt
|
|
536
|
+
import matplotlib.animation
|
|
537
|
+
from iris import Constraint
|
|
538
|
+
|
|
539
|
+
fig = plt.figure(figsize=figsize)
|
|
540
|
+
plt.close()
|
|
541
|
+
|
|
542
|
+
tracks_gb = track.groupby("time")
|
|
543
|
+
features_gb = features.groupby("time")
|
|
544
|
+
|
|
545
|
+
def update(time_in):
|
|
546
|
+
fig.clf()
|
|
547
|
+
ax = fig.add_subplot(111, projection=ccrs.PlateCarree())
|
|
548
|
+
field_i = field.sel(time=time_in)
|
|
549
|
+
mask_i = mask.sel(time=time_in)
|
|
550
|
+
track_i = tracks_gb.get_group(time_in)
|
|
551
|
+
features_i = features_gb.get_group(time_in)
|
|
552
|
+
# fig1,ax1=plt.subplots(ncols=1, nrows=1,figsize=figsize, subplot_kw={'projection': ccrs.PlateCarree()})
|
|
553
|
+
plot_tobac = plot_tracks_mask_field(
|
|
554
|
+
track_i, field=field_i, mask=mask_i, features=features_i, axes=ax, **kwargs
|
|
555
|
+
)
|
|
556
|
+
ax.set_title("{}".format(time_in))
|
|
557
|
+
|
|
558
|
+
animation = matplotlib.animation.FuncAnimation(
|
|
559
|
+
fig,
|
|
560
|
+
update,
|
|
561
|
+
init_func=None,
|
|
562
|
+
frames=field.time.values,
|
|
563
|
+
interval=interval,
|
|
564
|
+
blit=False,
|
|
565
|
+
)
|
|
566
|
+
return animation
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def plot_mask_cell_track_follow(
|
|
570
|
+
cell,
|
|
571
|
+
track,
|
|
572
|
+
cog,
|
|
573
|
+
features,
|
|
574
|
+
mask_total,
|
|
575
|
+
field_contour,
|
|
576
|
+
field_filled,
|
|
577
|
+
width=10000,
|
|
578
|
+
name="test",
|
|
579
|
+
plotdir="./",
|
|
580
|
+
file_format=["png"],
|
|
581
|
+
figsize=(10 / 2.54, 10 / 2.54),
|
|
582
|
+
dpi=300,
|
|
583
|
+
**kwargs,
|
|
584
|
+
):
|
|
585
|
+
"""Make plots for all cells centred around cell and with one background field as filling and one background field as contrours
|
|
586
|
+
Input:
|
|
587
|
+
Output:
|
|
588
|
+
"""
|
|
589
|
+
warnings.warn(
|
|
590
|
+
"plot_mask_cell_track_follow is depreciated and will be removed or significantly changed in v2.0.",
|
|
591
|
+
DeprecationWarning,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
mpl_backend = mpl.get_backend()
|
|
595
|
+
if mpl_backend != "agg":
|
|
596
|
+
warnings.warn(
|
|
597
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
598
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
599
|
+
)
|
|
600
|
+
import matplotlib.pyplot as plt
|
|
601
|
+
|
|
602
|
+
from iris import Constraint
|
|
603
|
+
from numpy import unique
|
|
604
|
+
import os
|
|
605
|
+
|
|
606
|
+
track_cell = track[track["cell"] == cell]
|
|
607
|
+
for i_row, row in track_cell.iterrows():
|
|
608
|
+
constraint_time = Constraint(time=row["time"])
|
|
609
|
+
constraint_x = Constraint(
|
|
610
|
+
projection_x_coordinate=lambda cell: row["projection_x_coordinate"] - width
|
|
611
|
+
< cell
|
|
612
|
+
< row["projection_x_coordinate"] + width
|
|
613
|
+
)
|
|
614
|
+
constraint_y = Constraint(
|
|
615
|
+
projection_y_coordinate=lambda cell: row["projection_y_coordinate"] - width
|
|
616
|
+
< cell
|
|
617
|
+
< row["projection_y_coordinate"] + width
|
|
618
|
+
)
|
|
619
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
620
|
+
mask_total_i = mask_total.extract(constraint)
|
|
621
|
+
if field_contour is None:
|
|
622
|
+
field_contour_i = None
|
|
623
|
+
else:
|
|
624
|
+
field_contour_i = field_contour.extract(constraint)
|
|
625
|
+
if field_filled is None:
|
|
626
|
+
field_filled_i = None
|
|
627
|
+
else:
|
|
628
|
+
field_filled_i = field_filled.extract(constraint)
|
|
629
|
+
|
|
630
|
+
cells = list(unique(mask_total_i.core_data()))
|
|
631
|
+
if cell not in cells:
|
|
632
|
+
cells.append(cell)
|
|
633
|
+
if 0 in cells:
|
|
634
|
+
cells.remove(0)
|
|
635
|
+
track_i = track[track["cell"].isin(cells)]
|
|
636
|
+
track_i = track_i[track_i["time"] == row["time"]]
|
|
637
|
+
if cog is None:
|
|
638
|
+
cog_i = None
|
|
639
|
+
else:
|
|
640
|
+
cog_i = cog[cog["cell"].isin(cells)]
|
|
641
|
+
cog_i = cog_i[cog_i["time"] == row["time"]]
|
|
642
|
+
|
|
643
|
+
if features is None:
|
|
644
|
+
features_i = None
|
|
645
|
+
else:
|
|
646
|
+
features_i = features[features["time"] == row["time"]]
|
|
647
|
+
|
|
648
|
+
fig1, ax1 = plt.subplots(ncols=1, nrows=1, figsize=figsize)
|
|
649
|
+
fig1.subplots_adjust(left=0.2, bottom=0.15, right=0.85, top=0.80)
|
|
650
|
+
|
|
651
|
+
datestring_stamp = row["time"].strftime("%Y-%m-%d %H:%M:%S")
|
|
652
|
+
celltime_stamp = "%02d:%02d:%02d" % (
|
|
653
|
+
row["time_cell"].dt.total_seconds() // 3600,
|
|
654
|
+
(row["time_cell"].dt.total_seconds() % 3600) // 60,
|
|
655
|
+
row["time_cell"].dt.total_seconds() % 60,
|
|
656
|
+
)
|
|
657
|
+
title = datestring_stamp + " , " + celltime_stamp
|
|
658
|
+
datestring_file = row["time"].strftime("%Y-%m-%d_%H%M%S")
|
|
659
|
+
|
|
660
|
+
ax1 = plot_mask_cell_individual_follow(
|
|
661
|
+
cell_i=cell,
|
|
662
|
+
track=track_i,
|
|
663
|
+
cog=cog_i,
|
|
664
|
+
features=features_i,
|
|
665
|
+
mask_total=mask_total_i,
|
|
666
|
+
field_contour=field_contour_i,
|
|
667
|
+
field_filled=field_filled_i,
|
|
668
|
+
width=width,
|
|
669
|
+
axes=ax1,
|
|
670
|
+
title=title,
|
|
671
|
+
**kwargs,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
out_dir = os.path.join(plotdir, name)
|
|
675
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
676
|
+
if "png" in file_format:
|
|
677
|
+
savepath_png = os.path.join(out_dir, name + "_" + datestring_file + ".png")
|
|
678
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
679
|
+
logging.debug(
|
|
680
|
+
"field_contour field_filled Mask plot saved to " + savepath_png
|
|
681
|
+
)
|
|
682
|
+
if "pdf" in file_format:
|
|
683
|
+
savepath_pdf = os.path.join(out_dir, name + "_" + datestring_file + ".pdf")
|
|
684
|
+
fig1.savefig(savepath_pdf, dpi=dpi)
|
|
685
|
+
logging.debug(
|
|
686
|
+
"field_contour field_filled Mask plot saved to " + savepath_pdf
|
|
687
|
+
)
|
|
688
|
+
plt.close()
|
|
689
|
+
plt.clf()
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def plot_mask_cell_individual_follow(
|
|
693
|
+
cell_i,
|
|
694
|
+
track,
|
|
695
|
+
cog,
|
|
696
|
+
features,
|
|
697
|
+
mask_total,
|
|
698
|
+
field_contour,
|
|
699
|
+
field_filled,
|
|
700
|
+
axes=None,
|
|
701
|
+
width=10000,
|
|
702
|
+
label_field_contour=None,
|
|
703
|
+
cmap_field_contour="Blues",
|
|
704
|
+
norm_field_contour=None,
|
|
705
|
+
linewidths_contour=0.8,
|
|
706
|
+
contour_labels=False,
|
|
707
|
+
vmin_field_contour=0,
|
|
708
|
+
vmax_field_contour=50,
|
|
709
|
+
levels_field_contour=None,
|
|
710
|
+
nlevels_field_contour=10,
|
|
711
|
+
label_field_filled=None,
|
|
712
|
+
cmap_field_filled="summer",
|
|
713
|
+
norm_field_filled=None,
|
|
714
|
+
vmin_field_filled=0,
|
|
715
|
+
vmax_field_filled=100,
|
|
716
|
+
levels_field_filled=None,
|
|
717
|
+
nlevels_field_filled=10,
|
|
718
|
+
title=None,
|
|
719
|
+
):
|
|
720
|
+
"""Make individual plot for cell centred around cell and with one background field as filling and one background field as contrours
|
|
721
|
+
Input:
|
|
722
|
+
Output:
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
import matplotlib.pyplot as plt
|
|
726
|
+
|
|
727
|
+
import numpy as np
|
|
728
|
+
from .utils import mask_cell_surface
|
|
729
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
730
|
+
from matplotlib.colors import Normalize
|
|
731
|
+
|
|
732
|
+
warnings.warn(
|
|
733
|
+
"plot_mask_cell_individual_follow is depreciated and will be removed or significantly changed in v2.0.",
|
|
734
|
+
DeprecationWarning,
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
divider = make_axes_locatable(axes)
|
|
738
|
+
|
|
739
|
+
x_pos = track[track["cell"] == cell_i]["projection_x_coordinate"].item()
|
|
740
|
+
y_pos = track[track["cell"] == cell_i]["projection_y_coordinate"].item()
|
|
741
|
+
if field_filled is not None:
|
|
742
|
+
if levels_field_filled is None:
|
|
743
|
+
levels_field_filled = np.linspace(
|
|
744
|
+
vmin_field_filled, vmax_field_filled, nlevels_field_filled
|
|
745
|
+
)
|
|
746
|
+
plot_field_filled = axes.contourf(
|
|
747
|
+
(field_filled.coord("projection_x_coordinate").points - x_pos) / 1000,
|
|
748
|
+
(field_filled.coord("projection_y_coordinate").points - y_pos) / 1000,
|
|
749
|
+
field_filled.data,
|
|
750
|
+
cmap=cmap_field_filled,
|
|
751
|
+
norm=norm_field_filled,
|
|
752
|
+
levels=levels_field_filled,
|
|
753
|
+
vmin=vmin_field_filled,
|
|
754
|
+
vmax=vmax_field_filled,
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
cax_filled = divider.append_axes("right", size="5%", pad=0.1)
|
|
758
|
+
norm_filled = Normalize(vmin=vmin_field_filled, vmax=vmax_field_filled)
|
|
759
|
+
sm_filled = plt.cm.ScalarMappable(norm=norm_filled, cmap=plot_field_filled.cmap)
|
|
760
|
+
sm_filled.set_array([])
|
|
761
|
+
|
|
762
|
+
cbar_field_filled = plt.colorbar(
|
|
763
|
+
sm_filled, orientation="vertical", cax=cax_filled
|
|
764
|
+
)
|
|
765
|
+
cbar_field_filled.ax.set_ylabel(label_field_filled)
|
|
766
|
+
cbar_field_filled.set_clim(vmin_field_filled, vmax_field_filled)
|
|
767
|
+
|
|
768
|
+
if field_contour is not None:
|
|
769
|
+
if levels_field_contour is None:
|
|
770
|
+
levels_field_contour = np.linspace(
|
|
771
|
+
vmin_field_contour, vmax_field_contour, nlevels_field_contour
|
|
772
|
+
)
|
|
773
|
+
if norm_field_contour:
|
|
774
|
+
vmin_field_contour = (None,)
|
|
775
|
+
vmax_field_contour = (None,)
|
|
776
|
+
|
|
777
|
+
plot_field_contour = axes.contour(
|
|
778
|
+
(field_contour.coord("projection_x_coordinate").points - x_pos) / 1000,
|
|
779
|
+
(field_contour.coord("projection_y_coordinate").points - y_pos) / 1000,
|
|
780
|
+
field_contour.data,
|
|
781
|
+
cmap=cmap_field_contour,
|
|
782
|
+
norm=norm_field_contour,
|
|
783
|
+
levels=levels_field_contour,
|
|
784
|
+
vmin=vmin_field_contour,
|
|
785
|
+
vmax=vmax_field_contour,
|
|
786
|
+
linewidths=linewidths_contour,
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
if contour_labels:
|
|
790
|
+
axes.clabel(plot_field_contour, fontsize=10)
|
|
791
|
+
|
|
792
|
+
cax_contour = divider.append_axes("bottom", size="5%", pad=0.1)
|
|
793
|
+
if norm_field_contour:
|
|
794
|
+
vmin_field_contour = None
|
|
795
|
+
vmax_field_contour = None
|
|
796
|
+
norm_contour = norm_field_contour
|
|
797
|
+
else:
|
|
798
|
+
norm_contour = Normalize(vmin=vmin_field_contour, vmax=vmax_field_contour)
|
|
799
|
+
|
|
800
|
+
sm_contour = plt.cm.ScalarMappable(
|
|
801
|
+
norm=norm_contour, cmap=plot_field_contour.cmap
|
|
802
|
+
)
|
|
803
|
+
sm_contour.set_array([])
|
|
804
|
+
|
|
805
|
+
cbar_field_contour = plt.colorbar(
|
|
806
|
+
sm_contour,
|
|
807
|
+
orientation="horizontal",
|
|
808
|
+
ticks=levels_field_contour,
|
|
809
|
+
cax=cax_contour,
|
|
810
|
+
)
|
|
811
|
+
cbar_field_contour.ax.set_xlabel(label_field_contour)
|
|
812
|
+
cbar_field_contour.set_clim(vmin_field_contour, vmax_field_contour)
|
|
813
|
+
|
|
814
|
+
for i_row, row in track.iterrows():
|
|
815
|
+
cell = int(row["cell"])
|
|
816
|
+
if cell == cell_i:
|
|
817
|
+
color = "darkred"
|
|
818
|
+
else:
|
|
819
|
+
color = "darkorange"
|
|
820
|
+
|
|
821
|
+
cell_string = " " + str(int(row["cell"]))
|
|
822
|
+
axes.text(
|
|
823
|
+
(row["projection_x_coordinate"] - x_pos) / 1000,
|
|
824
|
+
(row["projection_y_coordinate"] - y_pos) / 1000,
|
|
825
|
+
cell_string,
|
|
826
|
+
color=color,
|
|
827
|
+
fontsize=6,
|
|
828
|
+
clip_on=True,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
# Plot marker for tracked cell centre as a cross
|
|
832
|
+
axes.plot(
|
|
833
|
+
(row["projection_x_coordinate"] - x_pos) / 1000,
|
|
834
|
+
(row["projection_y_coordinate"] - y_pos) / 1000,
|
|
835
|
+
"x",
|
|
836
|
+
color=color,
|
|
837
|
+
markersize=4,
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Create surface projection of mask for the respective cell and plot it in the right color
|
|
841
|
+
z_coord = "model_level_number"
|
|
842
|
+
if len(mask_total.shape) == 3:
|
|
843
|
+
mask_total_i_surface = mask_cell_surface(
|
|
844
|
+
mask_total, cell, track, masked=False, z_coord=z_coord
|
|
845
|
+
)
|
|
846
|
+
elif len(mask_total.shape) == 2:
|
|
847
|
+
mask_total_i_surface = mask_total
|
|
848
|
+
axes.contour(
|
|
849
|
+
(mask_total_i_surface.coord("projection_x_coordinate").points - x_pos)
|
|
850
|
+
/ 1000,
|
|
851
|
+
(mask_total_i_surface.coord("projection_y_coordinate").points - y_pos)
|
|
852
|
+
/ 1000,
|
|
853
|
+
mask_total_i_surface.data,
|
|
854
|
+
levels=[0, cell],
|
|
855
|
+
colors=color,
|
|
856
|
+
linestyles=":",
|
|
857
|
+
linewidth=1,
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
if cog is not None:
|
|
861
|
+
for i_row, row in cog.iterrows():
|
|
862
|
+
cell = row["cell"]
|
|
863
|
+
|
|
864
|
+
if cell == cell_i:
|
|
865
|
+
color = "darkred"
|
|
866
|
+
else:
|
|
867
|
+
color = "darkorange"
|
|
868
|
+
# plot marker for centre of gravity as a circle
|
|
869
|
+
axes.plot(
|
|
870
|
+
(row["x_M"] - x_pos) / 1000,
|
|
871
|
+
(row["y_M"] - y_pos) / 1000,
|
|
872
|
+
"o",
|
|
873
|
+
markeredgecolor=color,
|
|
874
|
+
markerfacecolor="None",
|
|
875
|
+
markersize=4,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
if features is not None:
|
|
879
|
+
for i_row, row in features.iterrows():
|
|
880
|
+
color = "purple"
|
|
881
|
+
axes.plot(
|
|
882
|
+
(row["projection_x_coordinate"] - x_pos) / 1000,
|
|
883
|
+
(row["projection_y_coordinate"] - y_pos) / 1000,
|
|
884
|
+
"+",
|
|
885
|
+
color=color,
|
|
886
|
+
markersize=3,
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
axes.set_xlabel("x (km)")
|
|
890
|
+
axes.set_ylabel("y (km)")
|
|
891
|
+
axes.set_xlim([-1 * width / 1000, width / 1000])
|
|
892
|
+
axes.set_ylim([-1 * width / 1000, width / 1000])
|
|
893
|
+
axes.xaxis.set_label_position("top")
|
|
894
|
+
axes.xaxis.set_ticks_position("top")
|
|
895
|
+
axes.set_title(title, pad=35, fontsize=10, horizontalalignment="left", loc="left")
|
|
896
|
+
|
|
897
|
+
return axes
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def plot_mask_cell_track_static(
|
|
901
|
+
cell,
|
|
902
|
+
track,
|
|
903
|
+
cog,
|
|
904
|
+
features,
|
|
905
|
+
mask_total,
|
|
906
|
+
field_contour,
|
|
907
|
+
field_filled,
|
|
908
|
+
width=10000,
|
|
909
|
+
n_extend=1,
|
|
910
|
+
name="test",
|
|
911
|
+
plotdir="./",
|
|
912
|
+
file_format=["png"],
|
|
913
|
+
figsize=(10 / 2.54, 10 / 2.54),
|
|
914
|
+
dpi=300,
|
|
915
|
+
**kwargs,
|
|
916
|
+
):
|
|
917
|
+
"""Make plots for all cells with fixed frame including entire development of the cell and with one background field as filling and one background field as contrours
|
|
918
|
+
Input:
|
|
919
|
+
Output:
|
|
920
|
+
"""
|
|
921
|
+
|
|
922
|
+
warnings.warn(
|
|
923
|
+
"plot_mask_cell_track_static is depreciated and will be removed or significantly changed in v2.0.",
|
|
924
|
+
DeprecationWarning,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
mpl_backend = mpl.get_backend()
|
|
928
|
+
if mpl_backend != "agg":
|
|
929
|
+
warnings.warn(
|
|
930
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
931
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
932
|
+
)
|
|
933
|
+
import matplotlib.pyplot as plt
|
|
934
|
+
|
|
935
|
+
from iris import Constraint
|
|
936
|
+
from numpy import unique
|
|
937
|
+
import os
|
|
938
|
+
|
|
939
|
+
track_cell = track[track["cell"] == cell]
|
|
940
|
+
x_min = track_cell["projection_x_coordinate"].min() - width
|
|
941
|
+
x_max = track_cell["projection_x_coordinate"].max() + width
|
|
942
|
+
y_min = track_cell["projection_y_coordinate"].min() - width
|
|
943
|
+
y_max = track_cell["projection_y_coordinate"].max() + width
|
|
944
|
+
|
|
945
|
+
# set up looping over time based on mask's time coordinate to allow for one timestep before and after the track
|
|
946
|
+
time_coord = mask_total.coord("time")
|
|
947
|
+
time = time_coord.units.num2date(time_coord.points)
|
|
948
|
+
i_start = max(0, np.where(time == track_cell["time"].values[0])[0][0] - n_extend)
|
|
949
|
+
i_end = min(
|
|
950
|
+
len(time) - 1,
|
|
951
|
+
np.where(time == track_cell["time"].values[-1])[0][0] + n_extend + 1,
|
|
952
|
+
)
|
|
953
|
+
time_cell = time[slice(i_start, i_end)]
|
|
954
|
+
for time_i in time_cell:
|
|
955
|
+
# for i_row,row in track_cell.iterrows():
|
|
956
|
+
# time_i=row['time']
|
|
957
|
+
# constraint_time = Constraint(time=row['time'])
|
|
958
|
+
constraint_time = Constraint(time=time_i)
|
|
959
|
+
|
|
960
|
+
constraint_x = Constraint(
|
|
961
|
+
projection_x_coordinate=lambda cell: x_min < cell < x_max
|
|
962
|
+
)
|
|
963
|
+
constraint_y = Constraint(
|
|
964
|
+
projection_y_coordinate=lambda cell: y_min < cell < y_max
|
|
965
|
+
)
|
|
966
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
967
|
+
|
|
968
|
+
mask_total_i = mask_total.extract(constraint)
|
|
969
|
+
if field_contour is None:
|
|
970
|
+
field_contour_i = None
|
|
971
|
+
else:
|
|
972
|
+
field_contour_i = field_contour.extract(constraint)
|
|
973
|
+
if field_filled is None:
|
|
974
|
+
field_filled_i = None
|
|
975
|
+
else:
|
|
976
|
+
field_filled_i = field_filled.extract(constraint)
|
|
977
|
+
|
|
978
|
+
track_i = track[track["time"] == time_i]
|
|
979
|
+
|
|
980
|
+
cells_mask = list(unique(mask_total_i.core_data()))
|
|
981
|
+
track_cells = track_i.loc[
|
|
982
|
+
(track_i["projection_x_coordinate"] > x_min)
|
|
983
|
+
& (track_i["projection_x_coordinate"] < x_max)
|
|
984
|
+
& (track_i["projection_y_coordinate"] > y_min)
|
|
985
|
+
& (track_i["projection_y_coordinate"] < y_max)
|
|
986
|
+
]
|
|
987
|
+
cells_track = list(track_cells["cell"].values)
|
|
988
|
+
cells = list(set(cells_mask + cells_track))
|
|
989
|
+
if cell not in cells:
|
|
990
|
+
cells.append(cell)
|
|
991
|
+
if 0 in cells:
|
|
992
|
+
cells.remove(0)
|
|
993
|
+
track_i = track_i[track_i["cell"].isin(cells)]
|
|
994
|
+
|
|
995
|
+
if cog is None:
|
|
996
|
+
cog_i = None
|
|
997
|
+
else:
|
|
998
|
+
cog_i = cog[cog["cell"].isin(cells)]
|
|
999
|
+
cog_i = cog_i[cog_i["time"] == time_i]
|
|
1000
|
+
|
|
1001
|
+
if features is None:
|
|
1002
|
+
features_i = None
|
|
1003
|
+
else:
|
|
1004
|
+
features_i = features[features["time"] == time_i]
|
|
1005
|
+
|
|
1006
|
+
fig1, ax1 = plt.subplots(ncols=1, nrows=1, figsize=figsize)
|
|
1007
|
+
fig1.subplots_adjust(left=0.2, bottom=0.15, right=0.80, top=0.85)
|
|
1008
|
+
|
|
1009
|
+
datestring_stamp = time_i.strftime("%Y-%m-%d %H:%M:%S")
|
|
1010
|
+
if time_i in track_cell["time"].values:
|
|
1011
|
+
time_cell_i = track_cell[track_cell["time"].values == time_i]["time_cell"]
|
|
1012
|
+
celltime_stamp = "%02d:%02d:%02d" % (
|
|
1013
|
+
time_cell_i.dt.total_seconds() // 3600,
|
|
1014
|
+
(time_cell_i.dt.total_seconds() % 3600) // 60,
|
|
1015
|
+
time_cell_i.dt.total_seconds() % 60,
|
|
1016
|
+
)
|
|
1017
|
+
else:
|
|
1018
|
+
celltime_stamp = " - "
|
|
1019
|
+
title = datestring_stamp + " , " + celltime_stamp
|
|
1020
|
+
datestring_file = time_i.strftime("%Y-%m-%d_%H%M%S")
|
|
1021
|
+
|
|
1022
|
+
ax1 = plot_mask_cell_individual_static(
|
|
1023
|
+
cell_i=cell,
|
|
1024
|
+
track=track_i,
|
|
1025
|
+
cog=cog_i,
|
|
1026
|
+
features=features_i,
|
|
1027
|
+
mask_total=mask_total_i,
|
|
1028
|
+
field_contour=field_contour_i,
|
|
1029
|
+
field_filled=field_filled_i,
|
|
1030
|
+
xlim=[x_min / 1000, x_max / 1000],
|
|
1031
|
+
ylim=[y_min / 1000, y_max / 1000],
|
|
1032
|
+
axes=ax1,
|
|
1033
|
+
title=title,
|
|
1034
|
+
**kwargs,
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
out_dir = os.path.join(plotdir, name)
|
|
1038
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
1039
|
+
if "png" in file_format:
|
|
1040
|
+
savepath_png = os.path.join(out_dir, name + "_" + datestring_file + ".png")
|
|
1041
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
1042
|
+
logging.debug("Mask static plot saved to " + savepath_png)
|
|
1043
|
+
if "pdf" in file_format:
|
|
1044
|
+
savepath_pdf = os.path.join(out_dir, name + "_" + datestring_file + ".pdf")
|
|
1045
|
+
fig1.savefig(savepath_pdf, dpi=dpi)
|
|
1046
|
+
logging.debug("Mask static plot saved to " + savepath_pdf)
|
|
1047
|
+
plt.close()
|
|
1048
|
+
plt.clf()
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def plot_mask_cell_individual_static(
|
|
1052
|
+
cell_i,
|
|
1053
|
+
track,
|
|
1054
|
+
cog,
|
|
1055
|
+
features,
|
|
1056
|
+
mask_total,
|
|
1057
|
+
field_contour,
|
|
1058
|
+
field_filled,
|
|
1059
|
+
axes=None,
|
|
1060
|
+
xlim=None,
|
|
1061
|
+
ylim=None,
|
|
1062
|
+
label_field_contour=None,
|
|
1063
|
+
cmap_field_contour="Blues",
|
|
1064
|
+
norm_field_contour=None,
|
|
1065
|
+
linewidths_contour=0.8,
|
|
1066
|
+
contour_labels=False,
|
|
1067
|
+
vmin_field_contour=0,
|
|
1068
|
+
vmax_field_contour=50,
|
|
1069
|
+
levels_field_contour=None,
|
|
1070
|
+
nlevels_field_contour=10,
|
|
1071
|
+
label_field_filled=None,
|
|
1072
|
+
cmap_field_filled="summer",
|
|
1073
|
+
norm_field_filled=None,
|
|
1074
|
+
vmin_field_filled=0,
|
|
1075
|
+
vmax_field_filled=100,
|
|
1076
|
+
levels_field_filled=None,
|
|
1077
|
+
nlevels_field_filled=10,
|
|
1078
|
+
title=None,
|
|
1079
|
+
feature_number=False,
|
|
1080
|
+
):
|
|
1081
|
+
"""Make plots for cell in fixed frame and with one background field as filling and one background field as contrours
|
|
1082
|
+
Input:
|
|
1083
|
+
Output:
|
|
1084
|
+
"""
|
|
1085
|
+
import matplotlib.pyplot as plt
|
|
1086
|
+
|
|
1087
|
+
import numpy as np
|
|
1088
|
+
from .utils import mask_features, mask_features_surface
|
|
1089
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
1090
|
+
from matplotlib.colors import Normalize
|
|
1091
|
+
|
|
1092
|
+
warnings.warn(
|
|
1093
|
+
"plot_mask_cell_individual_static is depreciated and will be removed or significantly changed in v2.0.",
|
|
1094
|
+
DeprecationWarning,
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
divider = make_axes_locatable(axes)
|
|
1098
|
+
|
|
1099
|
+
if field_filled is not None:
|
|
1100
|
+
if levels_field_filled is None:
|
|
1101
|
+
levels_field_filled = np.linspace(vmin_field_filled, vmax_field_filled, 10)
|
|
1102
|
+
plot_field_filled = axes.contourf(
|
|
1103
|
+
field_filled.coord("projection_x_coordinate").points / 1000,
|
|
1104
|
+
field_filled.coord("projection_y_coordinate").points / 1000,
|
|
1105
|
+
field_filled.data,
|
|
1106
|
+
levels=levels_field_filled,
|
|
1107
|
+
norm=norm_field_filled,
|
|
1108
|
+
cmap=cmap_field_filled,
|
|
1109
|
+
vmin=vmin_field_filled,
|
|
1110
|
+
vmax=vmax_field_filled,
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
cax_filled = divider.append_axes("right", size="5%", pad=0.1)
|
|
1114
|
+
|
|
1115
|
+
norm_filled = Normalize(vmin=vmin_field_filled, vmax=vmax_field_filled)
|
|
1116
|
+
sm1 = plt.cm.ScalarMappable(norm=norm_filled, cmap=plot_field_filled.cmap)
|
|
1117
|
+
sm1.set_array([])
|
|
1118
|
+
|
|
1119
|
+
cbar_field_filled = plt.colorbar(sm1, orientation="vertical", cax=cax_filled)
|
|
1120
|
+
cbar_field_filled.ax.set_ylabel(label_field_filled)
|
|
1121
|
+
cbar_field_filled.set_clim(vmin_field_filled, vmax_field_filled)
|
|
1122
|
+
|
|
1123
|
+
if field_contour is not None:
|
|
1124
|
+
if levels_field_contour is None:
|
|
1125
|
+
levels_field_contour = np.linspace(
|
|
1126
|
+
vmin_field_contour, vmax_field_contour, 5
|
|
1127
|
+
)
|
|
1128
|
+
plot_field_contour = axes.contour(
|
|
1129
|
+
field_contour.coord("projection_x_coordinate").points / 1000,
|
|
1130
|
+
field_contour.coord("projection_y_coordinate").points / 1000,
|
|
1131
|
+
field_contour.data,
|
|
1132
|
+
cmap=cmap_field_contour,
|
|
1133
|
+
norm=norm_field_contour,
|
|
1134
|
+
levels=levels_field_contour,
|
|
1135
|
+
vmin=vmin_field_contour,
|
|
1136
|
+
vmax=vmax_field_contour,
|
|
1137
|
+
linewidths=linewidths_contour,
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
if contour_labels:
|
|
1141
|
+
axes.clabel(plot_field_contour, fontsize=10)
|
|
1142
|
+
|
|
1143
|
+
cax_contour = divider.append_axes("bottom", size="5%", pad=0.1)
|
|
1144
|
+
if norm_field_contour:
|
|
1145
|
+
vmin_field_contour = None
|
|
1146
|
+
vmax_field_contour = None
|
|
1147
|
+
norm_contour = norm_field_contour
|
|
1148
|
+
else:
|
|
1149
|
+
norm_contour = Normalize(vmin=vmin_field_contour, vmax=vmax_field_contour)
|
|
1150
|
+
|
|
1151
|
+
sm_contour = plt.cm.ScalarMappable(
|
|
1152
|
+
norm=norm_contour, cmap=plot_field_contour.cmap
|
|
1153
|
+
)
|
|
1154
|
+
sm_contour.set_array([])
|
|
1155
|
+
|
|
1156
|
+
cbar_field_contour = plt.colorbar(
|
|
1157
|
+
sm_contour,
|
|
1158
|
+
orientation="horizontal",
|
|
1159
|
+
ticks=levels_field_contour,
|
|
1160
|
+
cax=cax_contour,
|
|
1161
|
+
)
|
|
1162
|
+
cbar_field_contour.ax.set_xlabel(label_field_contour)
|
|
1163
|
+
cbar_field_contour.set_clim(vmin_field_contour, vmax_field_contour)
|
|
1164
|
+
|
|
1165
|
+
for i_row, row in track.iterrows():
|
|
1166
|
+
cell = row["cell"]
|
|
1167
|
+
feature = row["feature"]
|
|
1168
|
+
# logging.debug("cell: "+ str(row['cell']))
|
|
1169
|
+
# logging.debug("feature: "+ str(row['feature']))
|
|
1170
|
+
|
|
1171
|
+
if cell == cell_i:
|
|
1172
|
+
color = "darkred"
|
|
1173
|
+
if feature_number:
|
|
1174
|
+
cell_string = " " + str(int(cell)) + " (" + str(int(feature)) + ")"
|
|
1175
|
+
else:
|
|
1176
|
+
cell_string = " " + str(int(cell))
|
|
1177
|
+
elif np.isnan(cell):
|
|
1178
|
+
color = "gray"
|
|
1179
|
+
if feature_number:
|
|
1180
|
+
cell_string = " " + "(" + str(int(feature)) + ")"
|
|
1181
|
+
else:
|
|
1182
|
+
cell_string = " "
|
|
1183
|
+
else:
|
|
1184
|
+
color = "darkorange"
|
|
1185
|
+
if feature_number:
|
|
1186
|
+
cell_string = " " + str(int(cell)) + " (" + str(int(feature)) + ")"
|
|
1187
|
+
else:
|
|
1188
|
+
cell_string = " " + str(int(cell))
|
|
1189
|
+
|
|
1190
|
+
axes.text(
|
|
1191
|
+
row["projection_x_coordinate"] / 1000,
|
|
1192
|
+
row["projection_y_coordinate"] / 1000,
|
|
1193
|
+
cell_string,
|
|
1194
|
+
color=color,
|
|
1195
|
+
fontsize=6,
|
|
1196
|
+
clip_on=True,
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
# Plot marker for tracked cell centre as a cross
|
|
1200
|
+
axes.plot(
|
|
1201
|
+
row["projection_x_coordinate"] / 1000,
|
|
1202
|
+
row["projection_y_coordinate"] / 1000,
|
|
1203
|
+
"x",
|
|
1204
|
+
color=color,
|
|
1205
|
+
markersize=4,
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
# Create surface projection of mask for the respective cell and plot it in the right color
|
|
1209
|
+
z_coord = "model_level_number"
|
|
1210
|
+
if len(mask_total.shape) == 3:
|
|
1211
|
+
mask_total_i_surface = mask_features_surface(
|
|
1212
|
+
mask_total, feature, masked=False, z_coord=z_coord
|
|
1213
|
+
)
|
|
1214
|
+
elif len(mask_total.shape) == 2:
|
|
1215
|
+
mask_total_i_surface = mask_features(
|
|
1216
|
+
mask_total, feature, masked=False, z_coord=z_coord
|
|
1217
|
+
)
|
|
1218
|
+
axes.contour(
|
|
1219
|
+
mask_total_i_surface.coord("projection_x_coordinate").points / 1000,
|
|
1220
|
+
mask_total_i_surface.coord("projection_y_coordinate").points / 1000,
|
|
1221
|
+
mask_total_i_surface.data,
|
|
1222
|
+
levels=[0, feature],
|
|
1223
|
+
colors=color,
|
|
1224
|
+
linestyles=":",
|
|
1225
|
+
linewidth=1,
|
|
1226
|
+
)
|
|
1227
|
+
if cog is not None:
|
|
1228
|
+
for i_row, row in cog.iterrows():
|
|
1229
|
+
cell = row["cell"]
|
|
1230
|
+
|
|
1231
|
+
if cell == cell_i:
|
|
1232
|
+
color = "darkred"
|
|
1233
|
+
else:
|
|
1234
|
+
color = "darkorange"
|
|
1235
|
+
# plot marker for centre of gravity as a circle
|
|
1236
|
+
axes.plot(
|
|
1237
|
+
row["x_M"] / 1000,
|
|
1238
|
+
row["y_M"] / 1000,
|
|
1239
|
+
"o",
|
|
1240
|
+
markeredgecolor=color,
|
|
1241
|
+
markerfacecolor="None",
|
|
1242
|
+
markersize=4,
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
if features is not None:
|
|
1246
|
+
for i_row, row in features.iterrows():
|
|
1247
|
+
color = "purple"
|
|
1248
|
+
axes.plot(
|
|
1249
|
+
row["projection_x_coordinate"] / 1000,
|
|
1250
|
+
row["projection_y_coordinate"] / 1000,
|
|
1251
|
+
"+",
|
|
1252
|
+
color=color,
|
|
1253
|
+
markersize=3,
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
axes.set_xlabel("x (km)")
|
|
1257
|
+
axes.set_ylabel("y (km)")
|
|
1258
|
+
axes.set_xlim(xlim)
|
|
1259
|
+
axes.set_ylim(ylim)
|
|
1260
|
+
axes.xaxis.set_label_position("top")
|
|
1261
|
+
axes.xaxis.set_ticks_position("top")
|
|
1262
|
+
axes.set_title(title, pad=35, fontsize=10, horizontalalignment="left", loc="left")
|
|
1263
|
+
|
|
1264
|
+
return axes
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def plot_mask_cell_track_2D3Dstatic(
|
|
1268
|
+
cell,
|
|
1269
|
+
track,
|
|
1270
|
+
cog,
|
|
1271
|
+
features,
|
|
1272
|
+
mask_total,
|
|
1273
|
+
field_contour,
|
|
1274
|
+
field_filled,
|
|
1275
|
+
width=10000,
|
|
1276
|
+
n_extend=1,
|
|
1277
|
+
name="test",
|
|
1278
|
+
plotdir="./",
|
|
1279
|
+
file_format=["png"],
|
|
1280
|
+
figsize=(10 / 2.54, 10 / 2.54),
|
|
1281
|
+
dpi=300,
|
|
1282
|
+
ele=10,
|
|
1283
|
+
azim=30,
|
|
1284
|
+
**kwargs,
|
|
1285
|
+
):
|
|
1286
|
+
"""Make plots for all cells with fixed frame including entire development of the cell and with one background field as filling and one background field as contrours
|
|
1287
|
+
Input:
|
|
1288
|
+
Output:
|
|
1289
|
+
"""
|
|
1290
|
+
warnings.warn(
|
|
1291
|
+
"plot_mask_cell_track_2D3Dstatic is depreciated and will be removed or significantly changed in v2.0.",
|
|
1292
|
+
DeprecationWarning,
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
mpl_backend = mpl.get_backend()
|
|
1296
|
+
if mpl_backend != "agg":
|
|
1297
|
+
warnings.warn(
|
|
1298
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
1299
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
1300
|
+
)
|
|
1301
|
+
import matplotlib.pyplot as plt
|
|
1302
|
+
|
|
1303
|
+
from iris import Constraint
|
|
1304
|
+
from numpy import unique
|
|
1305
|
+
import os
|
|
1306
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
1307
|
+
import matplotlib.gridspec as gridspec
|
|
1308
|
+
|
|
1309
|
+
track_cell = track[track["cell"] == cell]
|
|
1310
|
+
x_min = track_cell["projection_x_coordinate"].min() - width
|
|
1311
|
+
x_max = track_cell["projection_x_coordinate"].max() + width
|
|
1312
|
+
y_min = track_cell["projection_y_coordinate"].min() - width
|
|
1313
|
+
y_max = track_cell["projection_y_coordinate"].max() + width
|
|
1314
|
+
|
|
1315
|
+
# set up looping over time based on mask's time coordinate to allow for one timestep before and after the track
|
|
1316
|
+
time_coord = mask_total.coord("time")
|
|
1317
|
+
time = time_coord.units.num2date(time_coord.points)
|
|
1318
|
+
i_start = max(0, np.where(time == track_cell["time"].values[0])[0][0] - n_extend)
|
|
1319
|
+
i_end = min(
|
|
1320
|
+
len(time) - 1,
|
|
1321
|
+
np.where(time == track_cell["time"].values[-1])[0][0] + n_extend + 1,
|
|
1322
|
+
)
|
|
1323
|
+
time_cell = time[slice(i_start, i_end)]
|
|
1324
|
+
for time_i in time_cell:
|
|
1325
|
+
# for i_row,row in track_cell.iterrows():
|
|
1326
|
+
# time_i=row['time']
|
|
1327
|
+
# constraint_time = Constraint(time=row['time'])
|
|
1328
|
+
constraint_time = Constraint(time=time_i)
|
|
1329
|
+
|
|
1330
|
+
constraint_x = Constraint(
|
|
1331
|
+
projection_x_coordinate=lambda cell: x_min < cell < x_max
|
|
1332
|
+
)
|
|
1333
|
+
constraint_y = Constraint(
|
|
1334
|
+
projection_y_coordinate=lambda cell: y_min < cell < y_max
|
|
1335
|
+
)
|
|
1336
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
1337
|
+
|
|
1338
|
+
mask_total_i = mask_total.extract(constraint)
|
|
1339
|
+
if field_contour is None:
|
|
1340
|
+
field_contour_i = None
|
|
1341
|
+
else:
|
|
1342
|
+
field_contour_i = field_contour.extract(constraint)
|
|
1343
|
+
if field_filled is None:
|
|
1344
|
+
field_filled_i = None
|
|
1345
|
+
else:
|
|
1346
|
+
field_filled_i = field_filled.extract(constraint)
|
|
1347
|
+
|
|
1348
|
+
track_i = track[track["time"] == time_i]
|
|
1349
|
+
|
|
1350
|
+
cells_mask = list(unique(mask_total_i.core_data()))
|
|
1351
|
+
track_cells = track_i.loc[
|
|
1352
|
+
(track_i["projection_x_coordinate"] > x_min)
|
|
1353
|
+
& (track_i["projection_x_coordinate"] < x_max)
|
|
1354
|
+
& (track_i["projection_y_coordinate"] > y_min)
|
|
1355
|
+
& (track_i["projection_y_coordinate"] < y_max)
|
|
1356
|
+
]
|
|
1357
|
+
cells_track = list(track_cells["cell"].values)
|
|
1358
|
+
cells = list(set(cells_mask + cells_track))
|
|
1359
|
+
if cell not in cells:
|
|
1360
|
+
cells.append(cell)
|
|
1361
|
+
if 0 in cells:
|
|
1362
|
+
cells.remove(0)
|
|
1363
|
+
track_i = track_i[track_i["cell"].isin(cells)]
|
|
1364
|
+
|
|
1365
|
+
if cog is None:
|
|
1366
|
+
cog_i = None
|
|
1367
|
+
else:
|
|
1368
|
+
cog_i = cog[cog["cell"].isin(cells)]
|
|
1369
|
+
cog_i = cog_i[cog_i["time"] == time_i]
|
|
1370
|
+
|
|
1371
|
+
if features is None:
|
|
1372
|
+
features_i = None
|
|
1373
|
+
else:
|
|
1374
|
+
features_i = features[features["time"] == time_i]
|
|
1375
|
+
|
|
1376
|
+
fig1 = plt.figure(figsize=(20 / 2.54, 10 / 2.54))
|
|
1377
|
+
fig1.subplots_adjust(
|
|
1378
|
+
left=0.1, bottom=0.15, right=0.9, top=0.9, wspace=0.3, hspace=0.25
|
|
1379
|
+
)
|
|
1380
|
+
|
|
1381
|
+
# make two subplots for figure:
|
|
1382
|
+
gs1 = gridspec.GridSpec(1, 2, width_ratios=[1, 1.2])
|
|
1383
|
+
fig1.add_subplot(gs1[0])
|
|
1384
|
+
fig1.add_subplot(gs1[1], projection="3d")
|
|
1385
|
+
|
|
1386
|
+
ax1 = fig1.get_axes()
|
|
1387
|
+
|
|
1388
|
+
datestring_stamp = time_i.strftime("%Y-%m-%d %H:%M:%S")
|
|
1389
|
+
if time_i in track_cell["time"].values:
|
|
1390
|
+
time_cell_i = track_cell[track_cell["time"].values == time_i]["time_cell"]
|
|
1391
|
+
celltime_stamp = "%02d:%02d:%02d" % (
|
|
1392
|
+
time_cell_i.dt.total_seconds() // 3600,
|
|
1393
|
+
(time_cell_i.dt.total_seconds() % 3600) // 60,
|
|
1394
|
+
time_cell_i.dt.total_seconds() % 60,
|
|
1395
|
+
)
|
|
1396
|
+
else:
|
|
1397
|
+
celltime_stamp = " - "
|
|
1398
|
+
title = datestring_stamp + " , " + celltime_stamp
|
|
1399
|
+
datestring_file = time_i.strftime("%Y-%m-%d_%H%M%S")
|
|
1400
|
+
|
|
1401
|
+
ax1[0] = plot_mask_cell_individual_static(
|
|
1402
|
+
cell_i=cell,
|
|
1403
|
+
track=track_i,
|
|
1404
|
+
cog=cog_i,
|
|
1405
|
+
features=features_i,
|
|
1406
|
+
mask_total=mask_total_i,
|
|
1407
|
+
field_contour=field_contour_i,
|
|
1408
|
+
field_filled=field_filled_i,
|
|
1409
|
+
xlim=[x_min / 1000, x_max / 1000],
|
|
1410
|
+
ylim=[y_min / 1000, y_max / 1000],
|
|
1411
|
+
axes=ax1[0],
|
|
1412
|
+
title=title,
|
|
1413
|
+
**kwargs,
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
ax1[1] = plot_mask_cell_individual_3Dstatic(
|
|
1417
|
+
cell_i=cell,
|
|
1418
|
+
track=track_i,
|
|
1419
|
+
cog=cog_i,
|
|
1420
|
+
features=features_i,
|
|
1421
|
+
mask_total=mask_total_i,
|
|
1422
|
+
field_contour=field_contour_i,
|
|
1423
|
+
field_filled=field_filled_i,
|
|
1424
|
+
xlim=[x_min / 1000, x_max / 1000],
|
|
1425
|
+
ylim=[y_min / 1000, y_max / 1000],
|
|
1426
|
+
axes=ax1[1],
|
|
1427
|
+
title=title,
|
|
1428
|
+
ele=ele,
|
|
1429
|
+
azim=azim,
|
|
1430
|
+
**kwargs,
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
out_dir = os.path.join(plotdir, name)
|
|
1434
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
1435
|
+
if "png" in file_format:
|
|
1436
|
+
savepath_png = os.path.join(out_dir, name + "_" + datestring_file + ".png")
|
|
1437
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
1438
|
+
logging.debug("Mask static 2d/3D plot saved to " + savepath_png)
|
|
1439
|
+
if "pdf" in file_format:
|
|
1440
|
+
savepath_pdf = os.path.join(out_dir, name + "_" + datestring_file + ".pdf")
|
|
1441
|
+
fig1.savefig(savepath_pdf, dpi=dpi)
|
|
1442
|
+
logging.debug("Mask static 2d/3D plot saved to " + savepath_pdf)
|
|
1443
|
+
plt.close()
|
|
1444
|
+
plt.clf()
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def plot_mask_cell_track_3Dstatic(
|
|
1448
|
+
cell,
|
|
1449
|
+
track,
|
|
1450
|
+
cog,
|
|
1451
|
+
features,
|
|
1452
|
+
mask_total,
|
|
1453
|
+
field_contour,
|
|
1454
|
+
field_filled,
|
|
1455
|
+
width=10000,
|
|
1456
|
+
n_extend=1,
|
|
1457
|
+
name="test",
|
|
1458
|
+
plotdir="./",
|
|
1459
|
+
file_format=["png"],
|
|
1460
|
+
figsize=(10 / 2.54, 10 / 2.54),
|
|
1461
|
+
dpi=300,
|
|
1462
|
+
**kwargs,
|
|
1463
|
+
):
|
|
1464
|
+
"""Make plots for all cells with fixed frame including entire development of the cell and with one background field as filling and one background field as contrours
|
|
1465
|
+
Input:
|
|
1466
|
+
Output:
|
|
1467
|
+
"""
|
|
1468
|
+
warnings.warn(
|
|
1469
|
+
"plot_mask_cell_track_3Dstatic is depreciated and will be removed or significantly changed in v2.0.",
|
|
1470
|
+
DeprecationWarning,
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1473
|
+
mpl_backend = mpl.get_backend()
|
|
1474
|
+
if mpl_backend != "agg":
|
|
1475
|
+
warnings.warn(
|
|
1476
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
1477
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
1478
|
+
)
|
|
1479
|
+
import matplotlib.pyplot as plt
|
|
1480
|
+
|
|
1481
|
+
from iris import Constraint
|
|
1482
|
+
from numpy import unique
|
|
1483
|
+
import os
|
|
1484
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
1485
|
+
|
|
1486
|
+
track_cell = track[track["cell"] == cell]
|
|
1487
|
+
x_min = track_cell["projection_x_coordinate"].min() - width
|
|
1488
|
+
x_max = track_cell["projection_x_coordinate"].max() + width
|
|
1489
|
+
y_min = track_cell["projection_y_coordinate"].min() - width
|
|
1490
|
+
y_max = track_cell["projection_y_coordinate"].max() + width
|
|
1491
|
+
|
|
1492
|
+
# set up looping over time based on mask's time coordinate to allow for one timestep before and after the track
|
|
1493
|
+
time_coord = mask_total.coord("time")
|
|
1494
|
+
time = time_coord.units.num2date(time_coord.points)
|
|
1495
|
+
i_start = max(0, np.where(time == track_cell["time"].values[0])[0][0] - n_extend)
|
|
1496
|
+
i_end = min(
|
|
1497
|
+
len(time) - 1,
|
|
1498
|
+
np.where(time == track_cell["time"].values[-1])[0][0] + n_extend + 1,
|
|
1499
|
+
)
|
|
1500
|
+
time_cell = time[slice(i_start, i_end)]
|
|
1501
|
+
for time_i in time_cell:
|
|
1502
|
+
# for i_row,row in track_cell.iterrows():
|
|
1503
|
+
# time_i=row['time']
|
|
1504
|
+
# constraint_time = Constraint(time=row['time'])
|
|
1505
|
+
constraint_time = Constraint(time=time_i)
|
|
1506
|
+
|
|
1507
|
+
constraint_x = Constraint(
|
|
1508
|
+
projection_x_coordinate=lambda cell: x_min < cell < x_max
|
|
1509
|
+
)
|
|
1510
|
+
constraint_y = Constraint(
|
|
1511
|
+
projection_y_coordinate=lambda cell: y_min < cell < y_max
|
|
1512
|
+
)
|
|
1513
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
1514
|
+
|
|
1515
|
+
mask_total_i = mask_total.extract(constraint)
|
|
1516
|
+
if field_contour is None:
|
|
1517
|
+
field_contour_i = None
|
|
1518
|
+
else:
|
|
1519
|
+
field_contour_i = field_contour.extract(constraint)
|
|
1520
|
+
if field_filled is None:
|
|
1521
|
+
field_filled_i = None
|
|
1522
|
+
else:
|
|
1523
|
+
field_filled_i = field_filled.extract(constraint)
|
|
1524
|
+
|
|
1525
|
+
track_i = track[track["time"] == time_i]
|
|
1526
|
+
|
|
1527
|
+
cells_mask = list(unique(mask_total_i.core_data()))
|
|
1528
|
+
track_cells = track_i.loc[
|
|
1529
|
+
(track_i["projection_x_coordinate"] > x_min)
|
|
1530
|
+
& (track_i["projection_x_coordinate"] < x_max)
|
|
1531
|
+
& (track_i["projection_y_coordinate"] > y_min)
|
|
1532
|
+
& (track_i["projection_y_coordinate"] < y_max)
|
|
1533
|
+
]
|
|
1534
|
+
cells_track = list(track_cells["cell"].values)
|
|
1535
|
+
cells = list(set(cells_mask + cells_track))
|
|
1536
|
+
if cell not in cells:
|
|
1537
|
+
cells.append(cell)
|
|
1538
|
+
if 0 in cells:
|
|
1539
|
+
cells.remove(0)
|
|
1540
|
+
track_i = track_i[track_i["cell"].isin(cells)]
|
|
1541
|
+
|
|
1542
|
+
if cog is None:
|
|
1543
|
+
cog_i = None
|
|
1544
|
+
else:
|
|
1545
|
+
cog_i = cog[cog["cell"].isin(cells)]
|
|
1546
|
+
cog_i = cog_i[cog_i["time"] == time_i]
|
|
1547
|
+
|
|
1548
|
+
if features is None:
|
|
1549
|
+
features_i = None
|
|
1550
|
+
else:
|
|
1551
|
+
features_i = features[features["time"] == time_i]
|
|
1552
|
+
|
|
1553
|
+
# fig1, ax1 = plt.subplots(ncols=1, nrows=1, figsize=figsize)
|
|
1554
|
+
# fig1.subplots_adjust(left=0.2, bottom=0.15, right=0.80, top=0.85)
|
|
1555
|
+
fig1, ax1 = plt.subplots(
|
|
1556
|
+
ncols=1,
|
|
1557
|
+
nrows=1,
|
|
1558
|
+
figsize=(10 / 2.54, 10 / 2.54),
|
|
1559
|
+
subplot_kw={"projection": "3d"},
|
|
1560
|
+
)
|
|
1561
|
+
|
|
1562
|
+
datestring_stamp = time_i.strftime("%Y-%m-%d %H:%M:%S")
|
|
1563
|
+
if time_i in track_cell["time"].values:
|
|
1564
|
+
time_cell_i = track_cell[track_cell["time"].values == time_i]["time_cell"]
|
|
1565
|
+
celltime_stamp = "%02d:%02d:%02d" % (
|
|
1566
|
+
time_cell_i.dt.total_seconds() // 3600,
|
|
1567
|
+
(time_cell_i.dt.total_seconds() % 3600) // 60,
|
|
1568
|
+
time_cell_i.dt.total_seconds() % 60,
|
|
1569
|
+
)
|
|
1570
|
+
else:
|
|
1571
|
+
celltime_stamp = " - "
|
|
1572
|
+
title = datestring_stamp + " , " + celltime_stamp
|
|
1573
|
+
datestring_file = time_i.strftime("%Y-%m-%d_%H%M%S")
|
|
1574
|
+
|
|
1575
|
+
ax1 = plot_mask_cell_individual_3Dstatic(
|
|
1576
|
+
cell_i=cell,
|
|
1577
|
+
track=track_i,
|
|
1578
|
+
cog=cog_i,
|
|
1579
|
+
features=features_i,
|
|
1580
|
+
mask_total=mask_total_i,
|
|
1581
|
+
field_contour=field_contour_i,
|
|
1582
|
+
field_filled=field_filled_i,
|
|
1583
|
+
xlim=[x_min / 1000, x_max / 1000],
|
|
1584
|
+
ylim=[y_min / 1000, y_max / 1000],
|
|
1585
|
+
axes=ax1,
|
|
1586
|
+
title=title,
|
|
1587
|
+
**kwargs,
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
out_dir = os.path.join(plotdir, name)
|
|
1591
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
1592
|
+
if "png" in file_format:
|
|
1593
|
+
savepath_png = os.path.join(out_dir, name + "_" + datestring_file + ".png")
|
|
1594
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
1595
|
+
logging.debug("Mask static plot saved to " + savepath_png)
|
|
1596
|
+
if "pdf" in file_format:
|
|
1597
|
+
savepath_pdf = os.path.join(out_dir, name + "_" + datestring_file + ".pdf")
|
|
1598
|
+
fig1.savefig(savepath_pdf, dpi=dpi)
|
|
1599
|
+
logging.debug("Mask static plot saved to " + savepath_pdf)
|
|
1600
|
+
plt.close()
|
|
1601
|
+
plt.clf()
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
def plot_mask_cell_individual_3Dstatic(
|
|
1605
|
+
cell_i,
|
|
1606
|
+
track,
|
|
1607
|
+
cog,
|
|
1608
|
+
features,
|
|
1609
|
+
mask_total,
|
|
1610
|
+
field_contour,
|
|
1611
|
+
field_filled,
|
|
1612
|
+
axes=None,
|
|
1613
|
+
xlim=None,
|
|
1614
|
+
ylim=None,
|
|
1615
|
+
label_field_contour=None,
|
|
1616
|
+
cmap_field_contour="Blues",
|
|
1617
|
+
norm_field_contour=None,
|
|
1618
|
+
linewidths_contour=0.8,
|
|
1619
|
+
contour_labels=False,
|
|
1620
|
+
vmin_field_contour=0,
|
|
1621
|
+
vmax_field_contour=50,
|
|
1622
|
+
levels_field_contour=None,
|
|
1623
|
+
nlevels_field_contour=10,
|
|
1624
|
+
label_field_filled=None,
|
|
1625
|
+
cmap_field_filled="summer",
|
|
1626
|
+
norm_field_filled=None,
|
|
1627
|
+
vmin_field_filled=0,
|
|
1628
|
+
vmax_field_filled=100,
|
|
1629
|
+
levels_field_filled=None,
|
|
1630
|
+
nlevels_field_filled=10,
|
|
1631
|
+
title=None,
|
|
1632
|
+
feature_number=False,
|
|
1633
|
+
ele=10.0,
|
|
1634
|
+
azim=210.0,
|
|
1635
|
+
):
|
|
1636
|
+
"""Make plots for cell in fixed frame and with one background field as filling and one background field as contrours
|
|
1637
|
+
Input:
|
|
1638
|
+
Output:
|
|
1639
|
+
"""
|
|
1640
|
+
import matplotlib.pyplot as plt
|
|
1641
|
+
|
|
1642
|
+
import numpy as np
|
|
1643
|
+
from .utils import mask_features, mask_features_surface
|
|
1644
|
+
|
|
1645
|
+
# from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
1646
|
+
# from matplotlib.colors import Normalize
|
|
1647
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
1648
|
+
|
|
1649
|
+
warnings.warn(
|
|
1650
|
+
"plot_mask_cell_individual_3Dstatic is depreciated and will be removed or significantly changed in v2.0.",
|
|
1651
|
+
DeprecationWarning,
|
|
1652
|
+
)
|
|
1653
|
+
|
|
1654
|
+
axes.view_init(elev=ele, azim=azim)
|
|
1655
|
+
axes.grid(b=False)
|
|
1656
|
+
axes.set_frame_on(False)
|
|
1657
|
+
|
|
1658
|
+
# make the panes transparent
|
|
1659
|
+
axes.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
|
1660
|
+
axes.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
|
1661
|
+
axes.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
|
1662
|
+
# make the grid lines transparent
|
|
1663
|
+
axes.xaxis._axinfo["grid"]["color"] = (1, 1, 1, 0)
|
|
1664
|
+
axes.yaxis._axinfo["grid"]["color"] = (1, 1, 1, 0)
|
|
1665
|
+
axes.zaxis._axinfo["grid"]["color"] = (1, 1, 1, 0)
|
|
1666
|
+
|
|
1667
|
+
if title is not None:
|
|
1668
|
+
axes.set_title(title, horizontalalignment="left", loc="left")
|
|
1669
|
+
|
|
1670
|
+
# colors_mask = ['pink','darkred', 'orange', 'darkred', 'red', 'darkorange']
|
|
1671
|
+
x = mask_total.coord("projection_x_coordinate").points
|
|
1672
|
+
y = mask_total.coord("projection_y_coordinate").points
|
|
1673
|
+
z = mask_total.coord("model_level_number").points
|
|
1674
|
+
|
|
1675
|
+
# z = mask_total.coord('geopotential_height').points
|
|
1676
|
+
zz, yy, xx = np.meshgrid(z, y, x, indexing="ij")
|
|
1677
|
+
# z_alt = mask_total.coord('geopotential_height').points
|
|
1678
|
+
|
|
1679
|
+
# divider = make_axes_locatable(axes)
|
|
1680
|
+
|
|
1681
|
+
# if field_filled is not None:
|
|
1682
|
+
# if levels_field_filled is None:
|
|
1683
|
+
# levels_field_filled=np.linspace(vmin_field_filled,vmax_field_filled, 10)
|
|
1684
|
+
# plot_field_filled = axes.contourf(field_filled.coord('projection_x_coordinate').points/1000,
|
|
1685
|
+
# field_filled.coord('projection_y_coordinate').points/1000,
|
|
1686
|
+
# field_filled.data,
|
|
1687
|
+
# levels=levels_field_filled, norm=norm_field_filled,
|
|
1688
|
+
# cmap=cmap_field_filled, vmin=vmin_field_filled, vmax=vmax_field_filled)
|
|
1689
|
+
|
|
1690
|
+
# cax_filled = divider.append_axes("right", size="5%", pad=0.1)
|
|
1691
|
+
|
|
1692
|
+
# norm_filled= Normalize(vmin=vmin_field_filled, vmax=vmax_field_filled)
|
|
1693
|
+
# sm1= plt.cm.ScalarMappable(norm=norm_filled, cmap = plot_field_filled.cmap)
|
|
1694
|
+
# sm1.set_array([])
|
|
1695
|
+
|
|
1696
|
+
# cbar_field_filled = plt.colorbar(sm1, orientation='vertical',cax=cax_filled)
|
|
1697
|
+
# cbar_field_filled.ax.set_ylabel(label_field_filled)
|
|
1698
|
+
# cbar_field_filled.set_clim(vmin_field_filled, vmax_field_filled)
|
|
1699
|
+
|
|
1700
|
+
# if field_contour is not None:
|
|
1701
|
+
# if levels_field_contour is None:
|
|
1702
|
+
# levels_field_contour=np.linspace(vmin_field_contour, vmax_field_contour, 5)
|
|
1703
|
+
# plot_field_contour = axes.contour(field_contour.coord('projection_x_coordinate').points/1000,
|
|
1704
|
+
# field_contour.coord('projection_y_coordinate').points/1000,
|
|
1705
|
+
# field_contour.data,
|
|
1706
|
+
# cmap=cmap_field_contour,norm=norm_field_contour,
|
|
1707
|
+
# levels=levels_field_contour,vmin=vmin_field_contour, vmax=vmax_field_contour,
|
|
1708
|
+
# linewidths=linewidths_contour)
|
|
1709
|
+
|
|
1710
|
+
# if contour_labels:
|
|
1711
|
+
# axes.clabel(plot_field_contour, fontsize=10)
|
|
1712
|
+
|
|
1713
|
+
# cax_contour = divider.append_axes("bottom", size="5%", pad=0.1)
|
|
1714
|
+
# if norm_field_contour:
|
|
1715
|
+
# vmin_field_contour=None
|
|
1716
|
+
# vmax_field_contour=None
|
|
1717
|
+
# norm_contour=norm_field_contour
|
|
1718
|
+
# else:
|
|
1719
|
+
# norm_contour= Normalize(vmin=vmin_field_contour, vmax=vmax_field_contour)
|
|
1720
|
+
#
|
|
1721
|
+
# sm_contour= plt.cm.ScalarMappable(norm=norm_contour, cmap = plot_field_contour.cmap)
|
|
1722
|
+
# sm_contour.set_array([])
|
|
1723
|
+
#
|
|
1724
|
+
# cbar_field_contour = plt.colorbar(sm_contour, orientation='horizontal',ticks=levels_field_contour,cax=cax_contour)
|
|
1725
|
+
# cbar_field_contour.ax.set_xlabel(label_field_contour)
|
|
1726
|
+
# cbar_field_contour.set_clim(vmin_field_contour, vmax_field_contour)
|
|
1727
|
+
#
|
|
1728
|
+
for i_row, row in track.iterrows():
|
|
1729
|
+
cell = row["cell"]
|
|
1730
|
+
feature = row["feature"]
|
|
1731
|
+
# logging.debug("cell: "+ str(row['cell']))
|
|
1732
|
+
# logging.debug("feature: "+ str(row['feature']))
|
|
1733
|
+
|
|
1734
|
+
if cell == cell_i:
|
|
1735
|
+
color = "darkred"
|
|
1736
|
+
if feature_number:
|
|
1737
|
+
cell_string = " " + str(int(cell)) + " (" + str(int(feature)) + ")"
|
|
1738
|
+
else:
|
|
1739
|
+
cell_string = " " + str(int(cell))
|
|
1740
|
+
elif np.isnan(cell):
|
|
1741
|
+
color = "gray"
|
|
1742
|
+
if feature_number:
|
|
1743
|
+
cell_string = " " + "(" + str(int(feature)) + ")"
|
|
1744
|
+
else:
|
|
1745
|
+
cell_string = " "
|
|
1746
|
+
else:
|
|
1747
|
+
color = "darkorange"
|
|
1748
|
+
if feature_number:
|
|
1749
|
+
cell_string = " " + str(int(cell)) + " (" + str(int(feature)) + ")"
|
|
1750
|
+
else:
|
|
1751
|
+
cell_string = " " + str(int(cell))
|
|
1752
|
+
|
|
1753
|
+
# axes.text(row['projection_x_coordinate']/1000,
|
|
1754
|
+
# row['projection_y_coordinate']/1000,
|
|
1755
|
+
# 0,
|
|
1756
|
+
# cell_string,color=color,fontsize=6, clip_on=True)
|
|
1757
|
+
|
|
1758
|
+
# # Plot marker for tracked cell centre as a cross
|
|
1759
|
+
# axes.plot(row['projection_x_coordinate']/1000,
|
|
1760
|
+
# row['projection_y_coordinate']/1000,
|
|
1761
|
+
# 0,
|
|
1762
|
+
# 'x', color=color,markersize=4)
|
|
1763
|
+
|
|
1764
|
+
# Create surface projection of mask for the respective cell and plot it in the right color
|
|
1765
|
+
# z_coord = 'model_level_number'
|
|
1766
|
+
# if len(mask_total.shape)==3:
|
|
1767
|
+
# mask_total_i_surface = mask_features_surface(mask_total, feature, masked=False, z_coord=z_coord)
|
|
1768
|
+
# elif len(mask_total.shape)==2:
|
|
1769
|
+
# mask_total_i_surface=mask_features(mask_total, feature, masked=False, z_coord=z_coord)
|
|
1770
|
+
# axes.contour(mask_total_i_surface.coord('projection_x_coordinate').points/1000,
|
|
1771
|
+
# mask_total_i_surface.coord('projection_y_coordinate').points/1000,
|
|
1772
|
+
# 0,
|
|
1773
|
+
# mask_total_i_surface.data,
|
|
1774
|
+
# levels=[0, feature], colors=color, linestyles=':',linewidth=1)
|
|
1775
|
+
mask_feature = mask_total.data == feature
|
|
1776
|
+
|
|
1777
|
+
axes.scatter(
|
|
1778
|
+
# xx[mask_feature]/1000, yy[mask_feature]/1000, zz[mask_feature]/1000,
|
|
1779
|
+
xx[mask_feature] / 1000,
|
|
1780
|
+
yy[mask_feature] / 1000,
|
|
1781
|
+
zz[mask_feature],
|
|
1782
|
+
c=color,
|
|
1783
|
+
marker=",",
|
|
1784
|
+
s=5, # 60000.0 * TWC_i[Mask_particle],
|
|
1785
|
+
alpha=0.3,
|
|
1786
|
+
cmap="inferno",
|
|
1787
|
+
label=cell_string,
|
|
1788
|
+
rasterized=True,
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
axes.set_xlim(xlim)
|
|
1792
|
+
axes.set_ylim(ylim)
|
|
1793
|
+
axes.set_zlim([0, 100])
|
|
1794
|
+
|
|
1795
|
+
# axes.set_zlim([0, 20])
|
|
1796
|
+
# axes.set_zticks([0, 5,10,15, 20])
|
|
1797
|
+
axes.set_xlabel("x (km)")
|
|
1798
|
+
axes.set_ylabel("y (km)")
|
|
1799
|
+
axes.zaxis.set_rotate_label(False) # disable automatic rotation
|
|
1800
|
+
# axes.set_zlabel('z (km)', rotation=90)
|
|
1801
|
+
axes.set_zlabel("model level", rotation=90)
|
|
1802
|
+
|
|
1803
|
+
return axes
|
|
1804
|
+
|
|
1805
|
+
|
|
1806
|
+
def plot_mask_cell_track_static_timeseries(
|
|
1807
|
+
cell,
|
|
1808
|
+
track,
|
|
1809
|
+
cog,
|
|
1810
|
+
features,
|
|
1811
|
+
mask_total,
|
|
1812
|
+
field_contour,
|
|
1813
|
+
field_filled,
|
|
1814
|
+
track_variable=None,
|
|
1815
|
+
variable=None,
|
|
1816
|
+
variable_ylabel=None,
|
|
1817
|
+
variable_label=[None],
|
|
1818
|
+
variable_legend=False,
|
|
1819
|
+
variable_color=None,
|
|
1820
|
+
width=10000,
|
|
1821
|
+
n_extend=1,
|
|
1822
|
+
name="test",
|
|
1823
|
+
plotdir="./",
|
|
1824
|
+
file_format=["png"],
|
|
1825
|
+
figsize=(20 / 2.54, 10 / 2.54),
|
|
1826
|
+
dpi=300,
|
|
1827
|
+
**kwargs,
|
|
1828
|
+
):
|
|
1829
|
+
"""Make plots for all cells with fixed frame including entire development of the cell and with one background field as filling and one background field as contrours
|
|
1830
|
+
Input:
|
|
1831
|
+
Output:
|
|
1832
|
+
"""
|
|
1833
|
+
warnings.warn(
|
|
1834
|
+
"plot_mask_cell_track_static_timeseries is depreciated and will be removed or significantly changed in v2.0.",
|
|
1835
|
+
DeprecationWarning,
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
mpl_backend = mpl.get_backend()
|
|
1839
|
+
if mpl_backend != "agg":
|
|
1840
|
+
warnings.warn(
|
|
1841
|
+
"When using tobac plotting functions that render a figure, you may need "
|
|
1842
|
+
"to set the Matplotlib backend to 'agg' by `matplotlib.use('agg')."
|
|
1843
|
+
)
|
|
1844
|
+
import matplotlib.pyplot as plt
|
|
1845
|
+
|
|
1846
|
+
from iris import Constraint
|
|
1847
|
+
from numpy import unique
|
|
1848
|
+
import os
|
|
1849
|
+
import pandas as pd
|
|
1850
|
+
|
|
1851
|
+
track_cell = track[track["cell"] == cell]
|
|
1852
|
+
x_min = track_cell["projection_x_coordinate"].min() - width
|
|
1853
|
+
x_max = track_cell["projection_x_coordinate"].max() + width
|
|
1854
|
+
y_min = track_cell["projection_y_coordinate"].min() - width
|
|
1855
|
+
y_max = track_cell["projection_y_coordinate"].max() + width
|
|
1856
|
+
time_min = track_cell["time"].min()
|
|
1857
|
+
# time_max=track_cell['time'].max()
|
|
1858
|
+
|
|
1859
|
+
track_variable_cell = track_variable[track_variable["cell"] == cell]
|
|
1860
|
+
track_variable_cell["time_cell"] = pd.to_timedelta(track_variable_cell["time_cell"])
|
|
1861
|
+
# track_variable_cell=track_variable_cell[(track_variable_cell['time']>=time_min) & (track_variable_cell['time']<=time_max)]
|
|
1862
|
+
|
|
1863
|
+
# set up looping over time based on mask's time coordinate to allow for one timestep before and after the track
|
|
1864
|
+
time_coord = mask_total.coord("time")
|
|
1865
|
+
time = time_coord.units.num2date(time_coord.points)
|
|
1866
|
+
i_start = max(0, np.where(time == track_cell["time"].values[0])[0][0] - n_extend)
|
|
1867
|
+
i_end = min(
|
|
1868
|
+
len(time) - 1,
|
|
1869
|
+
np.where(time == track_cell["time"].values[-1])[0][0] + n_extend + 1,
|
|
1870
|
+
)
|
|
1871
|
+
time_cell = time[slice(i_start, i_end)]
|
|
1872
|
+
for time_i in time_cell:
|
|
1873
|
+
constraint_time = Constraint(time=time_i)
|
|
1874
|
+
constraint_x = Constraint(
|
|
1875
|
+
projection_x_coordinate=lambda cell: x_min < cell < x_max
|
|
1876
|
+
)
|
|
1877
|
+
constraint_y = Constraint(
|
|
1878
|
+
projection_y_coordinate=lambda cell: y_min < cell < y_max
|
|
1879
|
+
)
|
|
1880
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
1881
|
+
|
|
1882
|
+
mask_total_i = mask_total.extract(constraint)
|
|
1883
|
+
if field_contour is None:
|
|
1884
|
+
field_contour_i = None
|
|
1885
|
+
else:
|
|
1886
|
+
field_contour_i = field_contour.extract(constraint)
|
|
1887
|
+
if field_filled is None:
|
|
1888
|
+
field_filled_i = None
|
|
1889
|
+
else:
|
|
1890
|
+
field_filled_i = field_filled.extract(constraint)
|
|
1891
|
+
|
|
1892
|
+
track_i = track[track["time"] == time_i]
|
|
1893
|
+
cells_mask = list(unique(mask_total_i.core_data()))
|
|
1894
|
+
track_cells = track_i.loc[
|
|
1895
|
+
(track_i["projection_x_coordinate"] > x_min)
|
|
1896
|
+
& (track_i["projection_x_coordinate"] < x_max)
|
|
1897
|
+
& (track_i["projection_y_coordinate"] > y_min)
|
|
1898
|
+
& (track_i["projection_y_coordinate"] < y_max)
|
|
1899
|
+
]
|
|
1900
|
+
cells_track = list(track_cells["cell"].values)
|
|
1901
|
+
cells = list(set(cells_mask + cells_track))
|
|
1902
|
+
if cell not in cells:
|
|
1903
|
+
cells.append(cell)
|
|
1904
|
+
if 0 in cells:
|
|
1905
|
+
cells.remove(0)
|
|
1906
|
+
track_i = track_i[track_i["cell"].isin(cells)]
|
|
1907
|
+
|
|
1908
|
+
if cog is None:
|
|
1909
|
+
cog_i = None
|
|
1910
|
+
else:
|
|
1911
|
+
cog_i = cog[cog["cell"].isin(cells)]
|
|
1912
|
+
cog_i = cog_i[cog_i["time"] == time_i]
|
|
1913
|
+
|
|
1914
|
+
if features is None:
|
|
1915
|
+
features_i = None
|
|
1916
|
+
else:
|
|
1917
|
+
features_i = features[features["time"] == time_i]
|
|
1918
|
+
|
|
1919
|
+
fig1, ax1 = plt.subplots(ncols=2, nrows=1, figsize=figsize)
|
|
1920
|
+
fig1.subplots_adjust(left=0.1, bottom=0.15, right=0.90, top=0.85, wspace=0.3)
|
|
1921
|
+
|
|
1922
|
+
datestring_stamp = time_i.strftime("%Y-%m-%d %H:%M:%S")
|
|
1923
|
+
if time_i in track_cell["time"].values:
|
|
1924
|
+
time_cell_i = track_cell[track_cell["time"].values == time_i]["time_cell"]
|
|
1925
|
+
celltime_stamp = "%02d:%02d:%02d" % (
|
|
1926
|
+
time_cell_i.dt.total_seconds() // 3600,
|
|
1927
|
+
(time_cell_i.dt.total_seconds() % 3600) // 60,
|
|
1928
|
+
time_cell_i.dt.total_seconds() % 60,
|
|
1929
|
+
)
|
|
1930
|
+
else:
|
|
1931
|
+
celltime_stamp = " - "
|
|
1932
|
+
title = celltime_stamp + " , " + datestring_stamp
|
|
1933
|
+
datestring_file = time_i.strftime("%Y-%m-%d_%H%M%S")
|
|
1934
|
+
|
|
1935
|
+
# plot evolving timeseries of variable to second axis:
|
|
1936
|
+
ax1[0] = plot_mask_cell_individual_static(
|
|
1937
|
+
cell_i=cell,
|
|
1938
|
+
track=track_i,
|
|
1939
|
+
cog=cog_i,
|
|
1940
|
+
features=features_i,
|
|
1941
|
+
mask_total=mask_total_i,
|
|
1942
|
+
field_contour=field_contour_i,
|
|
1943
|
+
field_filled=field_filled_i,
|
|
1944
|
+
xlim=[x_min / 1000, x_max / 1000],
|
|
1945
|
+
ylim=[y_min / 1000, y_max / 1000],
|
|
1946
|
+
axes=ax1[0],
|
|
1947
|
+
title=title,
|
|
1948
|
+
**kwargs,
|
|
1949
|
+
)
|
|
1950
|
+
|
|
1951
|
+
track_variable_past = track_variable_cell[
|
|
1952
|
+
(track_variable_cell["time"] >= time_min)
|
|
1953
|
+
& (track_variable_cell["time"] <= time_i)
|
|
1954
|
+
]
|
|
1955
|
+
track_variable_current = track_variable_cell[
|
|
1956
|
+
track_variable_cell["time"] == time_i
|
|
1957
|
+
]
|
|
1958
|
+
|
|
1959
|
+
if variable_color is None:
|
|
1960
|
+
variable_color = "navy"
|
|
1961
|
+
|
|
1962
|
+
if type(variable) is str:
|
|
1963
|
+
# logging.debug('variable: '+str(variable))
|
|
1964
|
+
if type(variable_color) is str:
|
|
1965
|
+
variable_color = {variable: variable_color}
|
|
1966
|
+
variable = [variable]
|
|
1967
|
+
|
|
1968
|
+
for i_variable, variable_i in enumerate(variable):
|
|
1969
|
+
color = variable_color[variable_i]
|
|
1970
|
+
ax1[1].plot(
|
|
1971
|
+
track_variable_past["time_cell"].dt.total_seconds() / 60.0,
|
|
1972
|
+
track_variable_past[variable_i].values,
|
|
1973
|
+
color=color,
|
|
1974
|
+
linestyle="-",
|
|
1975
|
+
label=variable_label[i_variable],
|
|
1976
|
+
)
|
|
1977
|
+
ax1[1].plot(
|
|
1978
|
+
track_variable_current["time_cell"].dt.total_seconds() / 60.0,
|
|
1979
|
+
track_variable_current[variable_i].values,
|
|
1980
|
+
color=color,
|
|
1981
|
+
marker="o",
|
|
1982
|
+
markersize=4,
|
|
1983
|
+
fillstyle="full",
|
|
1984
|
+
)
|
|
1985
|
+
ax1[1].yaxis.tick_right()
|
|
1986
|
+
ax1[1].yaxis.set_label_position("right")
|
|
1987
|
+
ax1[1].set_xlim([0, 2 * 60])
|
|
1988
|
+
ax1[1].set_xticks(np.arange(0, 120, 15))
|
|
1989
|
+
ax1[1].set_ylim([0, max(10, 1.25 * track_variable_cell[variable].max().max())])
|
|
1990
|
+
ax1[1].set_xlabel("cell lifetime (min)")
|
|
1991
|
+
if variable_ylabel == None:
|
|
1992
|
+
variable_ylabel = variable
|
|
1993
|
+
ax1[1].set_ylabel(variable_ylabel)
|
|
1994
|
+
ax1[1].set_title(title)
|
|
1995
|
+
|
|
1996
|
+
# insert legend, if flag is True
|
|
1997
|
+
if variable_legend:
|
|
1998
|
+
if len(variable_label) < 5:
|
|
1999
|
+
ncol = 1
|
|
2000
|
+
else:
|
|
2001
|
+
ncol = 2
|
|
2002
|
+
ax1[1].legend(
|
|
2003
|
+
loc="upper right", bbox_to_anchor=(1, 1), ncol=ncol, fontsize=8
|
|
2004
|
+
)
|
|
2005
|
+
|
|
2006
|
+
out_dir = os.path.join(plotdir, name)
|
|
2007
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
2008
|
+
if "png" in file_format:
|
|
2009
|
+
savepath_png = os.path.join(out_dir, name + "_" + datestring_file + ".png")
|
|
2010
|
+
fig1.savefig(savepath_png, dpi=dpi)
|
|
2011
|
+
logging.debug("Mask static plot saved to " + savepath_png)
|
|
2012
|
+
if "pdf" in file_format:
|
|
2013
|
+
savepath_pdf = os.path.join(out_dir, name + "_" + datestring_file + ".pdf")
|
|
2014
|
+
fig1.savefig(savepath_pdf, dpi=dpi)
|
|
2015
|
+
logging.debug("Mask static plot saved to " + savepath_pdf)
|
|
2016
|
+
plt.close()
|
|
2017
|
+
plt.clf()
|
|
2018
|
+
|
|
2019
|
+
|
|
2020
|
+
def map_tracks(
|
|
2021
|
+
track, axis_extent=None, figsize=None, axes=None, untracked_cell_value=-1
|
|
2022
|
+
):
|
|
2023
|
+
"""Plot the trajectories of the cells on a map.
|
|
2024
|
+
|
|
2025
|
+
Parameters
|
|
2026
|
+
----------
|
|
2027
|
+
track : pandas.DataFrame
|
|
2028
|
+
Dataframe containing the linked features with a
|
|
2029
|
+
column 'cell'.
|
|
2030
|
+
|
|
2031
|
+
axis_extent : matplotlib.axes, optional
|
|
2032
|
+
Array containing the bounds of the longitude
|
|
2033
|
+
and latitude values. The structure is
|
|
2034
|
+
[long_min, long_max, lat_min, lat_max].
|
|
2035
|
+
Default is None.
|
|
2036
|
+
|
|
2037
|
+
figsize : tuple of floats, optional
|
|
2038
|
+
Width, height of the plot in inches.
|
|
2039
|
+
Default is (10, 10).
|
|
2040
|
+
|
|
2041
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot, optional
|
|
2042
|
+
GeoAxesSubplot to use for plotting. Default is None.
|
|
2043
|
+
|
|
2044
|
+
untracked_cell_value : int or np.nan, optional
|
|
2045
|
+
Value of untracked cells in track['cell'].
|
|
2046
|
+
Default is -1.
|
|
2047
|
+
|
|
2048
|
+
Returns
|
|
2049
|
+
-------
|
|
2050
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot
|
|
2051
|
+
Axes with the plotted trajectories.
|
|
2052
|
+
|
|
2053
|
+
Raises
|
|
2054
|
+
------
|
|
2055
|
+
ValueError
|
|
2056
|
+
If no axes is passed.
|
|
2057
|
+
"""
|
|
2058
|
+
|
|
2059
|
+
if figsize is not None:
|
|
2060
|
+
warnings.warn(
|
|
2061
|
+
"figsize is depreciated as this function does not create its own figure.",
|
|
2062
|
+
DeprecationWarning,
|
|
2063
|
+
)
|
|
2064
|
+
if axes is None:
|
|
2065
|
+
raise ValueError(
|
|
2066
|
+
"axes needed to plot tracks onto. Pass in an axis to axes to resolve this error."
|
|
2067
|
+
)
|
|
2068
|
+
|
|
2069
|
+
lat_dim, lon_dim, _ = find_dataframe_horizontal_coords(track, coord_type="latlon")
|
|
2070
|
+
|
|
2071
|
+
for cell in track["cell"].dropna().unique():
|
|
2072
|
+
if cell == untracked_cell_value:
|
|
2073
|
+
continue
|
|
2074
|
+
track_i = track[track["cell"] == cell]
|
|
2075
|
+
axes.plot(track_i[lon_dim], track_i[lat_dim], "-")
|
|
2076
|
+
if axis_extent:
|
|
2077
|
+
axes.set_extent(axis_extent)
|
|
2078
|
+
axes = make_map(axes)
|
|
2079
|
+
return axes
|
|
2080
|
+
|
|
2081
|
+
|
|
2082
|
+
def make_map(axes):
|
|
2083
|
+
"""Configure the parameters of cartopy for plotting.
|
|
2084
|
+
|
|
2085
|
+
Parameters
|
|
2086
|
+
----------
|
|
2087
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot
|
|
2088
|
+
GeoAxesSubplot to configure.
|
|
2089
|
+
|
|
2090
|
+
Returns
|
|
2091
|
+
-------
|
|
2092
|
+
axes : cartopy.mpl.geoaxes.GeoAxesSubplot
|
|
2093
|
+
Cartopy axes to configure
|
|
2094
|
+
"""
|
|
2095
|
+
|
|
2096
|
+
import matplotlib.ticker as mticker
|
|
2097
|
+
import cartopy.crs as ccrs
|
|
2098
|
+
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
|
|
2099
|
+
|
|
2100
|
+
gl = axes.gridlines(
|
|
2101
|
+
crs=ccrs.PlateCarree(),
|
|
2102
|
+
draw_labels=True,
|
|
2103
|
+
linewidth=2,
|
|
2104
|
+
color="gray",
|
|
2105
|
+
alpha=0.5,
|
|
2106
|
+
linestyle="-",
|
|
2107
|
+
)
|
|
2108
|
+
axes.coastlines("10m")
|
|
2109
|
+
|
|
2110
|
+
gl.xlabels_top = False
|
|
2111
|
+
gl.ylabels_right = False
|
|
2112
|
+
gl.xlocator = mticker.MaxNLocator(nbins=5, min_n_ticks=3, steps=None)
|
|
2113
|
+
gl.ylocator = mticker.MaxNLocator(nbins=5, min_n_ticks=3, steps=None)
|
|
2114
|
+
gl.xformatter = LONGITUDE_FORMATTER
|
|
2115
|
+
gl.yformatter = LATITUDE_FORMATTER
|
|
2116
|
+
# gl.xlabel_style = {'size': 15, 'color': 'gray'}
|
|
2117
|
+
# gl.xlabel_style = {'color': 'red', 'weight': 'bold'}
|
|
2118
|
+
return axes
|
|
2119
|
+
|
|
2120
|
+
|
|
2121
|
+
def plot_lifetime_histogram(
|
|
2122
|
+
track, axes=None, bin_edges=np.arange(0, 200, 20), density=False, **kwargs
|
|
2123
|
+
):
|
|
2124
|
+
"""Plot the liftetime histogram of the cells.
|
|
2125
|
+
|
|
2126
|
+
Parameters
|
|
2127
|
+
----------
|
|
2128
|
+
track : pandas.DataFrame
|
|
2129
|
+
DataFrame of the features containing the columns
|
|
2130
|
+
'cell' and 'time_cell'.
|
|
2131
|
+
|
|
2132
|
+
axes : matplotlib.axes.Axes, optional
|
|
2133
|
+
Matplotlib axes to plot on. Default is None.
|
|
2134
|
+
|
|
2135
|
+
bin_edges : int or ndarray, optional
|
|
2136
|
+
If bin_edges is an int, it defines the number of
|
|
2137
|
+
equal-width bins in the given range. If bins is
|
|
2138
|
+
a sequence, it defines a monotonically increasing
|
|
2139
|
+
array of bin edges, including the rightmost edge.
|
|
2140
|
+
Default is np.arange(0, 200, 20).
|
|
2141
|
+
|
|
2142
|
+
density : bool, optional
|
|
2143
|
+
If False, the result will contain the number of
|
|
2144
|
+
samples in each bin. If True, the result is the
|
|
2145
|
+
value of the probability density function at the
|
|
2146
|
+
bin, normalized such that the integral over the
|
|
2147
|
+
range is 1. Default is False.
|
|
2148
|
+
|
|
2149
|
+
**kwargs
|
|
2150
|
+
|
|
2151
|
+
Returns
|
|
2152
|
+
-------
|
|
2153
|
+
plot_hist : list
|
|
2154
|
+
List containing the matplotlib.lines.Line2D instance
|
|
2155
|
+
of the histogram
|
|
2156
|
+
"""
|
|
2157
|
+
|
|
2158
|
+
hist, bin_edges, bin_centers = lifetime_histogram(
|
|
2159
|
+
track, bin_edges=bin_edges, density=density
|
|
2160
|
+
)
|
|
2161
|
+
plot_hist = axes.plot(bin_centers, hist, **kwargs)
|
|
2162
|
+
return plot_hist
|
|
2163
|
+
|
|
2164
|
+
|
|
2165
|
+
def plot_lifetime_histogram_bar(
|
|
2166
|
+
track,
|
|
2167
|
+
axes=None,
|
|
2168
|
+
bin_edges=np.arange(0, 200, 20),
|
|
2169
|
+
density=False,
|
|
2170
|
+
width_bar=1,
|
|
2171
|
+
shift=0.5,
|
|
2172
|
+
**kwargs,
|
|
2173
|
+
):
|
|
2174
|
+
"""Plot the liftetime histogram of the cells as bar plot.
|
|
2175
|
+
|
|
2176
|
+
Parameters
|
|
2177
|
+
----------
|
|
2178
|
+
track : pandas.DataFrame
|
|
2179
|
+
DataFrame of the features containing the columns
|
|
2180
|
+
'cell' and 'time_cell'.
|
|
2181
|
+
|
|
2182
|
+
axes : matplotlib.axes.Axes, optional
|
|
2183
|
+
Matplotlib axes to plot on. Default is None.
|
|
2184
|
+
|
|
2185
|
+
bin_edges : int or ndarray, optional
|
|
2186
|
+
If bin_edges is an int, it defines the number of
|
|
2187
|
+
equal-width bins in the given range. If bins is
|
|
2188
|
+
a sequence, it defines a monotonically increasing
|
|
2189
|
+
array of bin edges, including the rightmost edge.
|
|
2190
|
+
|
|
2191
|
+
density : bool, optional
|
|
2192
|
+
If False, the result will contain the number of
|
|
2193
|
+
samples in each bin. If True, the result is the
|
|
2194
|
+
value of the probability density function at the
|
|
2195
|
+
bin, normalized such that the integral over the
|
|
2196
|
+
range is 1. Default is False.
|
|
2197
|
+
|
|
2198
|
+
width_bar : float
|
|
2199
|
+
Width of the bars. Default is 1.
|
|
2200
|
+
|
|
2201
|
+
shift : float
|
|
2202
|
+
Value to shift the bin centers to the right.
|
|
2203
|
+
Default is 0.5.
|
|
2204
|
+
|
|
2205
|
+
**kwargs
|
|
2206
|
+
|
|
2207
|
+
Returns
|
|
2208
|
+
-------
|
|
2209
|
+
plot_hist : matplotlib.container.BarContainer
|
|
2210
|
+
matplotlib.container.BarContainer instance
|
|
2211
|
+
of the histogram
|
|
2212
|
+
"""
|
|
2213
|
+
|
|
2214
|
+
hist, bin_edges, bin_centers = lifetime_histogram(
|
|
2215
|
+
track, bin_edges=bin_edges, density=density
|
|
2216
|
+
)
|
|
2217
|
+
plot_hist = axes.bar(bin_centers + shift, hist, width=width_bar, **kwargs)
|
|
2218
|
+
return plot_hist
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
def plot_histogram_cellwise(
|
|
2222
|
+
track, bin_edges, variable, quantity, axes=None, density=False, **kwargs
|
|
2223
|
+
):
|
|
2224
|
+
"""Plot the histogram of a variable based on the cells.
|
|
2225
|
+
|
|
2226
|
+
Parameters
|
|
2227
|
+
----------
|
|
2228
|
+
track : pandas.DataFrame
|
|
2229
|
+
DataFrame of the features containing the variable
|
|
2230
|
+
as column and a column 'cell'.
|
|
2231
|
+
|
|
2232
|
+
bin_edges : int or ndarray
|
|
2233
|
+
If bin_edges is an int, it defines the number of
|
|
2234
|
+
equal-width bins in the given range. If bins is
|
|
2235
|
+
a sequence, it defines a monotonically increasing
|
|
2236
|
+
array of bin edges, including the rightmost edge.
|
|
2237
|
+
|
|
2238
|
+
variable : string
|
|
2239
|
+
Column of the DataFrame with the variable on which the
|
|
2240
|
+
histogram is to be based on. Default is None.
|
|
2241
|
+
|
|
2242
|
+
quantity : {'max', 'min', 'mean'}, optional
|
|
2243
|
+
Flag determining wether to use maximum, minimum or mean
|
|
2244
|
+
of a variable from all timeframes the cell covers.
|
|
2245
|
+
Default is 'max'.
|
|
2246
|
+
|
|
2247
|
+
axes : matplotlib.axes.Axes, optional
|
|
2248
|
+
Matplotlib axes to plot on. Default is None.
|
|
2249
|
+
|
|
2250
|
+
density : bool, optional
|
|
2251
|
+
If False, the result will contain the number of
|
|
2252
|
+
samples in each bin. If True, the result is the
|
|
2253
|
+
value of the probability density function at the
|
|
2254
|
+
bin, normalized such that the integral over the
|
|
2255
|
+
range is 1. Default is False.
|
|
2256
|
+
|
|
2257
|
+
**kwargs
|
|
2258
|
+
|
|
2259
|
+
Returns
|
|
2260
|
+
-------
|
|
2261
|
+
plot_hist : list
|
|
2262
|
+
List containing the matplotlib.lines.Line2D instance
|
|
2263
|
+
of the histogram
|
|
2264
|
+
"""
|
|
2265
|
+
|
|
2266
|
+
hist, bin_edges, bin_centers = histogram_cellwise(
|
|
2267
|
+
track,
|
|
2268
|
+
bin_edges=bin_edges,
|
|
2269
|
+
variable=variable,
|
|
2270
|
+
quantity=quantity,
|
|
2271
|
+
density=density,
|
|
2272
|
+
)
|
|
2273
|
+
plot_hist = axes.plot(bin_centers, hist, **kwargs)
|
|
2274
|
+
return plot_hist
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
def plot_histogram_featurewise(
|
|
2278
|
+
Track, bin_edges, variable, axes=None, density=False, **kwargs
|
|
2279
|
+
):
|
|
2280
|
+
"""Plot the histogram of a variable based on the features.
|
|
2281
|
+
|
|
2282
|
+
Parameters
|
|
2283
|
+
----------
|
|
2284
|
+
Track : pandas.DataFrame
|
|
2285
|
+
DataFrame of the features containing the variable
|
|
2286
|
+
as column.
|
|
2287
|
+
|
|
2288
|
+
bin_edges : int or ndarray
|
|
2289
|
+
If bin_edges is an int, it defines the number of
|
|
2290
|
+
equal-width bins in the given range. If bins is
|
|
2291
|
+
a sequence, it defines a monotonically increasing
|
|
2292
|
+
array of bin edges, including the rightmost edge.
|
|
2293
|
+
|
|
2294
|
+
variable : str
|
|
2295
|
+
Column of the DataFrame with the variable on which the
|
|
2296
|
+
histogram is to be based on.
|
|
2297
|
+
|
|
2298
|
+
axes : matplotlib.axes.Axes, optional
|
|
2299
|
+
Matplotlib axes to plot on. Default is None.
|
|
2300
|
+
|
|
2301
|
+
density : bool, optional
|
|
2302
|
+
If False, the result will contain the number of
|
|
2303
|
+
samples in each bin. If True, the result is the
|
|
2304
|
+
value of the probability density function at the
|
|
2305
|
+
bin, normalized such that the integral over the
|
|
2306
|
+
range is 1. Default is False.
|
|
2307
|
+
|
|
2308
|
+
**kwargs
|
|
2309
|
+
|
|
2310
|
+
Returns
|
|
2311
|
+
-------
|
|
2312
|
+
plot_hist : list
|
|
2313
|
+
List containing the matplotlib.lines.Line2D instance
|
|
2314
|
+
of the histogram
|
|
2315
|
+
"""
|
|
2316
|
+
|
|
2317
|
+
hist, bin_edges, bin_centers = histogram_featurewise(
|
|
2318
|
+
Track, bin_edges=bin_edges, variable=variable, density=density
|
|
2319
|
+
)
|
|
2320
|
+
plot_hist = axes.plot(bin_centers, hist, **kwargs)
|
|
2321
|
+
return plot_hist
|