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