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