Skip to content

Commit 6ac157d

Browse files
author
adelepoulle
committed
Draft
1 parent 5671610 commit 6ac157d

File tree

2 files changed

+208
-114
lines changed

2 files changed

+208
-114
lines changed

src/py_eddy_tracker/dataset/grid.py

Lines changed: 110 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@
1313
from scipy.spatial import cKDTree
1414
from matplotlib.figure import Figure
1515
from matplotlib.path import Path as BasePath
16+
from matplotlib.contour import QuadContourSet as BaseQuadContourSet
1617
from pyproj import Proj
1718
from ..tools import fit_circle_c, distance_vector
1819
from ..observations import EddiesObservations
19-
from ..eddy_feature import Amplitude
20+
from ..eddy_feature import Amplitude, get_uavg
2021

2122

23+
def contour_iter(self, anticyclonic_search):
24+
for coll in self.collections[::1 if anticyclonic_search else -1]:
25+
yield coll
26+
27+
BaseQuadContourSet.iter_ = contour_iter
28+
2229
@property
2330
def isvalid(self):
2431
return False not in (self.vertices[0] == self.vertices[-1]
@@ -51,12 +58,34 @@ def lat(self):
5158
BasePath.lat = lat
5259

5360

54-
# def nearest_grd_indice(self, lon_value, lat_value, grid):
55-
# if not hasattr(self, '_grid_indices'):
56-
# self._grid_indices = nearest(lon_value, lat_value,
57-
# grid.lon[0], grid.lat[:, 0])
58-
# return self._grid_indices
59-
# BasePath.nearest_grd_indice = nearest_grd_indice
61+
def uniform_resample(x_val, y_val, num_fac=2, fixed_size=None):
62+
"""
63+
Resample contours to have (nearly) equal spacing
64+
x_val, y_val : input contour coordinates
65+
num_fac : factor to increase lengths of output coordinates
66+
"""
67+
# Get distances
68+
dist = empty(x_val.shape)
69+
dist[0] = 0
70+
distance_vector(
71+
x_val[:-1], y_val[:-1], x_val[1:], y_val[1:], dist[1:])
72+
dist.cumsum(out=dist)
73+
# Get uniform distances
74+
if fixed_size is None:
75+
fixed_size = dist.size * num_fac
76+
d_uniform = linspace(0, dist[-1], num=fixed_size)
77+
x_new = interp(d_uniform, dist, x_val)
78+
y_new = interp(d_uniform, dist, y_val)
79+
return x_new, y_new
80+
81+
82+
@property
83+
def regular_coordinates(self):
84+
"""Give a standard/regular/double sample of contour
85+
"""
86+
if hasattr(self, '_regular_coordinates'):
87+
self._regular_coordinates = uniform_resample(self.lon, self.lat)
88+
return self._regular_coordinates
6089

6190

6291
def fit_circle_path(self):
@@ -93,25 +122,21 @@ def _fit_circle_path(self):
93122
BasePath._fit_circle_path = _fit_circle_path
94123

95124

96-
def uniform_resample(x_val, y_val, num_fac=2, fixed_size=None):
97-
"""
98-
Resample contours to have (nearly) equal spacing
99-
x_val, y_val : input contour coordinates
100-
num_fac : factor to increase lengths of output coordinates
101-
"""
102-
# Get distances
103-
dist = empty(x_val.shape)
104-
dist[0] = 0
105-
distance_vector(
106-
x_val[:-1], y_val[:-1], x_val[1:], y_val[1:], dist[1:])
107-
dist.cumsum(out=dist)
108-
# Get uniform distances
109-
if fixed_size is None:
110-
fixed_size = dist.size * num_fac
111-
d_uniform = linspace(0, dist[-1], num=fixed_size)
112-
x_new = interp(d_uniform, dist, x_val)
113-
y_new = interp(d_uniform, dist, y_val)
114-
return x_new, y_new
125+
def pixels_in(self, grid):
126+
if not hasattr(self, '_pixels_in'):
127+
self._pixels_in = grid.get_pixels_in(self)
128+
return self._pixels_in
129+
130+
131+
@property
132+
def nb_pixel(self):
133+
if not hasattr(self, '_pixels_in'):
134+
raise Exception('No pixels_in call before!')
135+
return self._pixels_in[0].shape[0]
136+
137+
138+
BasePath.pixels_in = pixels_in
139+
BasePath.nb_pixel = nb_pixel
115140

116141

117142
class GridDataset(object):
@@ -138,6 +163,7 @@ class GridDataset(object):
138163
'global_attrs',
139164
'vars',
140165
'interpolators',
166+
'speed_coef',
141167
)
142168

