| import concurrent
|
| import multiprocessing
|
| import netCDF4
|
| import os
|
|
|
| from matplotlib import gridspec, colors, pyplot as plt
|
| import numpy as np
|
| import torch
|
|
|
| from ..analysis import confmatrix, fss, rank
|
| from ..features import io
|
| from .cm import homeyer_rainbow
|
|
|
|
|
| def reverse_transform_R(R, mean=-0.051, std=0.528):
|
| return 10**(R*std + mean)
|
|
|
|
|
| def plot_precip_image(
|
| ax, R,
|
| Rmin=0, Rmax=25, threshold_mmh=0.1,
|
| transform_R=False,
|
| grid_spacing=64
|
| ):
|
| if isinstance(R, torch.Tensor):
|
| R = R.detach().numpy()
|
| if transform_R:
|
| R = reverse_transform_R(R, mean=mean, std=std)
|
| Rmin = reverse_transform_R(Rmin)
|
| Rmax = reverse_transform_R(Rmax)
|
| if threshold_mmh:
|
| Rmin = max(Rmin, threshold_mmh)
|
| R[R < threshold_mmh] = np.nan
|
| norm = colors.LogNorm(Rmin, Rmax)
|
| ax.set_yticks(np.arange(0, R.shape[0], grid_spacing))
|
| ax.set_xticks(np.arange(0, R.shape[1], grid_spacing))
|
| ax.grid(which='major', alpha=0.35)
|
| ax.tick_params(left=False, bottom=False,
|
| labelleft=False, labelbottom=False)
|
|
|
| return ax.imshow(R, norm=norm, cmap=homeyer_rainbow)
|
|
|
|
|
| def plot_autoencoder_reconstruction(
|
| R, R_hat, samples=8, timesteps=4,
|
| out_file=None
|
| ):
|
| fig = plt.figure(figsize=(2*timesteps*2+0.5, samples*2), dpi=150)
|
|
|
| gs = gridspec.GridSpec(
|
| samples, 2*timesteps+1,
|
| width_ratios=(1,)*(2*timesteps)+(0.2,),
|
| wspace=0.02, hspace=0.02
|
| )
|
| for k in range(samples):
|
| for (i,j) in enumerate(range(-timesteps,0)):
|
| ax = fig.add_subplot(gs[k,i])
|
| im = plot_precip_image(ax, R[k,0,j,:,:])
|
| for (i,j) in enumerate(range(-timesteps,0)):
|
| ax = fig.add_subplot(gs[k,i+timesteps])
|
| im = plot_precip_image(ax, R_hat[k,0,j,:,:])
|
|
|
| cax = fig.add_subplot(gs[:,-1])
|
| plt.colorbar(im, cax=cax)
|
|
|
| if out_file is not None:
|
| out_file = fig.savefig(out_file, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_animation(x, y, out_dir, sample=0, fmt="{}_{:02d}.png"):
|
| def plot_frame(R, label, timestep):
|
| fig = plt.figure()
|
| ax = fig.add_subplot()
|
| im = plot_precip_image(ax, R[sample,0,timestep,:,:])
|
| fn = fmt.format(label, k)
|
| fn = os.path.join(out_dir, fn)
|
| fig.savefig(fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
| for k in range(x.shape[2]):
|
| plot_frame(x, "x", k)
|
|
|
| for k in range(y.shape[2]):
|
| plot_frame(y, "y", k)
|
|
|
|
|
| model_colors = {
|
| "mch-dgmr": "#E69F00",
|
| "mch-pysteps": "#009E73",
|
| "mch-iters=50-res=256": "#0072B2",
|
| "mch-persistence": "#888888",
|
| "dwd-dgmr": "#E69F00",
|
| "dwd-pysteps": "#009E73",
|
| "dwd-iters=50-res=256": "#0072B2",
|
| "dwd-persistence": "#888888",
|
| "pm-mch-dgmr": "#E69F00",
|
| "pm-mch-pysteps": "#009E73",
|
| "pm-mch-iters=50-res=256": "#0072B2",
|
| "pm-dwd-dgmr": "#E69F00",
|
| "pm-dwd-pysteps": "#009E73",
|
| "pm-dwd-iters=50-res=256": "#0072B2",
|
| }
|
|
|
| scale_linestyles = {
|
| "1x1": "-",
|
| "8x8": "--",
|
| "64x64": ":"
|
| }
|
|
|
|
|
| def plot_crps(
|
| log=False,
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| model_labels=("LDCast", "DGMR", "PySTEPS", "Persist."),
|
| interval_mins=5,
|
| out_fn=None,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| crop_box=None
|
| ):
|
| crps = {}
|
| crps_name = "logcrps" if log else "crps"
|
| for model in models:
|
| crps[model] = {}
|
| fn = f"../results/crps/{crps_name}-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| var = f"crps_pool{scale}"
|
| crps_model_scale = np.array(ds[var][:], copy=False)
|
| if crop_box is not None:
|
| scale_int = int(scale.split("x")[0])
|
| crps_model_scale = crps_model_scale[
|
| ...,
|
| crop_box[0][0]//scale_int:crop_box[0][1]//scale_int,
|
| crop_box[1][0]//scale_int:crop_box[1][1]//scale_int
|
| ]
|
| crps[model][scale] = crps_model_scale.mean(axis=(0,1,3,4))
|
| del crps_model_scale
|
|
|
| if ax is None:
|
| fig = plt.figure(figsize=(8,5))
|
| ax = fig.add_subplot()
|
|
|
| max_t = 0
|
| for (model, label) in zip(models, model_labels):
|
| for scale in scales:
|
| score = crps[model][scale]
|
| color = model_colors[model]
|
| linestyle = scale_linestyles[scale]
|
| t = np.arange(
|
| interval_mins, (len(score)+0.1)*interval_mins, interval_mins
|
| )
|
| max_t = max(max_t, t[-1])
|
| ax.plot(t, score, color=color, linestyle=linestyle,
|
| label=label)
|
|
|
| if add_legend:
|
| plt.legend()
|
| if add_xlabel:
|
| plt.xlabel("Lead time [min]", fontsize=12)
|
| if add_ylabel:
|
| plt.ylabel(
|
| "LogCRPS" if log else "CRPS [mm h$^\\mathrm{-1}$]",
|
| fontsize=12
|
| )
|
|
|
| ax.set_xlim((0, max_t))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_rank_distribution(
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| out_fn=None,
|
| num_ensemble_members=32,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| crop_box=None
|
| ):
|
| rank_hist = {}
|
| rank_KL = {}
|
| for model in models:
|
| fn = f"../results/ranks/ranks-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| var = f"ranks_pool{scale}"
|
| ranks_model_scale = np.array(ds[var][:], copy=False)
|
| if crop_box is not None:
|
| scale_int = int(scale.split("x")[0])
|
| ranks_model_scale = ranks_model_scale[
|
| ...,
|
| crop_box[0][0]//scale_int:crop_box[0][1]//scale_int,
|
| crop_box[1][0]//scale_int:crop_box[1][1]//scale_int
|
| ]
|
| rank_hist[(model,scale)] = rank.rank_distribution(ranks_model_scale)
|
| rank_KL[(model,scale)] = rank.rank_DKL(rank_hist[(model,scale)])
|
| del ranks_model_scale
|
|
|
| if ax is None:
|
| fig = plt.figure(figsize=(8,5))
|
| ax = fig.add_subplot()
|
|
|
| for scale in scales:
|
| linestyle = scale_linestyles[scale]
|
| for (model, label) in zip(models, model_labels):
|
| h = rank_hist[(model,scale)]
|
| color = model_colors[model]
|
| x = np.linspace(0, 1, num_ensemble_members+1)
|
| label_with_score = f"{label}: {rank_KL[(model,scale)]:.3f}"
|
| ax.plot(x, h, color=color, linestyle=linestyle,
|
| label=label_with_score)
|
| h_ideal = 1/(num_ensemble_members+1)
|
| ax.plot([0, 1], [h_ideal, h_ideal], color=(0.4,0.4,0.4),
|
| linewidth=1.0)
|
|
|
| if add_legend:
|
| ax.legend(loc='upper center')
|
| if add_xlabel:
|
| ax.set_xlabel("Normalized rank", fontsize=12)
|
| if add_ylabel:
|
| ax.set_ylabel("Occurrence", fontsize=12)
|
|
|
| ax.set_xlim((0, 1))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
| ax.set_xticks([0, 0.25, 0.5, 0.75, 1])
|
|
|
| ax.set_xticklabels(["0", "0.25", "0.5", "0.75", "1"])
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_rank_metric(
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| interval_mins=5,
|
| metric_name="KL",
|
| out_fn=None,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| ):
|
| rank_metric = {}
|
| for model in models:
|
| rank_metric[model] = {}
|
| fn = f"../results/ranks/ranks-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| var = f"ranks_pool{scale}"
|
| ranks = np.array(ds[var][:], copy=False)
|
| rank_metric[model][scale] = rank.rank_metric_by_leadtime(ranks)
|
| del ranks
|
|
|
| if ax is None:
|
| fig = plt.figure(figsize=(8,5))
|
| ax = fig.add_subplot()
|
|
|
| max_t = 0
|
| for (model, label) in zip(models, model_labels):
|
| for scale in scales:
|
| score = rank_metric[model][scale]
|
| color = model_colors[model]
|
| linestyle = scale_linestyles[scale]
|
| label_with_scale = f"{label} {scale}"
|
| t = np.arange(
|
| interval_mins, (len(score)+0.1)*interval_mins, interval_mins
|
| )
|
| max_t = max(max_t, t[-1])
|
| ax.plot(t, score, color=color, linestyle=linestyle,
|
| label=label_with_scale)
|
|
|
| if add_legend:
|
| plt.legend()
|
| if add_xlabel:
|
| plt.xlabel("Lead time [min]", fontsize=12)
|
| if add_ylabel:
|
| plt.ylabel(f"Rank {metric_name}", fontsize=12)
|
|
|
| ax.set_xlim((0, max_t))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def load_fss(model, scale, use_timesteps, crop_box):
|
| fn = f"../results/fractions/fractions-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| sn = f"{scale}x{scale}"
|
| obs_frac = np.array(ds[f"obs_frac_scale{sn}"][:], copy=False)
|
| fc_frac = np.array(ds[f"fc_frac_scale{sn}"][:], copy=False)
|
| if crop_box is not None:
|
| obs_frac = obs_frac[
|
| ...,
|
| crop_box[0][0]//scale:crop_box[0][1]//scale,
|
| crop_box[1][0]//scale:crop_box[1][1]//scale
|
| ]
|
| fc_frac = fc_frac[
|
| ...,
|
| crop_box[0][0]//scale:crop_box[0][1]//scale,
|
| crop_box[1][0]//scale:crop_box[1][1]//scale
|
| ]
|
| return fss.fractions_skill_score(
|
| obs_frac, fc_frac, use_timesteps=use_timesteps
|
| )
|
|
|
|
|
| def plot_fss(
|
| log=False,
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| interval_mins=5,
|
| out_fn=None,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| scales=None,
|
| use_timesteps=18,
|
| crop_box=None
|
| ):
|
| if scales is None:
|
| scales = 2**np.arange(9)
|
| fss_scale = {}
|
| N_threads = min(multiprocessing.cpu_count(), len(models)*len(scales))
|
| with concurrent.futures.ProcessPoolExecutor(N_threads) as executor:
|
| for model in models:
|
| fss_scale[model] = {}
|
| for scale in scales:
|
| fss_scale[model][scale] = executor.submit(
|
| load_fss, model, scale, use_timesteps, crop_box
|
| )
|
|
|
| for model in models:
|
| for scale in scales:
|
| fss_scale[model][scale] = fss_scale[model][scale].result()
|
|
|
| if ax is None:
|
| fig = plt.figure(figsize=(8,5))
|
| ax = fig.add_subplot()
|
|
|
| for (model, label) in zip(models, model_labels):
|
| scales = sorted(fss_scale[model])
|
| fss_for_model = [fss_scale[model][s] for s in scales]
|
|
|
| model_parts = model.split("-")
|
| if model.startswith("pm-"):
|
| model_without_threshold = "-".join(model_parts[:1] + model_parts[2:])
|
| else:
|
| model_without_threshold = "-".join(model_parts[1:])
|
| color = model_colors[model_without_threshold]
|
|
|
| ax.plot(scales, fss_for_model, color=color,
|
| label=label)
|
|
|
| if add_legend:
|
| plt.legend()
|
| if add_xlabel:
|
| plt.xlabel("Scale [km]", fontsize=12)
|
| if add_ylabel:
|
| plt.ylabel("FSS")
|
|
|
| ax.set_xlim((scales[0], scales[-1]))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_csi_threshold(
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| prob_thresholds=tuple(np.linspace(0,1,33)),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| out_fn=None,
|
| num_ensemble_members=32,
|
| max_timestep=18,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| crop_box=None
|
| ):
|
| csi = {}
|
| for model in models:
|
| fn = f"../results/fractions/fractions-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| fc_var = f"fc_frac_scale{scale}"
|
| fc_frac = np.array(ds[fc_var], copy=False)
|
| fc_frac = fc_frac[...,:max_timestep,:,:]
|
| obs_var = f"obs_frac_scale{scale}"
|
| obs_frac = np.array(ds[obs_var], copy=False)
|
| obs_frac = obs_frac[...,:max_timestep,:,:]
|
| conf_matrix = confmatrix.confusion_matrix_thresholds(
|
| fc_frac, obs_frac, prob_thresholds
|
| )
|
| del fc_frac, obs_frac
|
| csi_scale = confmatrix.intersection_over_union(conf_matrix)
|
| csi[(model,scale)] = csi_scale
|
|
|
| if ax is None:
|
| fig = plt.figure(figsize=(8,5))
|
| ax = fig.add_subplot()
|
|
|
| for scale in scales:
|
| linestyle = scale_linestyles[scale]
|
| for (model, label) in zip(models, model_labels):
|
| c = csi[(model,scale)]
|
| model_parts = model.split("-")
|
| if model.startswith("pm-"):
|
| model_without_threshold = "-".join(model_parts[:1] + model_parts[2:])
|
| else:
|
| model_without_threshold = "-".join(model_parts[1:])
|
| color = model_colors[model_without_threshold]
|
| ax.plot(prob_thresholds, c, color=color, linestyle=linestyle, label=label)
|
|
|
| if add_legend:
|
| ax.legend(loc='upper center')
|
| if add_xlabel:
|
| ax.set_xlabel("Prob. threshold", fontsize=12)
|
| if add_ylabel:
|
| ax.set_ylabel("CSI", fontsize=12)
|
|
|
| ax.set_xlim((0, 1))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
| ax.set_xticks([0, 0.25, 0.5, 0.75, 1])
|
|
|
| ax.set_xticklabels(["0", "0.25", "0.5", "0.75", "1"])
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_csi_leadtime(
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| prob_thresholds=tuple(np.linspace(0,1,33)),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| out_fn=None,
|
| interval_mins=5,
|
| num_ensemble_members=32,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| crop_box=None
|
| ):
|
| csi = {}
|
| for model in models:
|
| fn = f"../results/fractions/fractions-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| fc_var = f"fc_frac_scale{scale}"
|
| fc_frac = np.array(ds[fc_var], copy=False)
|
| obs_var = f"obs_frac_scale{scale}"
|
| obs_frac = np.array(ds[obs_var], copy=False)
|
| conf_matrix = confmatrix.confusion_matrix_thresholds_leadtime(
|
| fc_frac, obs_frac, prob_thresholds
|
| )
|
|
|
| csi_scale = confmatrix.intersection_over_union(conf_matrix)
|
| csi[(model,scale)] = np.nanmax(csi_scale, axis=1)
|
|
|
| max_t = 0
|
| for (model, label) in zip(models, model_labels):
|
| for scale in scales:
|
| score = csi[(model,scale)]
|
|
|
| model_parts = model.split("-")
|
| if model.startswith("pm-"):
|
| model_without_threshold = "-".join(model_parts[:1] + model_parts[2:])
|
| else:
|
| model_without_threshold = "-".join(model_parts[1:])
|
| color = model_colors[model_without_threshold]
|
| linestyle = scale_linestyles[scale]
|
| t = np.arange(
|
| interval_mins, (len(score)+0.1)*interval_mins, interval_mins
|
| )
|
| max_t = max(max_t, t[-1])
|
| ax.plot(t, score, color=color, linestyle=linestyle,
|
| label=label)
|
|
|
| if add_legend:
|
| plt.legend()
|
| if add_xlabel:
|
| plt.xlabel("Lead time [min]", fontsize=12)
|
| if add_ylabel:
|
| plt.ylabel("CSI", fontsize=12)
|
|
|
| ax.set_xlim((0, max_t))
|
| ylim = ax.get_ylim()
|
| ylim = (0, ylim[1])
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|
|
|
| def plot_cost_loss_value(
|
| models=("iters=50-res=256", "dgmr", "pysteps"),
|
| scales=("1x1", "8x8", "64x64"),
|
| prob_thresholds=tuple(np.linspace(0,1,33)),
|
| model_labels=("LDCast", "DGMR", "PySTEPS"),
|
| out_fn=None,
|
| interval_mins=5,
|
| num_ensemble_members=32,
|
| ax=None,
|
| add_xlabel=True,
|
| add_ylabel=True,
|
| add_legend=True,
|
| crop_box=None
|
| ):
|
| value = {}
|
| loss = 1.0
|
| cost = np.linspace(0.01, 1, 100)
|
| for model in models:
|
| fn = f"../results/fractions/fractions-{model}.nc"
|
| with netCDF4.Dataset(fn, 'r') as ds:
|
| for scale in scales:
|
| fc_var = f"fc_frac_scale{scale}"
|
| fc_frac = np.array(ds[fc_var], copy=False)
|
| obs_var = f"obs_frac_scale{scale}"
|
| obs_frac = np.array(ds[obs_var], copy=False)
|
| conf_matrix = confmatrix.confusion_matrix_thresholds(
|
| fc_frac, obs_frac, prob_thresholds
|
| )
|
|
|
| p_clim = obs_frac.mean()
|
| value_scale = []
|
| for c in cost:
|
| v = confmatrix.cost_loss_value(
|
| conf_matrix, c, loss, p_clim
|
| )
|
| value_scale.append(v[len(v)//2])
|
| value[(model,scale)] = np.array(value_scale)
|
|
|
| max_score = 0
|
| for (model, label) in zip(models, model_labels):
|
| for scale in scales:
|
| score = value[(model,scale)]
|
| max_score = max(max_score, score[np.isfinite(score)].max())
|
|
|
| model_parts = model.split("-")
|
| if model.startswith("pm-"):
|
| model_without_threshold = "-".join(model_parts[:1] + model_parts[2:])
|
| else:
|
| model_without_threshold = "-".join(model_parts[1:])
|
| color = model_colors[model_without_threshold]
|
| linestyle = scale_linestyles[scale]
|
|
|
| ax.plot(cost, score, color=color, linestyle=linestyle,
|
| label=label)
|
|
|
| if add_legend:
|
| plt.legend()
|
| if add_xlabel:
|
| plt.xlabel("Cost/loss ratio", fontsize=12)
|
| if add_ylabel:
|
| plt.ylabel("Value", fontsize=12)
|
|
|
| ax.set_xlim((0, 1))
|
| ylim = (0, max_score*1.05)
|
| ax.set_ylim(ylim)
|
| ax.tick_params(axis='both', which='major', labelsize=12)
|
| ax.set_xticks([0, 0.25, 0.5, 0.75, 1])
|
|
|
| ax.set_xticklabels(["0", "0.25", "0.5", "0.75", "1"])
|
|
|
| if out_fn is not None:
|
| fig.savefig(out_fn, bbox_inches='tight')
|
| plt.close(fig)
|
|
|