13
13
from scipy .spatial import cKDTree
14
14
from matplotlib .figure import Figure
15
15
from matplotlib .path import Path as BasePath
16
+ from matplotlib .contour import QuadContourSet as BaseQuadContourSet
16
17
from pyproj import Proj
17
18
from ..tools import fit_circle_c , distance_vector
18
19
from ..observations import EddiesObservations
19
- from ..eddy_feature import Amplitude
20
+ from ..eddy_feature import Amplitude , get_uavg
20
21
21
22
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
+
22
29
@property
23
30
def isvalid (self ):
24
31
return False not in (self .vertices [0 ] == self .vertices [- 1 ]
@@ -51,12 +58,34 @@ def lat(self):
51
58
BasePath .lat = lat
52
59
53
60
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
60
89
61
90
62
91
def fit_circle_path (self ):
@@ -93,25 +122,21 @@ def _fit_circle_path(self):
93
122
BasePath ._fit_circle_path = _fit_circle_path
94
123
95
124
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
115
140
116
141
117
142
class GridDataset (object ):
@@ -138,6 +163,7 @@ class GridDataset(object):
138
163
'global_attrs' ,
139
164
'vars' ,
140
165
'interpolators' ,
166
+ 'speed_coef' ,
141
167
)
142
168
143
169
GRAVITY = 9.807
@@ -179,6 +205,7 @@ def is_centered(self):
179
205
def load_general_features (self ):
180
206
"""Load attrs
181
207
"""
208
+ logging .debug ('Load general feature from %(filename)s' , dict (filename = self .filename ))
182
209
with Dataset (self .filename ) as h :
183
210
# Load generals
184
211
self .dimensions = {i : len (v ) for i , v in h .dimensions .items ()}
@@ -286,6 +313,7 @@ def grid(self, varname):
286
313
if varname not in self .vars :
287
314
coordinates_dims = list (self .x_dim )
288
315
coordinates_dims .extend (list (self .y_dim ))
316
+ logging .debug ('Load %(varname)s from %(filename)s' , dict (varname = varname , filename = self .filename ))
289
317
with Dataset (self .filename ) as h :
290
318
dims = h .variables [varname ].dimensions
291
319
sl = [slice (None ) if dim in coordinates_dims else 0 for dim in dims ]
@@ -316,17 +344,18 @@ def bounds(self):
316
344
"""
317
345
return self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
318
346
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 ,
320
348
extra_array_variables = None , array_sampling = 50 , pixel_limit = None ):
321
349
if extra_variables is None :
322
350
extra_variables = list ()
323
351
if extra_array_variables is None :
324
352
extra_array_variables = list ()
325
353
if pixel_limit is None :
326
354
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 )
330
359
z_min , z_max = data .min (), data .max ()
331
360
332
361
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
336
365
if len (x .shape ) == 1 :
337
366
data = data .T
338
367
##
368
+ logging .debug ('Start computing iso lines' )
369
+ fig = Figure ()
370
+ ax = fig .add_subplot (111 )
339
371
contours = ax .contour (x , y , data , levels )
372
+ logging .debug ('Finish computing iso lines' )
340
373
341
374
anticyclonic_search = True
342
375
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
353
386
if nb_paths == 0 :
354
387
continue
355
388
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' ,
357
390
corrected_coll_index , cvalues , nb_paths )
358
391
359
392
# 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
377
410
if anticyclonic_search != acyc_not_cyc :
378
411
continue
379
412
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 )
382
414
383
415
# 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 ]:
385
417
continue
386
418
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 )
393
421
394
- print ( amp . within_amplitude_limits (), amp . amplitude )
422
+ # If we have a valid amplitude
395
423
if (not amp .within_amplitude_limits ()) or (amp .amplitude == 0 ):
396
424
continue
397
425
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
-
413
426
if reset_centroid :
414
427
centi = reset_centroid [0 ]
415
428
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 ]
418
431
419
432
# 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()
421
434
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 )
426
439
427
440
# Use azimuth equal projection for radius
428
441
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 ()))
431
443
432
444
# First, get position based on innermost
433
445
# contour
@@ -455,7 +467,7 @@ def eddy_identification(self, grid_name, step=0.005, shape_error=55, extra_varia
455
467
properties .obs ['radius_s' ] = eddy_radius_s / 1000
456
468
properties .obs ['speed_radius' ] = uavg
457
469
properties .obs ['radius_e' ] = eddy_radius_e / 1000
458
- properties .obs ['eke' ] = teke
470
+ #~ properties.obs['eke'] = teke
459
471
if 'shape_error_e' in eddy .track_extra_variables :
460
472
properties .obs ['shape_error_e' ] = aerr
461
473
if 'shape_error_s' in eddy .track_extra_variables :
@@ -505,33 +517,36 @@ def _gaussian_filter(data, sigma, mode='reflect'):
505
517
return ma .array (v / w , mask = w == 0 )
506
518
507
519
@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 ):
509
521
# Instantiate Amplitude object
510
522
amp = Amplitude (
511
- contour_x = cont_x ,
512
- contour_y = cont_y ,
523
+ # Indices of all pixels in contour
513
524
i_contour_x = i_x_in ,
514
525
i_contour_y = i_y_in ,
526
+ # Height of level
515
527
contour_height = contour_height ,
516
- data = data )
528
+ # All grid
529
+ data = data ,
530
+ # Step by level
531
+ interval = step )
517
532
518
533
if anticyclonic_search :
519
- reset_centroid = amp .all_pixels_above_h0 ()
534
+ reset_centroid = amp .all_pixels_above_h0 (level )
520
535
521
536
else :
522
- reset_centroid = amp .all_pixels_below_h0 (
523
- contours .levels [corrected_coll_index ])
537
+ reset_centroid = amp .all_pixels_below_h0 (level )
524
538
525
- return amp
526
- # if not amp.within_amplitude_limits() or amp.amplitude:
527
- # continue
539
+ return reset_centroid , amp
528
540
529
541
530
542
class UnRegularGridDataset (GridDataset ):
531
543
"""Class which manage unregular grid
532
544
"""
533
545
534
- __slots__ = 'index_interp'
546
+ __slots__ = (
547
+ 'index_interp' ,
548
+ '_speed_norm' ,
549
+ )
535
550
536
551
def bbox_indice (self , vertices ):
537
552
dist , idx = self .index_interp .query (vertices , k = 1 )
@@ -568,11 +583,13 @@ def compute_pixel_path(self, x0, y0, x1, y1):
568
583
pass
569
584
570
585
def init_pos_interpolator (self ):
586
+ logging .debug ('Create a KdTree could be long ...' )
571
587
self .index_interp = cKDTree (
572
588
column_stack ((
573
589
self .x_c .reshape (- 1 ),
574
590
self .y_c .reshape (- 1 )
575
591
)))
592
+ logging .debug ('... OK' )
576
593
577
594
def _low_filter (self , grid_name , x_cut , y_cut , factor = 40. ):
578
595
data = self .grid (grid_name )
@@ -614,6 +631,16 @@ def _low_filter(self, grid_name, x_cut, y_cut, factor=40.):
614
631
z_interp = RectBivariateSpline (x_center , y_center , z_filtered , ** opts_interpolation ).ev (x , y )
615
632
return ma .array (z_interp , mask = m_interp .ev (x , y ) > 0.00001 )
616
633
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
+
617
644
618
645
class RegularGridDataset (GridDataset ):
619
646
"""Class only for regular grid
@@ -782,3 +809,9 @@ def add_uv(self, grid_height):
782
809
d_x_degrees = (d_x_degrees + 180 ) % 360 - 180
783
810
d_x = self .EARTH_RADIUS * 2 * pi / 360 * d_x_degrees * cos (deg2rad (self .y_c ))
784
811
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