55from numpy import concatenate , int32 , empty , where , array , \
66 sin , deg2rad , pi , ones , cos , ma , int8 , histogram2d , arange , float_ , \
77 linspace , errstate , int_ , interp , meshgrid , nan , ceil , sinc , isnan , \
8- percentile , zeros , arctan2 , arcsin , round_
8+ percentile , zeros , arctan2 , arcsin , round_ , nanmean
99from datetime import datetime
1010from scipy .special import j1
1111from netCDF4 import Dataset
@@ -71,10 +71,14 @@ def value_on_regular_contour(x_g, y_g, z_g, m_g, vertices, num_fac=2, fixed_size
7171
7272
7373@njit (cache = True )
74- def mean_on_regular_contour (x_g , y_g , z_g , m_g , vertices , num_fac = 2 , fixed_size = None ):
74+ def mean_on_regular_contour (x_g , y_g , z_g , m_g , vertices , num_fac = 2 , fixed_size = None , nan_remove = False ):
7575 x_val , y_val = vertices [:, 0 ], vertices [:, 1 ]
7676 x_new , y_new = uniform_resample (x_val , y_val , num_fac , fixed_size )
77- return interp2d_geo (x_g , y_g , z_g , m_g , x_new [1 :], y_new [1 :]).mean ()
77+ values = interp2d_geo (x_g , y_g , z_g , m_g , x_new [1 :], y_new [1 :])
78+ if nan_remove :
79+ return nanmean (values )
80+ else :
81+ return values .mean ()
7882
7983
8084def fit_circle_path (self ):
@@ -415,7 +419,7 @@ def bounds(self):
415419 return self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
416420
417421 def eddy_identification (self , grid_height , uname , vname , date , step = 0.005 , shape_error = 55 ,
418- array_sampling = 50 , pixel_limit = None , bbox_surface_min_degree = .125 ** 2 ):
422+ array_sampling = 50 , pixel_limit = None ):
419423 if not isinstance (date , datetime ):
420424 raise Exception ('Date argument be a datetime object' )
421425 # The inf limit must be in pixel and sup limit in surface
@@ -446,7 +450,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
446450 x , y = self .x_c , self .y_c
447451
448452 # Compute ssh contour
449- self .contours = Contours (x , y , data , levels , bbox_surface_min_degree = bbox_surface_min_degree , wrap_x = self .is_circular ())
453+ self .contours = Contours (x , y , data , levels , wrap_x = self .is_circular ())
450454
451455 track_extra_variables = ['height_max_speed_contour' , 'height_external_contour' , 'height_inner_contour' ]
452456 array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' , 'uavg_profile' ]
@@ -525,7 +529,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
525529 # centlat_e and centlon_e must be index of maximum, we will loose some inner contour, if it's not
526530 max_average_speed , speed_contour , inner_contour , speed_array , i_max_speed , i_inner = \
527531 self .get_uavg (self .contours , centlon_e , centlat_e , current_contour , anticyclonic_search ,
528- corrected_coll_index )
532+ corrected_coll_index , pixel_min = pixel_limit [ 0 ] )
529533
530534 # Use azimuth equal projection for radius
531535 proj = Proj ('+proj=aeqd +ellps=WGS84 +lat_0={1} +lon_0={0}' .format (* inner_contour .mean_coordinates ))
@@ -606,7 +610,6 @@ def get_uavg(self, all_contours, centlon_e, centlat_e, original_contour, anticyc
606610 """
607611 max_average_speed = self .speed_coef_mean (original_contour )
608612 speed_array = [max_average_speed ]
609- pixel_min = 1
610613
611614 eddy_contours = [original_contour ]
612615 inner_contour = selected_contour = original_contour
@@ -619,22 +622,16 @@ def get_uavg(self, all_contours, centlon_e, centlat_e, original_contour, anticyc
619622 # Leave loop if no contours at level
620623 if level_contour is None :
621624 break
622- # 1. Ensure polygon_i contains point centlon_e, centlat_e (Maybe we loose some inner contour if eddy
623- # core are not centered)
624- # if winding_number_poly(centlon_e, centlat_e, level_contour.vertices) == 0:
625- # break
626- # 2. Ensure polygon_i is within polygon_e
625+ # Ensure polygon_i is within polygon_e
627626 if not poly_contain_poly (original_contour .vertices , level_contour .vertices ):
628627 break
629- # 3. Respect size range
628+ # 3. Respect size range (for max speed)
630629 # nb_pixel properties need call of pixels_in before with a grid of pixel
631630 level_contour .pixels_in (self )
632- if pixel_min > level_contour .nb_pixel :
633- break
634631 # Interpolate uspd to seglon, seglat, then get mean
635632 level_average_speed = self .speed_coef_mean (level_contour )
636633 speed_array .append (level_average_speed )
637- if level_average_speed >= max_average_speed :
634+ if pixel_min < level_contour . nb_pixel and level_average_speed >= max_average_speed :
638635 max_average_speed = level_average_speed
639636 i_max_speed = i
640637 selected_contour = level_contour
@@ -1065,19 +1062,118 @@ def spectrum_lonlat(self, grid_name, area=None, ref=None, **kwargs):
10651062 return (lon_content [0 ], lon_content [1 ] / ref_lon_content [1 ]), \
10661063 (lat_content [0 ], lat_content [1 ] / ref_lat_content [1 ])
10671064
1068- def add_uv (self , grid_height ):
1065+ def compute_grad (self , data , stencil_halfwidth = 4 , mode = 'reflect' , vertical = False ):
1066+ stencil_halfwidth = max (min (int (stencil_halfwidth ), 4 ), 1 )
1067+ logging .debug ('Stencil half width apply : %d' , stencil_halfwidth )
1068+ # output
1069+ grad = None
1070+
1071+ weights = [
1072+ array ((3 , - 32 , 168 , - 672 , 0 , 672 , - 168 , 32 , - 3 )) / 840. ,
1073+ array ((- 1 , 9 , - 45 , 0 , 45 , - 9 , 1 )) / 60. ,
1074+ array ((1 , - 8 , 0 , 8 , - 1 )) / 12. ,
1075+ array ((- 1 , 0 , 1 )) / 2. ,
1076+ # like array((0, -1, 1))
1077+ array ((- 1 , 1 )),
1078+ # like array((-1, 1, 0))
1079+ (1 , array ((- 1 , 1 ))),
1080+ ]
1081+ # reduce to stencil selected
1082+ weights = weights [4 - stencil_halfwidth :]
1083+ if vertical :
1084+ data = data .T
1085+ # Iteration from larger stencil to smaller (to fill matrix)
1086+ for weight in weights :
1087+ if isinstance (weight , tuple ):
1088+ # In the cas of unbalanced diff
1089+ shift , weight = weight
1090+ data_ = data .copy ()
1091+ data_ [shift :] = data [:- shift ]
1092+ if not vertical :
1093+ data_ [:shift ] = data [- shift :]
1094+ else :
1095+ data_ = data
1096+ # Delta h
1097+ d_h = convolve (data_ , weights = weight .reshape ((- 1 , 1 )), mode = mode )
1098+ mask = convolve (int8 (data_ .mask ), weights = ones (weight .shape ).reshape ((- 1 , 1 )), mode = mode )
1099+ d_h = ma .array (d_h , mask = mask != 0 )
1100+
1101+ # Delta d
1102+ if vertical :
1103+ d_h = d_h .T
1104+ d = self .EARTH_RADIUS * 2 * pi / 360 * convolve (self .y_c , weight )
1105+ else :
1106+ if mode == 'wrap' :
1107+ # Along x axis, we need to close
1108+ # we will compute in two part
1109+ x = self .x_c % 360
1110+ d_degrees = convolve (x , weight , mode = mode )
1111+ d_degrees_180 = convolve ((x + 180 ) % 360 - 180 , weight , mode = mode )
1112+ # Arbitrary, to be sure to be far far away of bound
1113+ m = (x < 50 ) + (x > 310 )
1114+ d_degrees [m ] = d_degrees_180 [m ]
1115+ d_degrees = d_degrees .reshape ((- 1 , 1 ))
1116+ else :
1117+ d_degrees = convolve (self .x_c , weight , mode = mode ).reshape ((- 1 , 1 ))
1118+ d = self .EARTH_RADIUS * 2 * pi / 360 * d_degrees * cos (deg2rad (self .y_c ))
1119+ if grad is None :
1120+ # First Gradient
1121+ grad = d_h / d
1122+ else :
1123+ # Fill hole
1124+ grad [grad .mask ] = (d_h / d )[grad .mask ]
1125+ return grad
1126+
1127+ def add_uv_lagerloef (self , grid_height , uname = 'u' , vname = 'v' , latmax = 5 ):
1128+ self .add_uv (grid_height , uname , vname )
1129+ logging .info ('Modified u/v with lagerloef 99 method abov %f' , latmax )
1130+ data = self .grid (grid_height )
1131+ # Divide by sideral day
1132+ gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
1133+ with errstate (divide = 'ignore' ):
1134+ gof = self .GRAVITY / (gof * ones ((self .x_c .shape [0 ], 1 )))
1135+
1136+ def add_uv2 (self , grid_height , uname = 'u2' , vname = 'v2' ):
10691137 """Compute a u and v grid
1070- """
1138+ """
1139+ logging .info ('Add u/v variable with stencil method' )
10711140 data = self .grid (grid_height )
10721141 h_dict = self .variables_description [grid_height ]
1073- for variable in ('u' , 'v' ):
1142+ for variable in (uname , vname ):
10741143 self .variables_description [variable ] = dict (
10751144 infos = h_dict ['infos' ].copy (),
10761145 attrs = h_dict ['attrs' ].copy (),
10771146 args = tuple ((variable , * h_dict ['args' ][1 :])),
10781147 kwargs = h_dict ['kwargs' ].copy (),
10791148 )
1149+ if 'units' in self .variables_description [variable ]['attrs' ]:
1150+ self .variables_description [variable ]['attrs' ]['units' ] += '/s'
1151+ if 'long_name' in self .variables_description [variable ]['attrs' ]:
1152+ self .variables_description [variable ]['attrs' ]['long_name' ] += ' gradient'
1153+ # Divide by sideral day
1154+ gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
1155+ with errstate (divide = 'ignore' ):
1156+ gof = self .GRAVITY / (gof * ones ((self .x_c .shape [0 ], 1 )))
10801157
1158+ # Compute v
1159+ mode = 'wrap' if self .is_circular () else 'reflect'
1160+ self .vars [vname ] = self .compute_grad (data , mode = mode ) * gof
1161+ # Compute u
1162+ self .vars [uname ] = - self .compute_grad (data , vertical = True ) * gof
1163+
1164+ def add_uv (self , grid_height , uname = 'u' , vname = 'v' ):
1165+ """Compute a u and v grid
1166+ """
1167+ logging .info ('Add u/v variable with stencil method' )
1168+ data = self .grid (grid_height )
1169+ h_dict = self .variables_description [grid_height ]
1170+ for variable in (uname , vname ):
1171+ self .variables_description [variable ] = dict (
1172+ infos = h_dict ['infos' ].copy (),
1173+ attrs = h_dict ['attrs' ].copy (),
1174+ args = tuple ((variable , * h_dict ['args' ][1 :])),
1175+ kwargs = h_dict ['kwargs' ].copy (),
1176+ )
10811177 self .variables_description [variable ]['attrs' ]['units' ] += '/s'
10821178 # Divide by sideral day
10831179 gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
@@ -1094,7 +1190,7 @@ def add_uv(self, grid_height):
10941190 ]
10951191
10961192 # Compute v
1097- self .vars ['v' ] = None
1193+ self .vars [vname ] = None
10981194 mode = 'wrap' if self .is_circular () else 'reflect'
10991195 for m_x in w :
11001196 if isinstance (m_x , tuple ):
@@ -1112,13 +1208,13 @@ def add_uv(self, grid_height):
11121208 d_x_degrees = (d_x_degrees + 180 ) % 360 - 180
11131209 d_x = self .EARTH_RADIUS * 2 * pi / 360 * d_x_degrees * cos (deg2rad (self .y_c ))
11141210 v = d_hx / d_x * gof
1115- if self .vars ['v' ] is None :
1116- self .vars ['v' ] = v
1211+ if self .vars [vname ] is None :
1212+ self .vars [vname ] = v
11171213 else :
1118- self .vars ['v' ][self .vars ['v' ].mask ] = v [self .vars ['v' ].mask ]
1214+ self .vars [vname ][self .vars [vname ].mask ] = v [self .vars [vname ].mask ]
11191215
11201216 # Compute u
1121- self .vars ['u' ] = None
1217+ self .vars [uname ] = None
11221218 for m_y in w :
11231219 if isinstance (m_y , tuple ):
11241220 shift , m_y = m_y
@@ -1134,19 +1230,17 @@ def add_uv(self, grid_height):
11341230 d_y = self .EARTH_RADIUS * 2 * pi / 360 * convolve (self .y_c , m_y )
11351231
11361232 u = - d_hy / d_y * gof
1137- if self .vars ['u' ] is None :
1138- self .vars ['u' ] = u
1233+ if self .vars [uname ] is None :
1234+ self .vars [uname ] = u
11391235 else :
1140- self .vars ['u' ][self .vars ['u' ].mask ] = u [self .vars ['u' ].mask ]
1236+ self .vars [uname ][self .vars [uname ].mask ] = u [self .vars [uname ].mask ]
11411237
11421238 def speed_coef_mean (self , contour ):
11431239 """some nan can be compute over contour if we are near border,
11441240 something to explore
11451241 """
11461242 return mean_on_regular_contour (
1147- self .x_c , self .y_c ,
1148- self ._speed_ev , self ._speed_ev .mask ,
1149- contour .vertices )
1243+ self .x_c , self .y_c , self ._speed_ev , self ._speed_ev .mask , contour .vertices , nan_remove = True )
11501244
11511245 def init_speed_coef (self , uname = 'u' , vname = 'v' ):
11521246 """Draft
0 commit comments