Skip to content

Commit 6309045

Browse files
committed
Cut tracking in different part, to compute tracking and merging in two step
1 parent 24bafd0 commit 6309045

File tree

10 files changed

+369
-56
lines changed

10 files changed

+369
-56
lines changed

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
scripts=[
1919
'src/scripts/EddyIdentification',
2020
'src/scripts/EddyTracking',
21-
'src/scripts/EddyTrackingFull'
21+
'src/scripts/EddyFinalTracking',
22+
'src/scripts/EddyMergeCorrespondances',
23+
# 'src/scripts/EddyTrackingFull'
2224
],
2325
zip_safe=False,
2426
cmdclass={

src/py_eddy_tracker/__init__.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,10 @@ def parse_args(self, *args, **kwargs):
199199
),
200200
radius_e=dict(
201201
attr_name='radius_e',
202-
scale_factor=1e-3,
203202
nc_name='radius_e',
204203
nc_type='float32',
204+
output_type='u2',
205+
scale_factor=0.05,
205206
nc_dims=('Nobs',),
206207
nc_attr=dict(
207208
long_name='effective radius scale',
@@ -211,9 +212,10 @@ def parse_args(self, *args, **kwargs):
211212
),
212213
radius_s=dict(
213214
attr_name='radius_s',
214-
#~ scale_factor=1e-3,
215215
nc_name='L',
216216
nc_type='float32',
217+
output_type='u2',
218+
scale_factor=0.05,
217219
nc_dims=('Nobs',),
218220
nc_attr=dict(
219221
long_name='speed radius scale',
@@ -243,11 +245,9 @@ def parse_args(self, *args, **kwargs):
243245
description='observation sequence number (XX day intervals)',
244246
)
245247
),
246-
# contour_lon_e=dict(
247-
contour_lon=dict(
248+
contour_lon_e=dict(
248249
attr_name=None,
249-
# nc_name='contour_lon_e',
250-
nc_name='contour_lon',
250+
nc_name='contour_lon_e',
251251
nc_type='f4',
252252
output_type='i2',
253253
scale_factor=0.01,
@@ -258,11 +258,9 @@ def parse_args(self, *args, **kwargs):
258258
description='lons of contour points',
259259
)
260260
),
261-
# contour_lat_e=dict(
262-
contour_lat=dict(
261+
contour_lat_e=dict(
263262
attr_name=None,
264-
# nc_name='contour_lat_e',
265-
nc_name='contour_lat',
263+
nc_name='contour_lat_e',
266264
nc_type='f4',
267265
output_type='i2',
268266
scale_factor=0.01,
@@ -308,10 +306,11 @@ def parse_args(self, *args, **kwargs):
308306
'smallest inner contour (pixel == 1)',
309307
)
310308
),
311-
shape_error=dict(
309+
shape_error_e=dict(
312310
attr_name=None,
313-
nc_name='shape_error',
314-
nc_type='u1',
311+
nc_name='shape_error_e',
312+
nc_type='f2',
313+
output_type='u1',
315314
scale_factor=0.5,
316315
nc_dims=('Nobs',),
317316
nc_attr=dict(
@@ -321,7 +320,8 @@ def parse_args(self, *args, **kwargs):
321320
shape_error_s=dict(
322321
attr_name=None,
323322
nc_name='shape_error_s',
324-
nc_type='u1',
323+
nc_type='f2',
324+
output_type='u1',
325325
scale_factor=0.5,
326326
nc_dims=('Nobs',),
327327
nc_attr=dict(

src/py_eddy_tracker/grid/aviso.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ def __init__(self, aviso_file, the_domain,
7676
# zero_crossing, used for handling a longitude range that
7777
# crosses zero degree meridian
7878
if lonmin < 0 and lonmax >= 0 and 'MedSea' not in self.domain:
79-
self.zero_crossing = True
79+
if ((self.lonmax < self._lon.max()) and (self.lonmax > self._lon.min()) and (self.lonmin < self._lon.max()) and (self.lonmin > self._lon.min())):
80+
pass
81+
else:
82+
self.zero_crossing = True
8083

8184
self.sla_coeffs = None
8285
self.uspd_coeffs = None

src/py_eddy_tracker/observations.py

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
3030
"""
3131
from numpy import zeros, empty, nan, arange, interp, where, unique, \
32-
ma, concatenate, cos, radians, isnan
32+
ma, concatenate, cos, radians, isnan, ones, ndarray
3333
from netCDF4 import Dataset
3434
from py_eddy_tracker.tools import distance_matrix, distance_vector
3535
from shapely.geometry import Polygon
@@ -247,20 +247,21 @@ def load_from_netcdf(filename):
247247
eddies.sign_type = h_nc.variables['cyc'][0]
248248
return eddies
249249

250-
def cost_function2(self, records_in, records_out, distance):
250+
@staticmethod
251+
def cost_function2(records_in, records_out, distance):
251252
nb_records = records_in.shape[0]
252253
costs = ma.empty(nb_records,dtype='f4')
253254
for i_record in xrange(nb_records):
254255
poly_in = Polygon(
255256
concatenate((
256-
(records_in[i_record]['contour_lon'],),
257-
(records_in[i_record]['contour_lat'],))
257+
(records_in[i_record]['contour_lon_e'],),
258+
(records_in[i_record]['contour_lat_e'],))
258259
).T
259260
)
260261
poly_out = Polygon(
261262
concatenate((
262-
(records_out[i_record]['contour_lon'],),
263-
(records_out[i_record]['contour_lat'],))
263+
(records_out[i_record]['contour_lon_e'],),
264+
(records_out[i_record]['contour_lat_e'],))
264265
).T
265266
)
266267
try:
@@ -269,8 +270,36 @@ def cost_function2(self, records_in, records_out, distance):
269270
costs[i_record] = 1
270271
costs.mask = costs == 1
271272
return costs
273+
274+
@staticmethod
275+
def across_ground(record0, record1, distance):
276+
from netCDF4 import Dataset
277+
from scipy.interpolate import RectBivariateSpline
278+
opts_interpolation = {'kx' : 1, 'ky' : 1, 's' : 0}
279+
280+
mask = empty(record0.shape, dtype='bool')
281+
282+
with Dataset('/data/adelepoulle/Test/Test_eddy/20161102_mask_from_bathy/mask.nc') as h:
283+
function = RectBivariateSpline(h.variables['x'][:], h.variables['y'][:], h.variables['coast_dist'][:], **opts_interpolation)
272284

273-
def cost_function(self, records_in, records_out, distance):
285+
for i, record_in in enumerate(record0):
286+
if distance[i] < 30:
287+
mask[i] = False
288+
continue
289+
nb_point = int(distance[i] / 10)
290+
dx, dy = (record1[i]['lon'] - record_in['lon']) / nb_point, (record1[i]['lat'] - record_in['lat']) / nb_point
291+
nb_point += 1
292+
lon_traj, lat_traj = arange(nb_point) * dx + record_in['lon'], arange(nb_point) * dy + record_in['lat']
293+
294+
mask[i] = (function.ev(lon_traj, lat_traj) < (record_in['radius_e'])).any()
295+
return mask
296+
297+
@staticmethod
298+
def cost_function(records_in, records_out, distance):
299+
m = EddiesObservations.across_ground(records_in, records_out, distance)
300+
# print m.sum()
301+
# print m.shape
302+
# exit()
274303
cost = ((records_in['amplitude'] - records_out['amplitude']
275304
) / records_in['amplitude']
276305
) ** 2
@@ -280,26 +309,30 @@ def cost_function(self, records_in, records_out, distance):
280309
cost += (distance / 125) ** 2
281310
cost **= 0.5
282311
# Mask value superior at 60 % of variation
283-
# return ma.array(cost, mask=cost > 0.6)
312+
return ma.array(cost, mask=m)
284313
return cost
285314

286315
def circle_mask(self, other, radius=100):
287316
"""Return a mask of available link"""
288317
return self.distance(other) < radius
289318

290319
def fixed_ellipsoid_mask(self, other, minor=50, major=100, only_east=False):
291-
dist = self.distance(other)
320+
dist = self.distance(other).T
292321
accepted = dist < minor
293322
rejected = dist > major
294323
rejected += isnan(dist)
295324

296325
# All obs we are not in rejected and accepted, there are between
297326
# two circle
298327
needs_investigation = - (rejected + accepted)
299-
index_self, index_other = where(needs_investigation)
328+
index_other, index_self = where(needs_investigation)
300329

301330
nb_case = index_self.shape[0]
302331
if nb_case != 0:
332+
if isinstance(major, ndarray):
333+
major = major[index_self]
334+
if isinstance(minor, ndarray):
335+
minor = minor[index_self]
303336
c_degree = ((major ** 2 - minor ** 2) ** .5) / (111.3 * cos(radians(self.obs['lat'][index_self])))
304337

305338
lon_self = self.obs['lon'][index_self]
@@ -318,20 +351,40 @@ def fixed_ellipsoid_mask(self, other, minor=50, major=100, only_east=False):
318351
dist_right_f)
319352
dist_2a = (dist_left_f + dist_right_f) / 1000
320353

321-
accepted[index_self, index_other] = dist_2a < (2 * major)
354+
accepted[index_other, index_self] = dist_2a < (2 * major)
322355
if only_east:
323356
d_lon = (other.obs['lon'][index_other] - lon_self + 180) % 360 - 180
324357
mask = d_lon < 0
325-
accepted[index_self[mask], index_other[mask]] = False
326-
return accepted
358+
accepted[index_other[mask], index_self[mask]] = False
359+
return accepted.T
360+
361+
@staticmethod
362+
def basic_formula_ellips_major_axis(lats, cmin=1.5, cmax=10., c0=1.5, lat1=13.5, lat2=5.):
363+
"""Give major axis in km with a given latitude
364+
"""
365+
# Straight line between lat1 and lat2:
366+
# y = a * x + b
367+
a = (cmin - cmax) / (lat1 - lat2)
368+
b = a * -lat1 + cmin
369+
370+
abs_lats = abs(lats)
371+
major_axis = ones(lats.shape, dtype='f8') * cmin
372+
major_axis[abs_lats < lat2] = cmax
373+
m = abs_lats > lat1
374+
m += abs_lats < lat2
375+
376+
major_axis[-m] = a * abs_lats[-m] + b
377+
return major_axis * 111.2
327378

328379
def tracking(self, other):
329380
"""Track obs between self and other
330381
"""
331382
dist = self.distance(other)
332383
# Links available which are close (circle area selection)
333-
mask_accept_dist = self.circle_mask(other, radius=125)
334-
# mask_accept_dist = self.fixed_ellipsoid_mask(other)
384+
# mask_accept_dist = self.circle_mask(other, radius=125)
385+
y = self.basic_formula_ellips_major_axis(self.obs['lat'])
386+
mask_accept_dist = self.fixed_ellipsoid_mask(other, minor=1.5 * 111.2, major=y, only_east=True)
387+
335388
indexs_closest = where(mask_accept_dist)
336389

337390
cost_values = self.cost_function(
@@ -368,19 +421,22 @@ def tracking(self, other):
368421
# Cost to resolve conflict
369422
cost_reduce = cost[i_self_keep][:, i_other_keep]
370423
shape = cost_reduce.shape
371-
logging.debug('Shape conflict matrix : %s', shape)
424+
nb_conflict = (-cost_reduce.mask).sum()
425+
logging.debug('Shape conflict matrix : %s, %d conflicts', shape, nb_conflict)
372426

373-
matrix_size = shape[0] * shape[1]
374-
if (matrix_size) >= 20000:
375-
logging.warning('High number of conflict : %d (matrix_size)',
376-
matrix_size)
427+
428+
if nb_conflict >= (shape[0] + shape[1]):
429+
logging.warning('High number of conflict : %d (nb_conflict)',
430+
shape[0] + shape[1])
377431

378432
links_resolve = 0
379433
# Arbitrary value
380434
max_iteration = max(cost_reduce.shape)
381435
security_increment = 0
382436
while False in cost_reduce.mask:
383437
if security_increment > max_iteration:
438+
# Maybe check if the size decrease if not rise an exception
439+
# x_i, y_i = where(-cost_reduce.mask)
384440
raise Exception('To many iteration: %d' % security_increment)
385441
security_increment += 1
386442
i_min_value = cost_reduce.argmin()
@@ -452,6 +508,7 @@ def extract_longer_eddies(self, nb_min, nb_obs, compress_id=True):
452508
)
453509
eddies.sign_type = self.sign_type
454510
for field in self.obs.dtype.descr:
511+
logging.debug('Copy of field %s ...', field)
455512
var = field[0]
456513
eddies.obs[var] = self.obs[var][mask]
457514
if compress_id:
@@ -490,12 +547,12 @@ def create_variable(handler_nc, kwargs_variable, attr_variable,
490547
except ValueError:
491548
logging.warn('Data is empty')
492549

493-
def write_netcdf(self, path='./'):
550+
def write_netcdf(self, path='./', filename='%(path)s/%(sign_type)s.nc'):
494551
"""Write a netcdf with eddy obs
495552
"""
496553
eddy_size = len(self.observations)
497554
sign_type = 'Cyclonic' if self.sign_type == -1 else 'Anticyclonic'
498-
filename = '%s/%s.nc' % (path, sign_type)
555+
filename = filename % dict(path=path, sign_type=sign_type)
499556
with Dataset(filename, 'w', format='NETCDF4') as h_nc:
500557
logging.info('Create file %s', filename)
501558
# Create dimensions

src/py_eddy_tracker/property_functions.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def _fit_circle_path(self):
341341
# http://www.geo.hunter.cuny.edu/~jochen/gtech201/lectures/
342342
# lec6concepts/map%20coordinate%20systems/
343343
# how%20to%20choose%20a%20projection.htm
344-
proj = Proj('+proj=aeqd +lat_0=%s +lon_0=%s'
344+
proj = Proj('+proj=aeqd +ellps=WGS84 +lat_0=%s +lon_0=%s'
345345
% (lat_mean, lon_mean))
346346

347347
c_x, c_y = proj(self.lon, self.lat)
@@ -503,7 +503,7 @@ def collection_loop(contours, grd, eddy, x_i=None, c_s_xi=None):
503503
#~ *args, save_all_uavg=True)
504504

505505
# Use azimuth equal projection for radius
506-
proj = Proj('+proj=aeqd +lat_0=%s +lon_0=%s'
506+
proj = Proj('+proj=aeqd +ellps=WGS84 +lat_0=%s +lon_0=%s'
507507
% (inner_contlat.mean(),
508508
inner_contlon.mean()))
509509

@@ -522,15 +522,22 @@ def collection_loop(contours, grd, eddy, x_i=None, c_s_xi=None):
522522
c_x, c_y = proj(contlon_s, contlat_s)
523523
_, _, eddy_radius_s, aerr_s = fit_circle_c(c_x, c_y)
524524

525-
properties.obs['radius_s'] = eddy_radius_s
525+
properties.obs['radius_s'] = eddy_radius_s / 1000
526526
properties.obs['speed_radius'] = uavg
527-
properties.obs['radius_e'] = eddy_radius_e
527+
properties.obs['radius_e'] = eddy_radius_e / 1000
528528
properties.obs['eke'] = teke
529-
if 'shape_error' in eddy.track_extra_variables:
530-
properties.obs['shape_error'] = aerr
529+
if 'shape_error_e' in eddy.track_extra_variables:
530+
properties.obs['shape_error_e'] = aerr
531531
if 'shape_error_s' in eddy.track_extra_variables:
532532
properties.obs['shape_error_s'] = aerr_s
533-
533+
534+
if aerr > 99.9 or aerr_s > 99.9:
535+
logging.warning(
536+
'Strange shape at this step! shape_error : %f, %f',
537+
aerr,
538+
aerr_s)
539+
continue
540+
534541
# Update SLA eddy properties
535542
if 'SLA' in eddy.diagnostic_type:
536543

@@ -556,7 +563,6 @@ def collection_loop(contours, grd, eddy, x_i=None, c_s_xi=None):
556563
eddy.mask_eff] = eddy.fillval
557564

558565

559-
560566
def func_hann2d_fast(var, numpasses):
561567
"""
562568
The treament of edges and corners in func_HANN2D can be mimicked by

0 commit comments

Comments
 (0)