Skip to content

Commit 484387f

Browse files
committed
Patch when extremum are contigus
1 parent d28a12e commit 484387f

File tree

2 files changed

+79
-48
lines changed

2 files changed

+79
-48
lines changed

src/py_eddy_tracker/dataset/grid.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from scipy.ndimage import gaussian_filter, convolve
1313
from scipy.interpolate import RectBivariateSpline, interp1d
1414
from scipy.spatial import cKDTree
15-
from scipy.signal import welch, convolve2d
15+
from scipy.signal import welch
1616
from cv2 import filter2D
1717
from numba import jit
1818
from matplotlib.path import Path as BasePath
@@ -471,7 +471,6 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
471471
reset_centroid, amp = self.get_amplitude(current_contour, cvalues, data,
472472
anticyclonic_search=anticyclonic_search,
473473
level=self.contours.levels[corrected_coll_index], step=step)
474-
print(reset_centroid, amp.amplitude)
475474
# If we have a valid amplitude
476475
if (not amp.within_amplitude_limits()) or (amp.amplitude == 0):
477476
continue

src/py_eddy_tracker/eddy_feature.py

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@
2727
"""
2828

2929
import logging
30-
from numpy import ones, where, empty, array, concatenate, ma, zeros
30+
from numpy import where, empty, array, concatenate, ma, zeros, unique, round
3131
from scipy.ndimage import minimum_filter
32-
from scipy.ndimage import binary_erosion
3332
from matplotlib.figure import Figure
3433
from .tools import index_from_nearest_path, index_from_nearest_path_with_pt_in_bbox
3534

@@ -39,7 +38,7 @@ class Amplitude(object):
3938
Class to calculate *amplitude* and counts of *local maxima/minima*
4039
within a closed region of a sea level anomaly field.
4140
"""
42-
41+
EPSILON = 1e-8
4342
__slots__ = (
4443
'h_0',
4544
'grid_extract',
@@ -48,7 +47,6 @@ class Amplitude(object):
4847
'contour',
4948
'interval',
5049
'amplitude',
51-
'local_extrema_inds',
5250
'mle',
5351
)
5452

@@ -73,7 +71,6 @@ def __init__(self, contour, contour_height, data, interval):
7371
self.sla = data[contour.pixels_index]
7472
# Amplitude which will be provide
7573
self.amplitude = 0
76-
self.local_extrema_inds = None
7774
# Maximum local extrema accepted
7875
self.mle = 1
7976

