Skip to content

Commit 5bf63ce

Browse files
committed
Speed up filter and correct step of grid
1 parent 810049f commit 5bf63ce

File tree

8 files changed

+66
-16
lines changed

8 files changed

+66
-16
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ h.write('/tmp/grid.nc')
6868

6969
Add second plot
7070
```python
71-
ax = fig.add_axes([.03, .02, .9, .45])
71+
ax = fig.add_axes([.02, .02, .9, .45])
7272
ax.set_title('ADT Filtered (m)')
7373
ax.set_aspect('equal')
7474
ax.set_ylim(-75, 75)
@@ -163,13 +163,13 @@ a, c = h.eddy_identification(
163163

164164
Plot identification
165165
```python
166-
fig = plt.figure(figsize=(14,8))
166+
fig = plt.figure(figsize=(14,7))
167167
ax = fig.add_axes([.03,.03,.94,.94])
168168
ax.set_title('Eddies detected -- Cyclonic(red) and Anticyclonic(blue)')
169169
ax.set_ylim(-75,75)
170+
ax.set_aspect('equal')
170171
ax.plot(a.obs['contour_lon_s'].T, a.obs['contour_lat_s'].T, 'b', linewidth=.5)
171172
ax.plot(c.obs['contour_lon_s'].T, c.obs['contour_lat_s'].T, 'r', linewidth=.5)
172-
ax.legend()
173173
ax.grid()
174174
fig.savefig('share/png/eddies.png')
175175
```

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
'matplotlib>=2.0.0',
4343
'scipy>=0.15.1',
4444
'netCDF4>=1.1.0',
45+
'opencv-python',
4546
'shapely',
4647
'pyyaml',
4748
'pyproj',

share/png/eddies.png

-136 KB
Loading

share/png/filter.png

-395 Bytes
Loading

share/png/spectrum.png

51 Bytes
Loading

share/png/spectrum_ratio.png

941 Bytes
Loading

src/py_eddy_tracker/dataset/grid.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
from numpy import concatenate, int32, empty, maximum, where, array, \
66
sin, deg2rad, pi, ones, cos, ma, int8, histogram2d, arange, float_, \
77
linspace, errstate, int_, column_stack, interp, meshgrid, nan, ceil, sinc, float64, isnan, \
8-
floor
8+
floor, percentile
99
from datetime import datetime
1010
from scipy.special import j1
1111
from netCDF4 import Dataset
1212
from scipy.ndimage import gaussian_filter, convolve
1313
from scipy.interpolate import RectBivariateSpline, interp1d
1414
from scipy.spatial import cKDTree
1515
from scipy.signal import welch, convolve2d
16+
from cv2 import filter2D
1617
from numba import jit
1718
from matplotlib.path import Path as BasePath
1819
from matplotlib.contour import QuadContourSet as BaseQuadContourSet
@@ -396,6 +397,16 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
396397

397398
# Compute levels for ssh
398399
z_min, z_max = data.min(), data.max()
400+
d_z = z_max -z_min
401+
data_tmp = data[~data.mask]
402+
epsilon = 0.001 # in %
403+
z_min_p, z_max_p = percentile(data_tmp, epsilon), percentile(data_tmp,100-epsilon)
404+
d_zp = z_max_p - z_min_p
405+
if d_z / d_zp > 2:
406+
logging.warning('Maybe some extrema are present zmin %f (m) and zmax %f (m) will be replace by %f and %f',
407+
z_min, z_max, z_min_p, z_max_p)
408+
z_min, z_max = z_min_p, z_max_p
409+
399410
levels = arange(z_min - z_min % step, z_max - z_max % step + 2 * step, step)
400411

401412
# Get x and y values
@@ -532,6 +543,12 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
532543
eddies_collection.sign_type = 1 if anticyclonic_search else -1
533544
eddies_collection.obs['time'] = (date - datetime(1950, 1, 1)).total_seconds() / 86400.
534545

546+
# normalization longitude between 0 - 360, because storage have an offset on 180
547+
eddies_collection.obs['lon'] %= 360
548+
ref = eddies_collection.obs['lon'] - 180
549+
eddies_collection.obs['contour_lon_e'] = ((eddies_collection.obs['contour_lon_e'].T - ref) % 360 + ref).T
550+
eddies_collection.obs['contour_lon_s'] = ((eddies_collection.obs['contour_lon_s'].T - ref) % 360 + ref).T
551+
535552
a_and_c.append(eddies_collection)
536553
h_units = self.units(grid_height)
537554
units = UnitRegistry()
@@ -733,12 +750,16 @@ class RegularGridDataset(GridDataset):
733750
'_speed_ev',
734751
'_is_circular',
735752
'x_size',
753+
'_x_step',
754+
'_y_step',
736755
)
737756

738757
def __init__(self, *args, **kwargs):
739758
super(RegularGridDataset, self).__init__(*args, **kwargs)
740759
self._is_circular = None
741760
self.x_size = self.x_c.shape[0]
761+
self._x_step = (self.x_c[1:] - self.x_c[:-1]).mean()
762+
self._y_step = (self.y_c[1:] - self.y_c[:-1]).mean()
742763

743764
def init_pos_interpolator(self):
744765
"""Create function to have a quick index interpolator
@@ -777,20 +798,20 @@ def normalize_x_indice(self, indices):
777798
return indices % self.x_size
778799

779800
def nearest_grd_indice(self, x, y):
780-
return int32(floor(((x - self.x_c[0] + self.xstep) % 360) // self.xstep)), \
781-
int32(floor(((y - self.y_c[0] + self.ystep) % 360) // self.ystep))
801+
return int32(((x - self.x_bounds[0]) % 360) // self.xstep), \
802+
int32(((y - self.y_bounds[0]) % 360) // self.ystep)
782803

783804
@property
784805
def xstep(self):
785806
"""Only for regular grid with no step variation
786807
"""
787-
return self.x_c[1] - self.x_c[0]
808+
return self._x_step
788809

789810
@property
790811
def ystep(self):
791812
"""Only for regular grid with no step variation
792813
"""
793-
return self.y_c[1] - self.y_c[0]
814+
return self._y_step
794815

795816
def compute_pixel_path(self, x0, y0, x1, y1):
796817
"""Give a series of index which describe the path between to position
@@ -949,7 +970,7 @@ def convolve_filter_with_dynamic_kernel(self, grid_name, kernel_func, lat_max=85
949970
data_out = ma.zeros(data.shape)
950971
data_out.mask = ones(data_out.shape, dtype=bool)
951972
for i, lat in enumerate(self.y_c):
952-
if abs(lat) > lat_max:
973+
if abs(lat) > lat_max or data[:, i].mask.all():
953974
data_out.mask[:, i] = True
954975
continue
955976
# Get kernel
@@ -975,9 +996,10 @@ def convolve_filter_with_dynamic_kernel(self, grid_name, kernel_func, lat_max=85
975996
# Convolution
976997
m = ~tmp_matrix.mask
977998
tmp_matrix[~m] = 0
978-
values_sum = convolve2d(tmp_matrix, kernel, mode='valid')[:,0]
979999

980-
kernel_sum = convolve2d((m).astype(float), kernel, mode='valid')[:,0]
1000+
demi_x, demi_y = k_shape[0] // 2, k_shape[1] // 2
1001+
values_sum = filter2D(tmp_matrix, -1, kernel)[demi_x:-demi_x, demi_y]
1002+
kernel_sum = filter2D(m.astype(float), -1, kernel)[demi_x:-demi_x, demi_y]
9811003
with errstate(invalid='ignore'):
9821004
data_out[:, i] = values_sum / kernel_sum
9831005
data_out = ma.array(data_out, mask=data.mask + data_out.mask)
@@ -1018,8 +1040,11 @@ def bessel_band_filter(self, grid_name, wave_length_inf, wave_length_sup, **kwar
10181040
self.vars[grid_name] -= data_out
10191041

10201042
def bessel_high_filter(self, grid_name, wave_length, order=1, lat_max=85):
1043+
logging.debug('Run filtering with wave of %(wave_length)s km and order of %(order)s ...',
1044+
dict(wave_length=wave_length, order=order))
10211045
data_out = self.convolve_filter_with_dynamic_kernel(
10221046
grid_name, self.kernel_bessel, lat_max=lat_max, wave_length=wave_length, order=order)
1047+
logging.debug('Filtering done')
10231048
self.vars[grid_name] -= data_out
10241049

10251050
def bessel_low_filter(self, grid_name, wave_length, order=1, lat_max=85):
@@ -1155,19 +1180,21 @@ def interp(self, grid_name, lons, lats):
11551180
new z
11561181
"""
11571182
z = empty(lons.shape, dtype='f4').reshape(-1)
1183+
g = self.grid(grid_name).astype('f4')
11581184
interp_numba(
11591185
self.x_c.astype('f4'),
11601186
self.y_c.astype('f4'),
1161-
self.grid(grid_name).astype('f4'),
1187+
g,
11621188
lons.reshape(-1).astype('f4'),
11631189
lats.reshape(-1).astype('f4'),
11641190
z,
1191+
g.fill_value
11651192
)
11661193
return z
11671194

11681195

1169-
@jit("void(f4[:], f4[:], f4[:,:], f4[:], f4[:], f4[:])", nopython=True)
1170-
def interp_numba(x_g, y_g, z, x, y, dest_z):
1196+
@jit("void(f4[:], f4[:], f4[:,:], f4[:], f4[:], f4[:], f4)", nopython=True)
1197+
def interp_numba(x_g, y_g, z, x, y, dest_z, fill_value):
11711198
x_ref = x_g[0]
11721199
y_ref = y_g[0]
11731200
x_step = x_g[1] - x_ref
@@ -1181,5 +1208,18 @@ def interp_numba(x_g, y_g, z, x, y, dest_z):
11811208
j1 = j0 + 1
11821209
xd = (x_ - i0)
11831210
yd = (y_ - j0)
1184-
dest_z[i] = (z[i0, j0] * (1 - xd) + (z[i1, j0] * xd)) * (1 - yd) + (z[i0, j1] * (1 - xd) + z[i1, j1] * xd) * yd
1211+
z00 = z[i0, j0]
1212+
z01 = z[i0, j1]
1213+
z10 = z[i1, j0]
1214+
z11 = z[i1, j1]
1215+
if z00 == fill_value:
1216+
dest_z[i] = nan
1217+
elif z01 == fill_value:
1218+
dest_z[i] = nan
1219+
elif z10 == fill_value:
1220+
dest_z[i] = nan
1221+
elif z11 == fill_value:
1222+
dest_z[i] = nan
1223+
else:
1224+
dest_z[i] = (z00 * (1 - xd) + (z10 * xd)) * (1 - yd) + (z01 * (1 - xd) + z11 * xd) * yd
11851225

src/py_eddy_tracker/eddy_feature.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def find_wrapcut_path_and_join(self, x0, x1):
261261
collection._paths = paths_out
262262
logging.info('%d contours close over the bounds', poly_solve)
263263

264-
def __init__(self, x, y, z, levels, bbox_surface_min_degree, wrap_x=False):
264+
def __init__(self, x, y, z, levels, bbox_surface_min_degree, wrap_x=False, keep_unclose=False):
265265
"""
266266
c_i : index to contours
267267
l_i : index to levels
@@ -276,6 +276,7 @@ def __init__(self, x, y, z, levels, bbox_surface_min_degree, wrap_x=False):
276276
logging.debug('X shape : %s', x.shape)
277277
logging.debug('Y shape : %s', y.shape)
278278
logging.debug('Z shape : %s', z.shape)
279+
logging.info('Start computing iso lines with %d levels from %f to %f ...', len(levels), levels[0], levels[-1])
279280
self.contours = ax.contour(x, y, z.T, levels)
280281
if wrap_x:
281282
self.find_wrapcut_path_and_join(x[0], x[-1])
@@ -305,6 +306,9 @@ def __init__(self, x, y, z, levels, bbox_surface_min_degree, wrap_x=False):
305306
square_root = bbox_surface_min_degree ** .5
306307
if d_x <= square_root or d_y <= square_root:
307308
continue
309+
if keep_unclose:
310+
keep_path.append(contour)
311+
continue
308312
# Remove unclosed path
309313
d_closed = ((contour.vertices[0, 0] - contour.vertices[-1, 0]) **2 + (contour.vertices[0, 1] - contour.vertices[-1, 1]) ** 2) ** .5
310314
if d_closed > self.DELTA_SUP:
@@ -405,4 +409,9 @@ def get_index_nearest_path_bbox_contain_pt(self, level, xpt, ypt):
405409
else:
406410
return self.contours.collections[level]._paths[index]
407411

412+
def display(self, ax, **kwargs):
413+
for collection in self.contours.collections:
414+
for path in collection.get_paths():
415+
x, y= path.vertices[:, 0], path.vertices[:, 1]
416+
ax.plot(x, y, **kwargs)
408417

0 commit comments

Comments
 (0)