143169
GRAVITY = 9.807
@@ -179,6 +205,7 @@ def is_centered(self):
179205
def load_general_features(self):
180206
"""Load attrs
181207
"""
208+
logging.debug('Load general feature from %(filename)s', dict(filename=self.filename))
182209
with Dataset(self.filename) as h:
183210
# Load generals
184211
self.dimensions = {i: len(v) for i, v in h.dimensions.items()}
@@ -286,6 +313,7 @@ def grid(self, varname):
286313
if varname not in self.vars:
287314
coordinates_dims = list(self.x_dim)
288315
coordinates_dims.extend(list(self.y_dim))
316+
logging.debug('Load %(varname)s from %(filename)s', dict(varname=varname, filename=self.filename))
289317
with Dataset(self.filename) as h:
290318
dims = h.variables[varname].dimensions
291319
sl = [slice(None) if dim in coordinates_dims else 0 for dim in dims]
@@ -316,17 +344,18 @@ def bounds(self):
316344
"""
317345
return self.x_bounds.min(), self.x_bounds.max(), self.y_bounds.min(), self.y_bounds.max()
318346

319-
def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_variables=None,
347+
def eddy_identification(self, grid_height, step=0.005, shape_error=55, extra_variables=None,
320348
extra_array_variables=None, array_sampling=50, pixel_limit=None):
321349
if extra_variables is None:
322350
extra_variables = list()
323351
if extra_array_variables is None:
324352
extra_array_variables = list()
325353
if pixel_limit is None:
326354
pixel_limit = (8, 1000)
327-
fig = Figure()
328-
ax = fig.add_subplot(111)
329-
data = self.grid(grid_name)
355+
356+
self.init_speed_coef()
357+
358+
data = self.grid(grid_height)
330359
z_min, z_max = data.min(), data.max()
331360

332361
levels = arange(z_min - z_min % step, z_max - z_max % step + 2 * step, step)
@@ -336,7 +365,11 @@ def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_varia
336365
if len(x.shape) == 1:
337366
data = data.T
338367
##
368+
logging.debug('Start computing iso lines')
369+
fig = Figure()
370+
ax = fig.add_subplot(111)
339371
contours = ax.contour(x, y, data, levels)
372+
logging.debug('Finish computing iso lines')
340373

341374
anticyclonic_search = True
342375
iterator = 1 if anticyclonic_search else -1
@@ -353,7 +386,7 @@ def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_varia
353386
if nb_paths == 0:
354387
continue
355388
cvalues = contours.cvalues[corrected_coll_index]
356-
logging.debug('doing collection %s, contour value %s, %d paths',
389+
logging.debug('doing collection %s, contour value %.4f, %d paths',
357390
corrected_coll_index, cvalues, nb_paths)
358391

359392
# Loop over individual c_s contours (i.e., every eddy in field)
@@ -377,57 +410,36 @@ def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_varia
377410
if anticyclonic_search != acyc_not_cyc:
378411
continue
379412

380-
i_x_in, i_y_in = self.get_pixels_in(cont)
381-
nb_pixel = i_x_in.shape[0]
413+
i_x_in, i_y_in = cont.pixels_in(self)
382414

383415
# Maybe limit max must be replace with a maximum of surface
384-
if nb_pixel < pixel_limit[0] or nb_pixel > pixel_limit[1]:
416+
if cont.nb_pixel < pixel_limit[0] or cont.nb_pixel > pixel_limit[1]:
385417
continue
386418

387-
# Resample the contour points for a more even
388-
# circumferential distribution
389-
contlon_e, contlat_e = uniform_resample(cont.lon, cont.lat)
390-
391-
amp = self.get_amplitude(contlon_e, contlat_e, i_x_in, i_y_in, cvalues, data,
392-
anticyclonic_search=anticyclonic_search)
419+
reset_centroid, amp = self.get_amplitude(i_x_in, i_y_in, cvalues, data,
420+
anticyclonic_search=anticyclonic_search, level=contours.levels[corrected_coll_index], step=step)
393421

394-
print(amp.within_amplitude_limits(), amp.amplitude)
422+
# If we have a valid amplitude
395423
if (not amp.within_amplitude_limits()) or (amp.amplitude == 0):
396424
continue
397425

398-
eddy.reshape_mask_eff(grd)
399-
# Instantiate Amplitude object
400-
amp = Amplitude(contlon_e, contlat_e, eddy, grd)
401-
402-
if anticyclonic_search:
403-
reset_centroid = amp.all_pixels_above_h0(
404-
contours.levels[corrected_coll_index])
405-
406-
else:
407-
reset_centroid = amp.all_pixels_below_h0(
408-
contours.levels[corrected_coll_index])
409-
410-
if not amp.within_amplitude_limits() or amp.amplitude:
411-
continue
412-
413426
if reset_centroid:
414427
centi = reset_centroid[0]
415428
centj = reset_centroid[1]
416-
centlon_e = grd.lon[centj, centi]
417-
centlat_e = grd.lat[centj, centi]
429+
centlon_e = x[centj, centi]
430+
centlat_e = y[centj, centi]
418431

419432
# Get sum of eke within Ceff
420-
teke = grd.eke[eddy.slice_j, eddy.slice_i][eddy.mask_eff].sum()
433+
#~ teke = grd.eke[eddy.slice_j, eddy.slice_i][eddy.mask_eff].sum()
421434

422-
(uavg, contlon_s, contlat_s, inner_contlon, inner_contlat,
423-
any_inner_contours
424-
) = get_uavg(eddy, contours, centlon_e, centlat_e, cont, grd,
425-
anticyclonic_search)
435+
#~ (uavg, contlon_s, contlat_s, inner_contlon, inner_contlat, any_inner_contours
436+
#~ ) = get_uavg(eddy, contours, centlon_e, centlat_e, cont, grd, anticyclonic_search)
437+
(uavg, contlon_s, contlat_s, inner_contlon, inner_contlat, any_inner_contours
438+
) = get_uavg(self, contours, centlon_e, centlat_e, cont, anticyclonic_search)
426439

427440
# Use azimuth equal projection for radius
428441
proj = Proj('+proj=aeqd +ellps=WGS84 +lat_0=%s +lon_0=%s'
429-
% (inner_contlat.mean(),
430-
inner_contlon.mean()))
442+
% (inner_contlat.mean(), inner_contlon.mean()))
431443

432444
# First, get position based on innermost
433445
# contour
@@ -455,7 +467,7 @@ def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_varia
455467
properties.obs['radius_s'] = eddy_radius_s / 1000
456468
properties.obs['speed_radius'] = uavg
457469
properties.obs['radius_e'] = eddy_radius_e / 1000
458-
properties.obs['eke'] = teke
470+
#~ properties.obs['eke'] = teke
459471
if 'shape_error_e' in eddy.track_extra_variables:
460472
properties.obs['shape_error_e'] = aerr
461473
if 'shape_error_s' in eddy.track_extra_variables:
@@ -505,33 +517,36 @@ def _gaussian_filter(data, sigma, mode='reflect'):
505517
return ma.array(v / w, mask=w == 0)
506518

507519
@staticmethod
508-
def get_amplitude(cont_x, cont_y, i_x_in, i_y_in, contour_height, data, anticyclonic_search=True):
520+
def get_amplitude(i_x_in, i_y_in, contour_height, data, anticyclonic_search=True, level=None, step=None):
509521
# Instantiate Amplitude object
510522
amp = Amplitude(
511-
contour_x=cont_x,
512-
contour_y=cont_y,
523+
# Indices of all pixels in contour
513524
i_contour_x=i_x_in,
514525
i_contour_y=i_y_in,
526+
# Height of level
515527
contour_height=contour_height,
516-
data=data)
528+
# All grid
529+
data=data,
530+
# Step by level
531+
interval=step)
517532

518533
if anticyclonic_search:
519-
reset_centroid = amp.all_pixels_above_h0()
534+
reset_centroid = amp.all_pixels_above_h0(level)
520535

521536
else:
522-
reset_centroid = amp.all_pixels_below_h0(
523-
contours.levels[corrected_coll_index])
537+
reset_centroid = amp.all_pixels_below_h0(level)
524538

525-
return amp
526-
# if not amp.within_amplitude_limits() or amp.amplitude:
527-
# continue
539+
return reset_centroid, amp
528540

529541

530542
class UnRegularGridDataset(GridDataset):
531543
"""Class which manage unregular grid
532544
"""
533545

534-
__slots__ = 'index_interp'
546+
__slots__ = (
547+
'index_interp',
548+
'_speed_norm',
549+
)
535550

536551
def bbox_indice(self, vertices):
537552
dist, idx = self.index_interp.query(vertices, k=1)
@@ -568,11 +583,13 @@ def compute_pixel_path(self, x0, y0, x1, y1):
568583
pass
569584

570585
def init_pos_interpolator(self):
586+
logging.debug('Create a KdTree could be long ...')
571587
self.index_interp = cKDTree(
572588
column_stack((
573589
self.x_c.reshape(-1),
574590
self.y_c.reshape(-1)
575591
)))
592+
logging.debug('... OK')
576593

577594
def _low_filter(self, grid_name, x_cut, y_cut, factor=40.):
578595
data = self.grid(grid_name)
@@ -614,6 +631,16 @@ def _low_filter(self, grid_name, x_cut, y_cut, factor=40.):
614631
z_interp = RectBivariateSpline(x_center, y_center, z_filtered, **opts_interpolation).ev(x, y)
615632
return ma.array(z_interp, mask=m_interp.ev(x, y) > 0.00001)
616633

634+
def speed_coef(self, contour):
635+
dist, idx = self.index_interp.query(contour.regular_coordinates[1:], k=4)
636+
i_y = idx % self.x_c.shape[1]
637+
i_x = int_((idx - i_y) / self.x_c.shape[1])
638+
# A simplified solution to be change by a weight mean
639+
return self._speed_norm[i_x, i_y].mean(axis=1)
640+
641+
def init_speed_coef(self, uname='u', vname='v'):
642+
self._speed_norm = (self.grid(uname) ** 2 + self.grid(vname) ** 2) ** .5
643+
617644

618645
class RegularGridDataset(GridDataset):
619646
"""Class only for regular grid
@@ -782,3 +809,9 @@ def add_uv(self, grid_height):
782809
d_x_degrees = (d_x_degrees + 180) % 360 - 180
783810
d_x = self.EARTH_RADIUS * 2 * pi / 360 * d_x_degrees * cos(deg2rad(self.y_c))
784811
self.vars['v'] = d_hx / d_x * gof
812+
813+
def init_speed_coef(self, uname='u', vname='v'):
814+
"""Draft
815+
"""
816+
uspd = (self.grid(uname) ** 2 + self.grid(vname) ** 2) ** .5
817+
self.speed_coef = RectBivariateSpline(self.xc, self.yc, uspd, kx=1, ky=1).ev

0 commit comments

Comments
 (0)