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