5
5
from scipy .interpolate import interp1d
6
6
from numpy import concatenate , int32 , empty , maximum , where , array , \
7
7
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
9
9
from netCDF4 import Dataset
10
10
from scipy .ndimage import gaussian_filter , convolve
11
11
from scipy .interpolate import RectBivariateSpline
@@ -277,6 +277,7 @@ def load(self):
277
277
self .vars [y_name ] = h .variables [y_name ][:]
278
278
279
279
if self .is_centered :
280
+ logging .info ('Grid center' )
280
281
self .x_c = self .vars [x_name ]
281
282
self .y_c = self .vars [y_name ]
282
283
@@ -346,122 +347,142 @@ def bounds(self):
346
347
"""
347
348
return self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
348
349
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
350
353
if pixel_limit is None :
351
354
pixel_limit = (8 , 1000 )
352
355
353
- self .init_speed_coef ()
356
+ # Compute an interpolator for eke
357
+ self .init_speed_coef (uname , vname )
354
358
359
+ # Get h grid
355
360
data = self .grid (grid_height )
356
- z_min , z_max = data .min (), data .max ()
357
361
362
+ # Compute levels for ssh
363
+ z_min , z_max = data .min (), data .max ()
358
364
levels = arange (z_min - z_min % step , z_max - z_max % step + 2 * step , step )
359
365
366
+ # Get x and y values
360
367
x , y = self .vars [self .coordinates [0 ]], self .vars [self .coordinates [1 ]]
361
368
362
- eddies = list ()
369
+ # Compute ssh contour
363
370
contours = Contours (x , y , data , levels )
364
371
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 :
388
387
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 )
396
391
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 )
404
407
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
406
415
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 )
410
418
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
413
422
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 )
417
427
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
465
486
466
487
@staticmethod
467
488
def _gaussian_filter (data , sigma , mode = 'reflect' ):
@@ -471,7 +492,7 @@ def _gaussian_filter(data, sigma, mode='reflect'):
471
492
local_data [data .mask ] = 0
472
493
473
494
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 )
475
496
476
497
with errstate (invalid = 'ignore' ):
477
498
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
492
513
493
514
if anticyclonic_search :
494
515
reset_centroid = amp .all_pixels_above_h0 (level )
495
-
496
516
else :
497
517
reset_centroid = amp .all_pixels_below_h0 (level )
498
518
@@ -597,16 +617,48 @@ class RegularGridDataset(GridDataset):
597
617
"""Class only for regular grid
598
618
"""
599
619
600
- __slots__ = ()
620
+ __slots__ = (
621
+ '_speed_ev' ,
622
+ )
601
623
602
624
def init_pos_interpolator (self ):
603
625
"""Create function to have a quick index interpolator
604
626
"""
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
607
649
608
650
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 ))
610
662
611
663
@property
612
664
def xstep (self ):
@@ -761,8 +813,12 @@ def add_uv(self, grid_height):
761
813
d_x = self .EARTH_RADIUS * 2 * pi / 360 * d_x_degrees * cos (deg2rad (self .y_c ))
762
814
self .vars ['v' ] = d_hx / d_x * gof
763
815
816
+ def speed_coef (self , contour ):
817
+ lon , lat = contour .regular_coordinates [1 :].T
818
+ return self ._speed_ev (lon , lat )
819
+
764
820
def init_speed_coef (self , uname = 'u' , vname = 'v' ):
765
821
"""Draft
766
822
"""
767
823
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