4
4
import logging
5
5
from numpy import concatenate , int32 , empty , maximum , where , array , \
6
6
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
8
9
from scipy .special import j1
9
- from scipy .signal import convolve2d
10
10
from netCDF4 import Dataset
11
11
from scipy .ndimage import gaussian_filter , convolve
12
- from scipy .interpolate import RectBivariateSpline
12
+ from scipy .interpolate import RectBivariateSpline , interp1d
13
13
from scipy .spatial import cKDTree
14
+ from scipy .signal import welch , convolve2d
14
15
from matplotlib .path import Path as BasePath
15
16
from matplotlib .contour import QuadContourSet as BaseQuadContourSet
16
17
from pyproj import Proj
18
+ from pint import UnitRegistry
17
19
from ..tools import fit_circle_c , distance_vector , winding_number_poly , poly_contain_poly , \
18
20
distance , distance_point_vector
19
21
from ..observations import EddiesObservations
20
22
from ..eddy_feature import Amplitude , Contours
23
+ from .. import VAR_DESCR
21
24
22
25
23
26
def raw_resample (datas , fixed_size ):
@@ -314,6 +317,12 @@ def is_circular(self):
314
317
"""
315
318
return False
316
319
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
+
317
326
def grid (self , varname ):
318
327
"""give grid required
319
328
"""
@@ -351,8 +360,10 @@ def bounds(self):
351
360
"""
352
361
return self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
353
362
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 ,
355
364
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' )
356
367
# The inf limit must be in pixel and sup limit in surface
357
368
if pixel_limit is None :
358
369
pixel_limit = (4 , 1000 )
@@ -459,8 +470,7 @@ def eddy_identification(self, grid_height, uname, vname, step=0.005, shape_error
459
470
# Instantiate new EddyObservation object
460
471
properties = EddiesObservations (
461
472
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' ],
464
474
track_array_variables = array_sampling ,
465
475
array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' , 'uavg_profile' ]
466
476
)
@@ -492,7 +502,23 @@ def eddy_identification(self, grid_height, uname, vname, step=0.005, shape_error
492
502
eddies .append (properties )
493
503
# To reserve definitively the area
494
504
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
496
522
return a_and_c
497
523
498
524
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
528
554
level_contour .pixels_in (self )
529
555
if pixel_min > level_contour .nb_pixel :
530
556
break
531
-
532
557
# Interpolate uspd to seglon, seglat, then get mean
533
558
level_average_speed = self .speed_coef (level_contour ).mean ()
534
559
speed_array .append (level_average_speed )
@@ -851,6 +876,10 @@ def kernel_bessel(self, lat, wave_length, order=1):
851
876
# Estimate size of kernel
852
877
step_y_km = self .ystep * distance (0 , 0 , 0 , 1 ) / 1000
853
878
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 ()
854
883
# half size will be multiply with by order
855
884
half_x_pt , half_y_pt = ceil (wave_length / step_x_km ).astype (int ), ceil (wave_length / step_y_km ).astype (int )
856
885
@@ -876,7 +905,7 @@ def kernel_bessel(self, lat, wave_length, order=1):
876
905
kernel [dist_norm > order ] = 0
877
906
return kernel
878
907
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 ):
880
909
logging .warning ('No filtering above %f degrees of latitude' , lat_max )
881
910
data = self .grid (grid_name ).copy ()
882
911
# Matrix for result
@@ -943,6 +972,14 @@ def lanczos_low_filter(self, grid_name, wave_length, order=1, lat_max=85):
943
972
grid_name , self .kernel_lanczos , lat_max = lat_max , wave_length = wave_length , order = order )
944
973
self .vars [grid_name ] = data_out
945
974
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
+
946
983
def bessel_high_filter (self , grid_name , wave_length , order = 1 , lat_max = 85 ):
947
984
data_out = self .convolve_filter_with_dynamic_kernel (
948
985
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):
953
990
grid_name , self .kernel_bessel , lat_max = lat_max , wave_length = wave_length , order = order )
954
991
self .vars [grid_name ] = data_out
955
992
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
+
956
1049
def add_uv (self , grid_height ):
957
1050
"""Compute a u and v grid
958
1051
"""
0 commit comments