55from  scipy .interpolate  import  interp1d 
66from  numpy  import  concatenate , int32 , empty , maximum , where , array , \
77    sin , deg2rad , pi , ones , cos , ma , int8 , histogram2d , arange , float_ , \
8-     linspace , errstate , round_ ,  int_ , column_stack , interp 
8+     linspace , errstate , int_ , column_stack , interp ,  meshgrid ,  unique ,  nan 
99from  netCDF4  import  Dataset 
1010from  scipy .ndimage  import  gaussian_filter , convolve 
1111from  scipy .interpolate  import  RectBivariateSpline 
@@ -277,6 +277,7 @@ def load(self):
277277            self .vars [y_name ] =  h .variables [y_name ][:]
278278
279279            if  self .is_centered :
280+                 logging .info ('Grid center' )
280281                self .x_c  =  self .vars [x_name ]
281282                self .y_c  =  self .vars [y_name ]
282283
@@ -346,122 +347,142 @@ def bounds(self):
346347        """ 
347348        return  self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
348349
349-     def  eddy_identification (self , grid_height , step = 0.005 , shape_error = 55 , array_sampling = 50 , pixel_limit = None ):
350+     def  eddy_identification (self , grid_height , uname , vname ,
351+                             step = 0.005 , shape_error = 55 , array_sampling = 50 , pixel_limit = None ):
352+         # The inf limit must be in pixel and  sup limit in surface 
350353        if  pixel_limit  is  None :
351354            pixel_limit  =  (8 , 1000 )
352355
353-         self .init_speed_coef ()
356+         # Compute an interpolator for eke 
357+         self .init_speed_coef (uname , vname )
354358
359+         # Get h grid 
355360        data  =  self .grid (grid_height )
356-         z_min , z_max  =  data .min (), data .max ()
357361
362+         # Compute levels for ssh 
363+         z_min , z_max  =  data .min (), data .max ()
358364        levels  =  arange (z_min  -  z_min  %  step , z_max  -  z_max  %  step  +  2  *  step , step )
359365
366+         # Get x and y values 
360367        x , y  =  self .vars [self .coordinates [0 ]], self .vars [self .coordinates [1 ]]
361368
362-         eddies   =   list () 
369+         # Compute ssh contour 
363370        contours  =  Contours (x , y , data , levels )
364371
365-         anticyclonic_search  =  True 
366-         iterator  =  1  if  anticyclonic_search  else  - 1 
367- 
368-         # Loop over each collection 
369-         for  coll_ind , coll  in  enumerate (contours .iter (step = iterator )):
370-             corrected_coll_index  =  coll_ind 
371-             if  iterator  ==  - 1 :
372-                 corrected_coll_index  =  -  coll_ind  -  1 
373- 
374-             contour_paths  =  coll .get_paths ()
375-             nb_paths  =  len (contour_paths )
376-             if  nb_paths  ==  0 :
377-                 continue 
378-             cvalues  =  contours .cvalues [corrected_coll_index ]
379-             logging .debug ('doing collection %s, contour value %.4f, %d paths' ,
380-                           corrected_coll_index , cvalues , nb_paths )
381- 
382-             # Loop over individual c_s contours (i.e., every eddy in field) 
383-             for  current_contour  in  contour_paths :
384-                 if  current_contour .used :
385-                     continue 
386-                 # Filter for closed contours 
387-                 if  not  current_contour .isvalid :
372+         # Compute cyclonic and anticylonic research: 
373+         a_and_c  =  list ()
374+         for  anticyclonic_search  in  [True , False ]:
375+             eddies  =  list ()
376+             iterator  =  1  if  anticyclonic_search  else  - 1 
377+ 
378+             # Loop over each collection 
379+             for  coll_ind , coll  in  enumerate (contours .iter (step = iterator )):
380+                 corrected_coll_index  =  coll_ind 
381+                 if  iterator  ==  - 1 :
382+                     corrected_coll_index  =  -  coll_ind  -  1 
383+ 
384+                 contour_paths  =  coll .get_paths ()
385+                 nb_paths  =  len (contour_paths )
386+                 if  nb_paths  ==  0 :
388387                    continue 
389-                 centlon_e , centlat_e , eddy_radius_e , aerr  =  current_contour .fit_circle ()
390-                 # Filter for shape 
391-                 if  aerr  <  0  or  aerr  >  shape_error :
392-                     continue 
393-                 # Get indices of centroid 
394-                 # Give only 1D array of lon and lat not 2D data 
395-                 i_x , i_y  =  self .nearest_grd_indice (centlon_e , centlat_e )
388+                 cvalues  =  contours .cvalues [corrected_coll_index ]
389+                 logging .debug ('doing collection %s, contour value %.4f, %d paths' ,
390+                               corrected_coll_index , cvalues , nb_paths )
396391
397-                 # Check if centroid is on define value 
398-                 if  hasattr (data , 'mask' ) and  data .mask [i_x , i_y ]:
399-                     continue 
400-                 # Test to know cyclone or anticyclone 
401-                 acyc_not_cyc  =  data [i_x , i_y ] >=  cvalues 
402-                 if  anticyclonic_search  !=  acyc_not_cyc :
403-                     continue 
392+                 # Loop over individual c_s contours (i.e., every eddy in field) 
393+                 for  current_contour  in  contour_paths :
394+                     if  current_contour .used :
395+                         continue 
396+                     # Filter for closed contours 
397+                     if  not  current_contour .isvalid :
398+                         continue 
399+ 
400+                     centlon_e , centlat_e , eddy_radius_e , aerr  =  current_contour .fit_circle ()
401+                     # Filter for shape 
402+                     if  aerr  <  0  or  aerr  >  shape_error :
403+                         continue 
404+                     # Get indices of centroid 
405+                     # Give only 1D array of lon and lat not 2D data 
406+                     i_x , i_y  =  self .nearest_grd_indice (centlon_e , centlat_e )
404407
405-                 i_x_in , i_y_in  =  current_contour .pixels_in (self )
408+                     # Check if centroid is on define value 
409+                     if  hasattr (data , 'mask' ) and  data .mask [i_x , i_y ]:
410+                         continue 
411+                     # Test to know cyclone or anticyclone 
412+                     acyc_not_cyc  =  data [i_x , i_y ] >=  cvalues 
413+                     if  anticyclonic_search  !=  acyc_not_cyc :
414+                         continue 
406415
407-                 # Maybe limit max must be replace with a maximum of surface 
408-                 if  current_contour .nb_pixel  <  pixel_limit [0 ] or  current_contour .nb_pixel  >  pixel_limit [1 ]:
409-                     continue 
416+                     # Find all pixels in the contour 
417+                     i_x_in , i_y_in  =  current_contour .pixels_in (self )
410418
411-                 reset_centroid , amp  =  self .get_amplitude (i_x_in , i_y_in , cvalues , data ,
412-                     anticyclonic_search = anticyclonic_search , level = contours .levels [corrected_coll_index ], step = step )
419+                     # Maybe limit max must be replace with a maximum of surface 
420+                     if  current_contour .nb_pixel  <  pixel_limit [0 ] or  current_contour .nb_pixel  >  pixel_limit [1 ]:
421+                         continue 
413422
414-                 # If we have a valid amplitude 
415-                 if  (not  amp .within_amplitude_limits ()) or  (amp .amplitude  ==  0 ):
416-                     continue 
423+                     # Compute amplitude 
424+                     reset_centroid , amp  =  self .get_amplitude (i_x_in , i_y_in , cvalues , data ,
425+                                                              anticyclonic_search = anticyclonic_search ,
426+                                                              level = contours .levels [corrected_coll_index ], step = step )
417427
418-                 if  reset_centroid :
419-                     centi  =  reset_centroid [0 ]
420-                     centj  =  reset_centroid [1 ]
421-                     centlon_e  =  x [centi , centj ]
422-                     centlat_e  =  y [centi , centj ]
423- 
424-                 average_speed , speed_contour , inner_contour , speed_array  =  \
425-                     get_uavg (self , contours , centlon_e , centlat_e , current_contour , anticyclonic_search , corrected_coll_index )
426- 
427-                 # Use azimuth equal projection for radius 
428-                 proj  =  Proj ('+proj=aeqd +ellps=WGS84 +lat_0={1} +lon_0={0}' .format (* inner_contour .mean_coordinates ))
429-                 # First, get position based on innermost 
430-                 # contour 
431-                 c_x , c_y  =  proj (inner_contour .lon , inner_contour .lat )
432-                 centx_s , centy_s , _ , _  =  fit_circle_c (c_x , c_y )
433-                 centlon_s , centlat_s  =  proj (centx_s , centy_s , inverse = True )
434-                 # Second, get speed-based radius based on 
435-                 # contour of max uavg 
436-                 c_x , c_y  =  proj (speed_contour .lon , speed_contour .lat )
437-                 _ , _ , eddy_radius_s , aerr_s  =  fit_circle_c (c_x , c_y )
438- 
439-                 # Instantiate new EddyObservation object 
440-                 properties  =  EddiesObservations (
441-                     size = 1 ,
442-                     track_extra_variables = ['shape_error_e' , 'shape_error_s' ],
443-                     track_array_variables = array_sampling ,
444-                     array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' ]
445-                 )
446-                 properties .obs ['amplitude' ] =  amp .amplitude 
447-                 properties .obs ['radius_s' ] =  eddy_radius_s  /  1000 
448-                 properties .obs ['speed_radius' ] =  average_speed 
449-                 properties .obs ['radius_e' ] =  eddy_radius_e  /  1000 
450-                 properties .obs ['shape_error_e' ] =  aerr 
451-                 properties .obs ['shape_error_s' ] =  aerr_s 
452-                 properties .obs ['lon' ] =  centlon_s 
453-                 properties .obs ['lat' ] =  centlat_s 
454-                 properties .obs ['contour_lon_e' ], properties .obs ['contour_lat_e' ] =  uniform_resample (
455-                     current_contour .lon , current_contour .lat , fixed_size = array_sampling )
456-                 properties .obs ['contour_lon_s' ], properties .obs ['contour_lat_s' ] =  uniform_resample (
457-                         speed_contour .lon , speed_contour .lat , fixed_size = array_sampling )
458-                 if  aerr  >  99.9  or  aerr_s  >  99.9 :
459-                     logging .warning ('Strange shape at this step! shape_error : %f, %f' , aerr , aerr_s )
460- 
461-                 eddies .append (properties )
462-                 # To reserve definitively the area 
463-                 data .mask [i_x_in , i_y_in ] =  True 
464-         return  eddies 
428+                     # If we have a valid amplitude 
429+                     if  (not  amp .within_amplitude_limits ()) or  (amp .amplitude  ==  0 ):
430+                         continue 
431+ 
432+                     if  reset_centroid :
433+                         centi  =  reset_centroid [0 ]
434+                         centj  =  reset_centroid [1 ]
435+                         # To move in regular and unregular grid 
436+                         if  len (x .shape ) ==  1 :
437+                             centlon_e  =  x [centi ]
438+                             centlat_e  =  y [centj ]
439+                         else :
440+                             centlon_e  =  x [centi , centj ]
441+                             centlat_e  =  y [centi , centj ]
442+ 
443+                     # centlat_e and centlon_e must be index of maximum, we will loose some inner contour, if it's not 
444+                     average_speed , speed_contour , inner_contour , speed_array  =  \
445+                         get_uavg (self , contours , centlon_e , centlat_e , current_contour , anticyclonic_search , corrected_coll_index )
446+ 
447+                     # Use azimuth equal projection for radius 
448+                     proj  =  Proj ('+proj=aeqd +ellps=WGS84 +lat_0={1} +lon_0={0}' .format (* inner_contour .mean_coordinates ))
449+                     # First, get position based on innermost 
450+                     # contour 
451+                     c_x , c_y  =  proj (inner_contour .lon , inner_contour .lat )
452+                     centx_s , centy_s , _ , _  =  fit_circle_c (c_x , c_y )
453+                     centlon_s , centlat_s  =  proj (centx_s , centy_s , inverse = True )
454+                     # Second, get speed-based radius based on 
455+                     # contour of max uavg 
456+                     c_x , c_y  =  proj (speed_contour .lon , speed_contour .lat )
457+                     _ , _ , eddy_radius_s , aerr_s  =  fit_circle_c (c_x , c_y )
458+ 
459+                     # Instantiate new EddyObservation object 
460+                     properties  =  EddiesObservations (
461+                         size = 1 ,
462+                         track_extra_variables = ['shape_error_e' , 'shape_error_s' ],
463+                         track_array_variables = array_sampling ,
464+                         array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' ]
465+                     )
466+                     properties .obs ['amplitude' ] =  amp .amplitude 
467+                     properties .obs ['radius_s' ] =  eddy_radius_s  /  1000 
468+                     properties .obs ['speed_radius' ] =  average_speed 
469+                     properties .obs ['radius_e' ] =  eddy_radius_e  /  1000 
470+                     properties .obs ['shape_error_e' ] =  aerr 
471+                     properties .obs ['shape_error_s' ] =  aerr_s 
472+                     properties .obs ['lon' ] =  centlon_s 
473+                     properties .obs ['lat' ] =  centlat_s 
474+                     properties .obs ['contour_lon_e' ], properties .obs ['contour_lat_e' ] =  uniform_resample (
475+                         current_contour .lon , current_contour .lat , fixed_size = array_sampling )
476+                     properties .obs ['contour_lon_s' ], properties .obs ['contour_lat_s' ] =  uniform_resample (
477+                             speed_contour .lon , speed_contour .lat , fixed_size = array_sampling )
478+                     if  aerr  >  99.9  or  aerr_s  >  99.9 :
479+                         logging .warning ('Strange shape at this step! shape_error : %f, %f' , aerr , aerr_s )
480+ 
481+                     eddies .append (properties )
482+                     # To reserve definitively the area 
483+                     data .mask [i_x_in , i_y_in ] =  True 
484+             a_and_c .append (EddiesObservations .concatenate (eddies ))
485+         return  a_and_c 
465486
466487    @staticmethod  
467488    def  _gaussian_filter (data , sigma , mode = 'reflect' ):
@@ -471,7 +492,7 @@ def _gaussian_filter(data, sigma, mode='reflect'):
471492        local_data [data .mask ] =  0 
472493
473494        v  =  gaussian_filter (local_data , sigma = sigma , mode = mode )
474-         w  =  gaussian_filter (float_ (- data .mask ), sigma = sigma , mode = mode )
495+         w  =  gaussian_filter (float_ (~ data .mask ), sigma = sigma , mode = mode )
475496
476497        with  errstate (invalid = 'ignore' ):
477498            return  ma .array (v  /  w , mask = w  ==  0 )
@@ -492,7 +513,6 @@ def get_amplitude(i_x_in, i_y_in, contour_height, data, anticyclonic_search=True
492513
493514        if  anticyclonic_search :
494515            reset_centroid  =  amp .all_pixels_above_h0 (level )
495- 
496516        else :
497517            reset_centroid  =  amp .all_pixels_below_h0 (level )
498518
@@ -597,16 +617,48 @@ class RegularGridDataset(GridDataset):
597617    """Class only for regular grid 
598618    """ 
599619
600-     __slots__  =  ()
620+     __slots__  =  (
621+         '_speed_ev' ,
622+         )
601623
602624    def  init_pos_interpolator (self ):
603625        """Create function to have a quick index interpolator 
604626        """ 
605-         self .xinterp  =  interp1d (self .x_bounds , range (self .x_bounds .shape [0 ]), assume_sorted = True )
606-         self .yinterp  =  interp1d (self .y_bounds , range (self .y_bounds .shape [0 ]), assume_sorted = True )
627+         self .xinterp  =  arange (self .x_bounds .shape [0 ])
628+         self .yinterp  =  arange (self .y_bounds .shape [0 ])
629+ 
630+     def  bbox_indice (self , vertices ):
631+         lon , lat  =  vertices .T 
632+         lon_min , lon_max  =  lon .min (), lon .max ()
633+         lat_min , lat_max  =  lat .min (), lat .max ()
634+         i_x0 , i_y0  =  self .nearest_grd_indice (lon_min , lat_min )
635+         i_x1 , i_y1  =  self .nearest_grd_indice (lon_max , lat_max )
636+         slice_x  =  slice (i_x0  -  self .N , i_x1  +  self .N  +  1 )
637+         slice_y  =  slice (i_y0  -  self .N , i_y1  +  self .N  +  1 )
638+         return  slice_x , slice_y 
639+ 
640+     def  get_pixels_in (self , contour ):
641+         slice_x , slice_y  =  self .bbox_indice (contour .vertices )
642+         x , y  =  meshgrid (self .x_c [slice_x ], self .y_c [slice_y ])
643+         pts  =  array ((x .reshape (- 1 ), y .reshape (- 1 ))).T 
644+         mask  =  contour .contains_points (pts ).reshape (x .shape )
645+         i_x , i_y  =  where (mask .T )
646+         i_x  +=  slice_x .start 
647+         i_y  +=  slice_y .start 
648+         return  i_x , i_y 
607649
608650    def  nearest_grd_indice (self , x , y ):
609-         return  int_ (round_ (self .xinterp (x ))), int_ (round_ (self .yinterp (y )))
651+         """ 
652+         Can use this version, which are faster without check 
653+         from numpy.core.multiarray import interp 
654+         Args: 
655+             x: 
656+             y: 
657+ 
658+         Returns: 
659+ 
660+         """ 
661+         return  round (interp (x , self .x_bounds , self .xinterp )), round (interp (y , self .y_bounds , self .yinterp ))
610662
611663    @property  
612664    def  xstep (self ):
@@ -761,8 +813,12 @@ def add_uv(self, grid_height):
761813        d_x  =  self .EARTH_RADIUS  *  2  *  pi  /  360  *  d_x_degrees  *  cos (deg2rad (self .y_c ))
762814        self .vars ['v' ] =  d_hx  /  d_x  *  gof 
763815
816+     def  speed_coef (self , contour ):
817+         lon , lat  =  contour .regular_coordinates [1 :].T 
818+         return  self ._speed_ev (lon , lat )
819+ 
764820    def  init_speed_coef (self , uname = 'u' , vname = 'v' ):
765821        """Draft 
766822        """ 
767823        uspd  =  (self .grid (uname ) **  2  +  self .grid (vname ) **  2 ) **  .5 
768-         self .speed_coef  =  RectBivariateSpline (self .xc , self .yc , uspd , kx = 1 , ky = 1 ).ev 
824+         self ._speed_ev  =  RectBivariateSpline (self .x_c , self .y_c , uspd , kx = 1 , ky = 1 ).ev 
0 commit comments