|
| 1 | +""" |
| 2 | +Eddy detection : Antartic circum polar |
| 3 | +====================================== |
| 4 | +
|
| 5 | +Script will detect eddies on adt field, and compute u,v with method add_uv(which could use, only if equator is avoid) |
| 6 | +
|
| 7 | +Two ones with filtering adt and another without |
| 8 | +
|
| 9 | +""" |
| 10 | +from datetime import datetime |
| 11 | + |
| 12 | +from matplotlib import pyplot as plt |
| 13 | + |
| 14 | +from py_eddy_tracker import data |
| 15 | +from py_eddy_tracker.dataset.grid import RegularGridDataset |
| 16 | + |
| 17 | + |
| 18 | +def quad_axes(title): |
| 19 | + fig = plt.figure(figsize=(13, 8.5)) |
| 20 | + fig.suptitle(title, weight="bold") |
| 21 | + axes = list() |
| 22 | + for position in ( |
| 23 | + [0.05, 0.53, 0.44, 0.44], |
| 24 | + [0.53, 0.53, 0.44, 0.44], |
| 25 | + [0.05, 0.03, 0.44, 0.44], |
| 26 | + [0.53, 0.03, 0.44, 0.44], |
| 27 | + ): |
| 28 | + ax = fig.add_axes(position) |
| 29 | + ax.set_xlim(5, 45), ax.set_ylim(-60, -37) |
| 30 | + ax.set_aspect("equal"), ax.grid(True) |
| 31 | + axes.append(ax) |
| 32 | + return axes |
| 33 | + |
| 34 | + |
| 35 | +# %% |
| 36 | +# Load Input grid, ADT is used to detect eddies |
| 37 | +margin = 30 |
| 38 | + |
| 39 | +kw_data = dict( |
| 40 | + filename=data.get_path("nrt_global_allsat_phy_l4_20190223_20190226.nc"), |
| 41 | + x_name="longitude", |
| 42 | + y_name="latitude", |
| 43 | + # Manual area subset |
| 44 | + indexs=dict( |
| 45 | + latitude=slice(100 - margin, 220 + margin), |
| 46 | + longitude=slice(0, 230 + margin), |
| 47 | + ), |
| 48 | +) |
| 49 | +g_raw = RegularGridDataset(**kw_data) |
| 50 | +g_raw.add_uv("adt") |
| 51 | +g = RegularGridDataset(**kw_data) |
| 52 | +g.copy("adt", "adt_low") |
| 53 | +g.bessel_high_filter("adt", 700) |
| 54 | +g.bessel_low_filter("adt_low", 700) |
| 55 | +g.add_uv("adt") |
| 56 | + |
| 57 | +# %% |
| 58 | +# Identification |
| 59 | +# ^^^^^^^^^^^^^^ |
| 60 | +# Run the identification step with slices of 2 mm |
| 61 | +date = datetime(2016, 5, 15) |
| 62 | +kw_ident = dict( |
| 63 | + date=date, step=0.002, shape_error=70, sampling=30, uname="u", vname="v" |
| 64 | +) |
| 65 | +a, c = g.eddy_identification("adt", **kw_ident) |
| 66 | +a_, c_ = g_raw.eddy_identification("adt", **kw_ident) |
| 67 | + |
| 68 | + |
| 69 | +# %% |
| 70 | +# Figures |
| 71 | +# ------- |
| 72 | +axs = quad_axes("General properties field") |
| 73 | +m = g_raw.display(axs[0], "adt", vmin=-1, vmax=1, cmap="RdBu_r") |
| 74 | +axs[0].set_title("ADT(m)") |
| 75 | +m = g.display(axs[1], "adt_low", vmin=-1, vmax=1, cmap="RdBu_r") |
| 76 | +axs[1].set_title("ADT (m) large scale with cut at 700 km") |
| 77 | +m = g.display(axs[2], "adt", vmin=-1, vmax=1, cmap="RdBu_r") |
| 78 | +axs[2].set_title("ADT (m) high scale with cut at 700 km") |
| 79 | +cb = plt.colorbar( |
| 80 | + m, cax=axs[0].figure.add_axes([0.03, 0.51, 0.94, 0.01]), orientation="horizontal" |
| 81 | +) |
| 82 | +cb.set_label("ADT(m)", labelpad=-2) |
| 83 | + |
| 84 | +# %% |
| 85 | +axs = quad_axes("") |
| 86 | +axs[0].set_title("Without filter") |
| 87 | +axs[0].set_ylabel("Contours used in eddies") |
| 88 | +axs[1].set_title("With filter") |
| 89 | +axs[2].set_ylabel("Closed contours but not used") |
| 90 | +g_raw.contours.display(axs[0], lw=0.5, only_used=True) |
| 91 | +g.contours.display(axs[1], lw=0.5, only_used=True) |
| 92 | +g_raw.contours.display(axs[2], lw=0.5, only_unused=True) |
| 93 | +g.contours.display(axs[3], lw=0.5, only_unused=True) |
| 94 | + |
| 95 | +# %% |
| 96 | +kw = dict(ref=-10, linewidth=0.75) |
| 97 | +kw_a = dict(color="r", label="Anticyclonic ({nb_obs} eddies)") |
| 98 | +kw_c = dict(color="b", label="Cyclonic ({nb_obs} eddies)") |
| 99 | +kw_filled = dict(vmin=0, vmax=100, cmap="Spectral_r", lut=20, intern=True, factor=100) |
| 100 | +axs = quad_axes("Comparison between two detection") |
| 101 | +# Match with intern/inner contour |
| 102 | +i_a, j_a, s_a = a_.match(a, intern=True, cmin=0.15) |
| 103 | +i_c, j_c, s_c = c_.match(c, intern=True, cmin=0.15) |
| 104 | + |
| 105 | +a_.index(i_a).filled(axs[0], s_a, **kw_filled) |
| 106 | +a.index(j_a).filled(axs[1], s_a, **kw_filled) |
| 107 | +c_.index(i_c).filled(axs[0], s_c, **kw_filled) |
| 108 | +m = c.index(j_c).filled(axs[1], s_c, **kw_filled) |
| 109 | + |
| 110 | +cb = plt.colorbar( |
| 111 | + m, cax=axs[0].figure.add_axes([0.03, 0.51, 0.94, 0.01]), orientation="horizontal" |
| 112 | +) |
| 113 | +cb.set_label("Similarity index", labelpad=-5) |
| 114 | +a_.display(axs[0], **kw, **kw_a), c_.display(axs[0], **kw, **kw_c) |
| 115 | +a.display(axs[1], **kw, **kw_a), c.display(axs[1], **kw, **kw_c) |
| 116 | + |
| 117 | +axs[0].set_title("Without filter") |
| 118 | +axs[0].set_ylabel("Detection") |
| 119 | +axs[1].set_title("With filter") |
| 120 | +axs[2].set_ylabel("Contours' rejection criteria") |
| 121 | + |
| 122 | +g_raw.contours.display(axs[2], lw=0.5, only_unused=True, display_criterion=True) |
| 123 | +g.contours.display(axs[3], lw=0.5, only_unused=True, display_criterion=True) |
| 124 | + |
| 125 | +for ax in axs: |
| 126 | + ax.legend() |
| 127 | +# %% |
| 128 | +# Criteria for rejecting a contour : |
| 129 | +# 0. Accepted (green) |
| 130 | +# 1. Rejection for shape error (red) |
| 131 | +# 2. Masked value within contour (blue) |
| 132 | +# 3. Under or over the pixel limit bounds (black) |
| 133 | +# 4. Amplitude criterion (yellow) |
| 134 | + |
| 135 | +# %% |
| 136 | +i_a, j_a = i_a[s_a >= 0.4], j_a[s_a >= 0.4] |
| 137 | +i_c, j_c = i_c[s_c >= 0.4], j_c[s_c >= 0.4] |
| 138 | +fig = plt.figure(figsize=(12, 12)) |
| 139 | +fig.suptitle(f"Scatter plot (A : {i_a.shape[0]}, C : {i_c.shape[0]} matches)") |
| 140 | + |
| 141 | +for i, (label, field, factor, stop) in enumerate( |
| 142 | + ( |
| 143 | + ("speed radius (km)", "radius_s", 0.001, 120), |
| 144 | + ("outter radius (km)", "radius_e", 0.001, 120), |
| 145 | + ("amplitude (cm)", "amplitude", 100, 25), |
| 146 | + ("speed max (cm/s)", "speed_average", 100, 25), |
| 147 | + ) |
| 148 | +): |
| 149 | + ax = fig.add_subplot(2, 2, i + 1, title=label) |
| 150 | + ax.set_xlabel("Without filter") |
| 151 | + ax.set_ylabel("With filter") |
| 152 | + |
| 153 | + ax.plot( |
| 154 | + a_[field][i_a] * factor, |
| 155 | + a[field][j_a] * factor, |
| 156 | + "r.", |
| 157 | + label="Anticyclonic", |
| 158 | + ) |
| 159 | + ax.plot( |
| 160 | + c_[field][i_c] * factor, |
| 161 | + c[field][j_c] * factor, |
| 162 | + "b.", |
| 163 | + label="Cyclonic", |
| 164 | + ) |
| 165 | + ax.set_aspect("equal"), ax.grid() |
| 166 | + ax.plot((0, 1000), (0, 1000), "g") |
| 167 | + ax.set_xlim(0, stop), ax.set_ylim(0, stop) |
| 168 | + ax.legend() |
0 commit comments