1313from scipy .spatial import cKDTree
1414from matplotlib .figure import Figure
1515from matplotlib .path import Path as BasePath
16+ from matplotlib .contour import QuadContourSet as BaseQuadContourSet
1617from pyproj import Proj
1718from ..tools import fit_circle_c , distance_vector
1819from ..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
2330def isvalid (self ):
2431 return False not in (self .vertices [0 ] == self .vertices [- 1 ]
@@ -51,12 +58,34 @@ def lat(self):
5158BasePath .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
6291def fit_circle_path (self ):
@@ -93,25 +122,21 @@ def _fit_circle_path(self):
93122BasePath ._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
117142class 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
530542class 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
618645class 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