44import  logging 
55from  numpy  import  concatenate , int32 , empty , maximum , where , array , \
66    sin , deg2rad , pi , ones , cos , ma , int8 , histogram2d , arange , float_ , \
7-     linspace , errstate , int_ , column_stack , interp , meshgrid , nan , ceil , sinc , float64 
7+     linspace , errstate , int_ , column_stack , interp , meshgrid , nan , ceil , sinc , float64 , isnan 
8+ from  datetime  import  datetime 
89from  scipy .special  import  j1 
9- from  scipy .signal  import  convolve2d 
1010from  netCDF4  import  Dataset 
1111from  scipy .ndimage  import  gaussian_filter , convolve 
12- from  scipy .interpolate  import  RectBivariateSpline 
12+ from  scipy .interpolate  import  RectBivariateSpline ,  interp1d 
1313from  scipy .spatial  import  cKDTree 
14+ from  scipy .signal  import  welch , convolve2d 
1415from  matplotlib .path  import  Path  as  BasePath 
1516from  matplotlib .contour  import  QuadContourSet  as  BaseQuadContourSet 
1617from  pyproj  import  Proj 
18+ from  pint  import  UnitRegistry 
1719from  ..tools  import  fit_circle_c , distance_vector , winding_number_poly , poly_contain_poly , \
1820    distance , distance_point_vector 
1921from  ..observations  import  EddiesObservations 
2022from  ..eddy_feature  import  Amplitude , Contours 
23+ from  .. import  VAR_DESCR 
2124
2225
2326def  raw_resample (datas , fixed_size ):
@@ -314,6 +317,12 @@ def is_circular(self):
314317        """ 
315318        return  False 
316319
320+     def  units (self , varname ):
321+         with  Dataset (self .filename ) as  h :
322+             var  =  h .variables [varname ]
323+             if  hasattr (var , 'units' ):
324+                 return  var .units 
325+ 
317326    def  grid (self , varname ):
318327        """give grid required 
319328        """ 
@@ -351,8 +360,10 @@ def bounds(self):
351360        """ 
352361        return  self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
353362
354-     def  eddy_identification (self , grid_height , uname , vname , step = 0.005 , shape_error = 55 ,
363+     def  eddy_identification (self , grid_height , uname , vname , date ,  step = 0.005 , shape_error = 55 ,
355364                            array_sampling = 50 , pixel_limit = None , bbox_surface_min_degree = .125 ** 2 ):
365+         if  not  isinstance (date , datetime ):
366+             raise  Exception ('Date argument be a datetime object' )
356367        # The inf limit must be in pixel and  sup limit in surface 
357368        if  pixel_limit  is  None :
358369            pixel_limit  =  (4 , 1000 )
@@ -459,8 +470,7 @@ def eddy_identification(self, grid_height, uname, vname, step=0.005, shape_error
459470                    # Instantiate new EddyObservation object 
460471                    properties  =  EddiesObservations (
461472                        size = 1 ,
462-                         track_extra_variables = ['shape_error_e' , 'shape_error_s' , 'height_max_speed_contour' ,
463-                                                'height_external_contour' , 'height_inner_contour' , 'nb_contour_selected' ],
473+                         track_extra_variables = [ 'height_max_speed_contour' , 'height_external_contour' , 'height_inner_contour' ],
464474                        track_array_variables = array_sampling ,
465475                        array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' , 'uavg_profile' ]
466476                    )
@@ -492,7 +502,23 @@ def eddy_identification(self, grid_height, uname, vname, step=0.005, shape_error
492502                    eddies .append (properties )
493503                    # To reserve definitively the area 
494504                    data .mask [i_x_in , i_y_in ] =  True 
495-             a_and_c .append (EddiesObservations .concatenate (eddies ))
505+             if  len (eddies ) ==  0 :
506+                 eddies_collection  =  EddiesObservations ()
507+             else :
508+                 eddies_collection  =  EddiesObservations .concatenate (eddies )
509+             eddies_collection .sign_type  =  1  if  anticyclonic_search  else  - 1 
510+             eddies_collection .obs ['time' ] =  (date  -  datetime (1950 , 1 , 1 )).total_seconds () /  86400. 
511+ 
512+             a_and_c .append (eddies_collection )
513+         h_units  =  self .units (grid_height )
514+         units  =  UnitRegistry ()
515+         in_unit  =  units .parse_expression (h_units )
516+         if  in_unit  is  not None :
517+             for  name  in  ['amplitude' , 'height_max_speed_contour' , 'height_external_contour' , 'height_inner_contour' ]:
518+                 out_unit  =  units .parse_expression (VAR_DESCR [name ]['nc_attr' ]['units' ])
519+                 factor , _  =  in_unit .to (out_unit ).to_tuple ()
520+                 a_and_c [0 ].obs [name ] *=  factor 
521+                 a_and_c [1 ].obs [name ] *=  factor 
496522        return  a_and_c 
497523
498524    def  get_uavg (self , all_contours , centlon_e , centlat_e , original_contour , anticyclonic_search , level_start ,
@@ -528,7 +554,6 @@ def get_uavg(self, all_contours, centlon_e, centlat_e, original_contour, anticyc
528554            level_contour .pixels_in (self )
529555            if  pixel_min  >  level_contour .nb_pixel :
530556                break 
531- 
532557            # Interpolate uspd to seglon, seglat, then get mean 
533558            level_average_speed  =  self .speed_coef (level_contour ).mean ()
534559            speed_array .append (level_average_speed )
@@ -851,6 +876,10 @@ def kernel_bessel(self, lat, wave_length, order=1):
851876        # Estimate size of kernel 
852877        step_y_km  =  self .ystep  *  distance (0 , 0 , 0 , 1 ) /  1000 
853878        step_x_km  =  self .xstep  *  distance (0 , lat , 1 , lat ) /  1000 
879+         min_wave_length  =  max (step_x_km  *  2 , step_y_km  *  2 )
880+         if  wave_length  <  min_wave_length :
881+             logging .error ('Wave_length to short for resolution, must be > %d km' , ceil (min_wave_length ))
882+             raise  Exception ()
854883        # half size will be multiply with by order 
855884        half_x_pt , half_y_pt  =  ceil (wave_length  /  step_x_km ).astype (int ), ceil (wave_length  /  step_y_km ).astype (int )
856885
@@ -876,7 +905,7 @@ def kernel_bessel(self, lat, wave_length, order=1):
876905        kernel [dist_norm  >  order ] =  0 
877906        return  kernel 
878907
879-     def  convolve_filter_with_dynamic_kernel (self , grid_name , kernel_func , lat_max , ** kwargs_func ):
908+     def  convolve_filter_with_dynamic_kernel (self , grid_name , kernel_func , lat_max = 85 , ** kwargs_func ):
880909        logging .warning ('No filtering above %f degrees of latitude' , lat_max )
881910        data  =  self .grid (grid_name ).copy ()
882911        # Matrix for result 
@@ -943,6 +972,14 @@ def lanczos_low_filter(self, grid_name, wave_length, order=1, lat_max=85):
943972            grid_name , self .kernel_lanczos , lat_max = lat_max , wave_length = wave_length , order = order )
944973        self .vars [grid_name ] =  data_out 
945974
975+     def  bessel_band_filter (self , grid_name , wave_length_inf , wave_length_sup , ** kwargs ):
976+         data_out  =  self .convolve_filter_with_dynamic_kernel (
977+             grid_name , self .kernel_bessel , wave_length = wave_length_inf , ** kwargs )
978+         self .vars [grid_name ] =  data_out 
979+         data_out  =  self .convolve_filter_with_dynamic_kernel (
980+             grid_name , self .kernel_bessel , wave_length = wave_length_sup , ** kwargs )
981+         self .vars [grid_name ] -=  data_out 
982+ 
946983    def  bessel_high_filter (self , grid_name , wave_length , order = 1 , lat_max = 85 ):
947984        data_out  =  self .convolve_filter_with_dynamic_kernel (
948985            grid_name , self .kernel_bessel , lat_max = lat_max , wave_length = wave_length , order = order )
@@ -953,6 +990,62 @@ def bessel_low_filter(self, grid_name, wave_length, order=1, lat_max=85):
953990            grid_name , self .kernel_bessel , lat_max = lat_max , wave_length = wave_length , order = order )
954991        self .vars [grid_name ] =  data_out 
955992
993+     def  spectrum_lonlat (self , grid_name , area = None , ref = None , ** kwargs ):
994+         if  area  is  None :
995+             area  =  dict (llcrnrlon = 190 , urcrnrlon = 280 , llcrnrlat = - 62 , urcrnrlat = 8 )
996+         scaling  =  kwargs .pop ('scaling' , 'density' )
997+         x0 , y0  =  self .nearest_grd_indice (area ['llcrnrlon' ], area ['llcrnrlat' ])
998+         x1 , y1  =  self .nearest_grd_indice (area ['urcrnrlon' ], area ['urcrnrlat' ])
999+ 
1000+         data  =  self .grid (grid_name )[x0 :x1 ,y0 :y1 ]
1001+ 
1002+         # Lat spectrum 
1003+         pws  =  list ()
1004+         step_y_km  =  self .ystep  *  distance (0 , 0 , 0 , 1 ) /  1000 
1005+         nb_invalid  =  0 
1006+         for  i , _  in  enumerate (self .x_c [x0 :x1 ]):
1007+             f , pw  =  welch (data [i ,:],  1  /  step_y_km , scaling = scaling , ** kwargs )
1008+             if  isnan (pw ).any ():
1009+                 nb_invalid  +=  1 
1010+                 continue 
1011+             pws .append (pw )
1012+         if  nb_invalid :
1013+             logging .warning ('%d/%d columns invalid' , nb_invalid , i  +  1 )
1014+         lat_content  =  1  /  f , array (pws ).mean (axis = 0 )
1015+ 
1016+         # Lon spectrum 
1017+         fs , pws  =  list (), list ()
1018+         f_min , f_max  =  None , None 
1019+         nb_invalid  =  0 
1020+         for  i , lat  in  enumerate (self .y_c [y0 :y1 ]):
1021+             step_x_km  =  self .xstep  *  distance (0 , lat , 1 , lat ) /  1000 
1022+             f , pw  =  welch (data [:,i ], 1  /  step_x_km , scaling = scaling , ** kwargs )
1023+             if  isnan (pw ).any ():
1024+                 nb_invalid  +=  1 
1025+                 continue 
1026+             if  f_min  is  None :
1027+                 f_min  =  f .min ()
1028+                 f_max  =  f .max ()
1029+             else :
1030+                 f_min  =  max (f_min , f .min ())
1031+                 f_max  =  min (f_max , f .max ())
1032+             fs .append (f )
1033+             pws .append (pw )
1034+         if  nb_invalid :
1035+             logging .warning ('%d/%d lines invalid' , nb_invalid , i  +  1 )
1036+         f_interp  =  linspace (f_min , f_max , f .shape [0 ])
1037+         pw_m  =  array (
1038+             [interp1d (f , pw , fill_value = 0. , bounds_error = False )(f_interp ) for  f , pw  in  zip (fs , pws )]).mean (axis = 0 )
1039+         lon_content  =  1  /  f_interp , pw_m 
1040+         if  ref  is  None :
1041+             return  lon_content , lat_content 
1042+         else :
1043+             ref_lon_content , ref_lat_content  =  ref .spectrum_lonlat (grid_name , area , ** kwargs )
1044+             return  (lon_content [0 ], lon_content [1 ] /  ref_lon_content [1 ]), \
1045+                    (lat_content [0 ], lat_content [1 ] /  ref_lat_content [1 ])
1046+ 
1047+ 
1048+ 
9561049    def  add_uv (self , grid_height ):
9571050        """Compute a u and v grid 
9581051        """ 
0 commit comments