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]) # int labels for 0 and 1 to save space 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]) # int labels for 0 and 1 to save space 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]) # int labels for 0 and 1 to save space 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)