@@ -98,54 +95,96 @@ def all_pixels_below_h0(self, level):
9895
Check CSS11 criterion 1: The SSH values of all of the pixels
9996
are below a given SSH threshold for cyclonic eddies.
10097
"""
101-
if (self.sla > self.h_0).any() or (hasattr(self.sla, 'mask') and self.sla.mask.any()):
98+
# In some case pixel value must be very near of contour bounds
99+
if ((self.sla - self.h_0) > self.EPSILON).any() or (hasattr(self.sla, 'mask') and self.sla.mask.any()):
102100
return False
103101
else:
104-
self._set_local_extrema(1)
105-
lmi_i, lmi_j = where(self.local_extrema_inds)
106-
m = self.mask[lmi_i, lmi_j]
107-
lmi_i, lmi_j = lmi_i[m], lmi_j[m]
108-
nb_real_extrema = ((level - self.grid_extract[lmi_i, lmi_j]) >= 2 * self.interval).sum()
109-
if nb_real_extrema > self.mle:
110-
return False
111-
amp_min = level - (2 * self.interval)
112-
index = self.grid_extract[lmi_i, lmi_j].argmin()
113-
jmin_, imin_ = lmi_j[index], lmi_i[index]
114-
if self.grid_extract[imin_, jmin_] <= amp_min:
115-
self._set_cyc_amplitude()
102+
# All local extrema index on th box
103+
lmi_i, lmi_j = self._set_local_extrema(1)
116104
slice_x, slice_y = self.contour.bbox_slice
117-
return imin_ + slice_x.start, jmin_ + slice_y.start
105+
if len(lmi_i) == 1:
106+
i, j = lmi_i[0] + slice_x.start, lmi_j[0] + slice_y.start
107+
else:
108+
# Verify if several extrema are seriously below contour
109+
nb_real_extrema = ((level - self.grid_extract[lmi_i, lmi_j]) >= 2 * self.interval).sum()
110+
if nb_real_extrema > self.mle:
111+
return False
112+
index = self.grid_extract[lmi_i, lmi_j].argmin()
113+
i, j = lmi_i[index] + slice_x.start, lmi_j[index] + slice_y.start
114+
self._set_cyc_amplitude()
115+
return i, j
118116

119117
def all_pixels_above_h0(self, level):
120118
"""
121119
Check CSS11 criterion 1: The SSH values of all of the pixels
122120
are above a given SSH threshold for anticyclonic eddies.
123121
"""
124-
if (self.sla < self.h_0).any() or (hasattr(self.sla, 'mask') and self.sla.mask.any()):
122+
# In some case pixel value must be very near of contour bounds
123+
if ((self.sla - self.h_0) < - self.EPSILON).any() or (hasattr(self.sla, 'mask') and self.sla.mask.any()):
125124
# i.e.,with self.amplitude == 0
126125
return False
127126
else:
128-
self._set_local_extrema(-1)
129-
lmi_i, lmi_j = where(self.local_extrema_inds)
130-
m = self.mask[lmi_i, lmi_j]
131-
lmi_i, lmi_j = lmi_i[m], lmi_j[m]
132-
nb_real_extrema = ((self.grid_extract[lmi_i, lmi_j] - level) >= 2 * self.interval).sum()
133-
if nb_real_extrema > self.mle:
134-
return False
135-
amp_min = level + (2 * self.interval)
136-
index = self.grid_extract[lmi_i, lmi_j].argmax()
137-
jmin_, imin_ = lmi_j[index], lmi_i[index]
138-
if self.grid_extract[imin_, jmin_] >= amp_min:
139-
self._set_cyc_amplitude()
127+
128+
# All local extrema index on th box
129+
lmi_i, lmi_j = self._set_local_extrema(-1)
140130
slice_x, slice_y = self.contour.bbox_slice
141-
return imin_ + slice_x.start, jmin_ + slice_y.start
131+
if len(lmi_i) == 1:
132+
i, j = lmi_i[0] + slice_x.start, lmi_j[0] + slice_y.start
133+
else:
134+
# Verify if several extrema are seriously above contour
135+
nb_real_extrema = ((self.grid_extract[lmi_i, lmi_j] - level) >= 2 * self.interval).sum()
136+
if nb_real_extrema > self.mle:
137+
return False
138+
index = self.grid_extract[lmi_i, lmi_j].argmax()
139+
i, j = lmi_i[index] + slice_x.start, lmi_j[index] + slice_y.start
140+
self._set_cyc_amplitude()
141+
return i, j
142142

143143
def _set_local_extrema(self, sign):
144144
"""
145145
Set count of local SLA maxima/minima within eddy
146146
"""
147-
# mask of minima
148-
self.local_extrema_inds = self.detect_local_minima(self.grid_extract * sign)
147+
# index of minima on whole grid extract
148+
i_x, i_y = self.detect_local_minima(self.grid_extract * sign)
149+
# Only index in contour
150+
m = self.mask[i_x, i_y]
151+
i_x, i_y = i_x[m], i_y[m]
152+
# Verify if some extramum is contigus
153+
nb_extrema = len(i_x)
154+
if nb_extrema > 1:
155+
# Group
156+
nb_group = 1
157+
gr = zeros(nb_extrema, dtype='u2')
158+
for i1, (i, j) in enumerate(zip(i_x[:-1], i_y[:-1])):
159+
for i2, (k, l) in enumerate(zip(i_x[i1 + 1:], i_y[i1 + 1:])):
160+
if (abs(i - k) + abs(j - l)) == 1:
161+
i2_ = i2 + i1 + 1
162+
if gr[i1] == 0 and gr[i2_] == 0:
163+
# Nobody was link with a know group
164+
gr[i1] = nb_group
165+
gr[i2_] = nb_group
166+
nb_group += 1
167+
elif gr[i1] == 0 and gr[i2_] != 0:
168+
# i2 is link not i1
169+
gr[i1] = gr[i2_]
170+
elif gr[i2_] == 0 and gr[i1] != 0:
171+
# i1 is link not i2
172+
gr[i2_] = gr[i1]
173+
else:
174+
# there already linked in two different group
175+
# we replace group from i1 with group from i2
176+
gr[gr == gr[i1]] = gr[i2_]
177+
m = gr != 0
178+
grs = unique(gr[m])
179+
# all non grouped extremum
180+
i_x_new, i_y_new = list(i_x[~m]), list(i_y[~m])
181+
for gr_ in grs:
182+
m = gr_ == gr
183+
# Choose barycentre of group
184+
i_x_new.append(round(i_x[m].mean(axis=0)).astype('i2'))
185+
i_y_new.append(round(i_y[m].mean(axis=0)).astype('i2'))
186+
return i_x_new, i_y_new
187+
return i_x, i_y
149188

150189
@staticmethod
151190
def detect_local_minima(grid):
@@ -155,20 +194,13 @@ def detect_local_minima(grid):
155194
the pixel's value is the neighborhood maximum, 0 otherwise)
156195
http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710
157196
"""
158-
# Equivalent
159-
neighborhood = ones((3, 3), dtype='bool')
197+
# To don't perturbate filter
160198
if hasattr(grid, 'mask'):
161199
grid[grid.mask] = 2e10
162200
# Get local mimima
163-
detected_minima = minimum_filter(
164-
grid, footprint=neighborhood) == grid
165-
background = (grid == 0)
166-
# Aims ?
167-
eroded_background = binary_erosion(
168-
background, structure=neighborhood, border_value=1)
169-
detected_minima ^= eroded_background
170-
# mask of minima
171-
return detected_minima
201+
detected_minima = minimum_filter(grid, size=3) == grid
202+
# index of minima
203+
return where(detected_minima)
172204

173205

174206
class Contours(object):

0 commit comments

Comments
 (0)