28
28
===========================================================================
29
29
30
30
"""
31
- from numpy import zeros, empty, nan, arange, interp, where, unique
31
+ from numpy import zeros, empty, nan, arange, interp, where, unique, \
32
+ ma
32
33
from netCDF4 import Dataset
33
34
from py_eddy_tracker.tools import distance_matrix
34
35
from . import VAR_DESCR, VAR_DESCR_inv
@@ -82,9 +83,11 @@ def __getitem__(self, attr):
82
83
if attr in self.elements:
83
84
return self.observations[attr]
84
85
raise KeyError('%s unknown' % attr)
85
-
86
+
86
87
@property
87
88
def dtype(self):
89
+ """Return dtype to build numpy array
90
+ """
88
91
dtype = list()
89
92
for elt in self.elements:
90
93
dtype.append((elt, VAR_DESCR[elt][
@@ -94,6 +97,8 @@ def dtype(self):
94
97
95
98
@property
96
99
def elements(self):
100
+ """Return all variable name
101
+ """
97
102
elements = [
98
103
'lon', # 'centlon'
99
104
'lat', # 'centlat'
@@ -113,9 +118,13 @@ def elements(self):
113
118
return elements
114
119
115
120
def coherence(self, other):
121
+ """Check coherence between two dataset
122
+ """
116
123
return self.track_extra_variables == other.track_extra_variables
117
-
124
+
118
125
def merge(self, other):
126
+ """Merge two dataset
127
+ """
119
128
nb_obs_self = len(self)
120
129
nb_obs = nb_obs_self + len(other)
121
130
eddies = self.__class__(size=nb_obs)
@@ -126,6 +135,8 @@ def merge(self, other):
126
135
127
136
@property
128
137
def obs(self):
138
+ """returan array observations
139
+ """
129
140
return self.observations
130
141
131
142
def __len__(self):
@@ -136,6 +147,8 @@ def __iter__(self):
136
147
yield obs
137
148
138
149
def insert_observations(self, other, index):
150
+ """Insert other obs in self at the index
151
+ """
139
152
if not self.coherence(other):
140
153
raise Exception('Observations with no coherence')
141
154
insert_size = len(other.obs)
@@ -156,6 +169,8 @@ def insert_observations(self, other, index):
156
169
return self
157
170
158
171
def append(self, other):
172
+ """Merge
173
+ """
159
174
return self + other
160
175
161
176
def __add__(self, other):
@@ -172,13 +187,15 @@ def distance(self, other):
172
187
return dist_result
173
188
174
189
def index(self, index):
190
+ """Return obs from self at the index
191
+ """
175
192
size = 1
176
193
if hasattr(index, '__iter__'):
177
194
size = len(index)
178
195
eddies = self.__class__(size, self.track_extra_variables)
179
196
eddies.obs[:] = self.obs[index]
180
197
return eddies
181
-
198
+
182
199
@staticmethod
183
200
def load_from_netcdf(filename):
184
201
with Dataset(filename) as h_nc:
@@ -187,22 +204,79 @@ def load_from_netcdf(filename):
187
204
for variable in h_nc.variables:
188
205
if variable == 'cyc':
189
206
continue
190
- eddies.obs[VAR_DESCR_inv[variable]] = h_nc.variables[variable][:]
207
+ eddies.obs[VAR_DESCR_inv[variable]
208
+ ] = h_nc.variables[variable][:]
191
209
eddies.sign_type = h_nc.variables['cyc'][0]
192
210
return eddies
193
211
194
212
def tracking(self, other):
195
- """Track obs between from self to other
213
+ """Track obs between self and other
196
214
"""
197
- dist = self.distance(other)
198
- i_self, i_other = where(dist < 20.)
215
+ cost = self.distance(other)
216
+ # Links available which respect a maximal cost
217
+ mask_accept_cost = cost < 75
218
+ cost = ma.array(cost, mask=-mask_accept_cost, dtype='i2')
219
+
220
+ # Count number of link by self obs and other obs
221
+ self_links = mask_accept_cost.sum(axis=1)
222
+ other_links = mask_accept_cost.sum(axis=0)
223
+ max_links = max(self_links.max(), other_links.max())
224
+ if max_links > 5:
225
+ logging.warning('One observation have %d links', max_links)
226
+
227
+ # If some obs have multiple link, we keep only one link by eddy
228
+ eddies_separation = 1 < self_links
229
+ eddies_merge = 1 < other_links
230
+ test = eddies_separation.any() or eddies_merge.any()
231
+ if test:
232
+ # We extract matrix which contains concflict
233
+ obs_linking_to_self = mask_accept_cost[eddies_separation
234
+ ].any(axis=0)
235
+ obs_linking_to_other = mask_accept_cost[:, eddies_merge
236
+ ].any(axis=1)
237
+ i_self_keep = where(obs_linking_to_other + eddies_separation)[0]
238
+ i_other_keep = where(obs_linking_to_self + eddies_merge)[0]
239
+
240
+ # Cost to resolve conflict
241
+ cost_reduce = cost[i_self_keep][:, i_other_keep]
242
+ shape = cost_reduce.shape
243
+ logging.debug('Shape conflict matrix : %s', shape)
244
+
245
+ matrix_size = shape[0] * shape[1]
246
+ if (matrix_size) >= 20000:
247
+ logging.warning('High number of conflict : %d (matrix_size)',
248
+ matrix_size)
249
+
250
+ links_resolve = 0
251
+ while False in cost_reduce.mask:
252
+ i_min_value = cost_reduce.argmin()
253
+ i, j = i_min_value / shape[1], i_min_value % shape[1]
254
+ # Set to False all link
255
+ mask_accept_cost[i_self_keep[i]] = False
256
+ mask_accept_cost[:, i_other_keep[j]] = False
257
+ cost_reduce.mask[i] = True
258
+ cost_reduce.mask[:, j] = True
259
+ # we active only this link
260
+ mask_accept_cost[i_self_keep[i], i_other_keep[j]] = True
261
+ links_resolve += 1
262
+ logging.debug('%d links resolve', links_resolve)
263
+
264
+ i_self, i_other = where(mask_accept_cost)
199
265
200
266
logging.debug('%d matched with previous', i_self.shape[0])
267
+
268
+ # Check
269
+ if unique(i_other).shape[0] != i_other.shape[0]:
270
+ raise Exception()
271
+ if unique(i_self).shape[0] != i_self.shape[0]:
272
+ raise Exception()
201
273
return i_self, i_other
202
274
203
275
204
276
class VirtualEddiesObservations(EddiesObservations):
205
-
277
+ """Class to work with virtual obs
278
+ """
279
+
206
280
@property
207
281
def elements(self):
208
282
elements = super(VirtualEddiesObservations, self).elements
@@ -211,7 +285,9 @@ def elements(self):
211
285
212
286
213
287
class TrackEddiesObservations(EddiesObservations):
214
-
288
+ """Class to practice Tracking on observations
289
+ """
290
+
215
291
def filled_by_interpolation(self, mask):
216
292
"""Filled selected values by interpolation
217
293
"""
@@ -228,42 +304,47 @@ def filled_by_interpolation(self, mask):
228
304
self.obs[var][-mask])
229
305
230
306
def extract_longer_eddies(self, nb_min, nb_obs, compress_id=True):
231
- m = nb_obs >= nb_min
232
- nb_obs_select = m.sum()
307
+ """Select eddies which are longer than nb_min
308
+ """
309
+ mask = nb_obs >= nb_min
310
+ nb_obs_select = mask.sum()
233
311
logging.info('Selection of %d observations', nb_obs_select)
234
312
eddies = TrackEddiesObservations(size=nb_obs_select)
235
313
eddies.sign_type = self.sign_type
236
314
for var, _ in eddies.obs.dtype.descr:
237
- eddies.obs[var] = self.obs[var][m ]
315
+ eddies.obs[var] = self.obs[var][mask ]
238
316
if compress_id:
239
317
list_id = unique(eddies.obs['track'])
240
318
list_id.sort()
241
319
id_translate = arange(list_id.max() + 1)
242
320
id_translate[list_id] = arange(len(list_id)) + 1
243
321
eddies.obs['track'] = id_translate[eddies.obs['track']]
244
322
return eddies
245
-
323
+
246
324
@property
247
325
def elements(self):
248
326
elements = super(TrackEddiesObservations, self).elements
249
327
elements.extend(['track', 'n', 'virtual'])
250
328
return elements
251
-
252
- def create_variable(self, handler_nc, kwargs_variable,
329
+
330
+ @staticmethod
331
+ def create_variable(handler_nc, kwargs_variable,
253
332
attr_variable, data, scale_factor=None):
333
+ """Create variable
334
+ """
254
335
var = handler_nc.createVariable(
255
336
zlib=True,
256
337
complevel=1,
257
338
**kwargs_variable)
258
339
for attr, attr_value in attr_variable.iteritems():
259
340
var.setncattr(attr, attr_value)
260
-
341
+
261
342
var[:] = data
262
-
343
+
263
344
#~ var.set_auto_maskandscale(False)
264
345
if scale_factor is not None:
265
346
var.scale_factor = scale_factor
266
-
347
+
267
348
try:
268
349
var.setncattr('min', var[:].min())
269
350
var.setncattr('max', var[:].max())
@@ -291,7 +372,10 @@ def write_netcdf(self):
291
372
dimensions=VAR_DESCR[name]['nc_dims']),
292
373
VAR_DESCR[name]['nc_attr'],
293
374
self.observations[name],
294
- scale_factor=None if 'scale_factor' not in VAR_DESCR[name] else VAR_DESCR[name]['scale_factor'])
375
+ scale_factor=None
376
+ if 'scale_factor' not in VAR_DESCR[name] else
377
+ VAR_DESCR[name]['scale_factor']
378
+ )
295
379
296
380
# Add cyclonic information
297
381
self.create_variable(
@@ -305,7 +389,12 @@ def write_netcdf(self):
305
389
self.set_global_attr_netcdf(h_nc)
306
390
307
391
def set_global_attr_netcdf(self, h_nc):
308
- h_nc.title = 'Cyclonic' if self.sign_type == -1 else 'Anticyclonic' + ' eddy tracks'
392
+ """Set global attr
393
+ """
394
+ if self.sign_type == -1:
395
+ h_nc.title = 'Cyclonic'
396
+ else:
397
+ h_nc.title = 'Anticyclonic' + ' eddy tracks'
309
398
#~ h_nc.grid_filename = self.grd.grid_filename
310
399
#~ h_nc.grid_date = str(self.grd.grid_date)
311
400
#~ h_nc.product = self.product
@@ -324,7 +413,7 @@ def set_global_attr_netcdf(self, h_nc):
324
413
#~ h_nc.evolve_amp_max = self.evolve_amp_max
325
414
#~ h_nc.evolve_area_min = self.evolve_area_min
326
415
#~ h_nc.evolve_area_max = self.evolve_area_max
327
- #~
416
+
328
417
#~ h_nc.llcrnrlon = self.grd.lonmin
329
418
#~ h_nc.urcrnrlon = self.grd.lonmax
330
419
#~ h_nc.llcrnrlat = self.grd.latmin
0 commit comments