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 , isnan
7+ linspace , errstate , int_ , column_stack , interp , meshgrid , nan , ceil , sinc , float64 , isnan , \
8+ floor
89from datetime import datetime
910from scipy .special import j1
1011from netCDF4 import Dataset
1112from scipy .ndimage import gaussian_filter , convolve
1213from scipy .interpolate import RectBivariateSpline , interp1d
1314from scipy .spatial import cKDTree
1415from scipy .signal import welch , convolve2d
16+ from numba import jit
1517from matplotlib .path import Path as BasePath
1618from matplotlib .contour import QuadContourSet as BaseQuadContourSet
1719from pyproj import Proj
@@ -132,11 +134,27 @@ def _fit_circle_path(self):
132134
133135
134136def pixels_in (self , grid ):
137+ if not hasattr (self , '_slice' ):
138+ self ._slice = grid .bbox_indice (self .vertices )
135139 if not hasattr (self , '_pixels_in' ):
136140 self ._pixels_in = grid .get_pixels_in (self )
137141 return self ._pixels_in
138142
139143
144+ @property
145+ def bbox_slice (self ):
146+ if not hasattr (self , '_slice' ):
147+ raise Exception ('No pixels_in call before!' )
148+ return self ._slice
149+
150+
151+ @property
152+ def pixels_index (self ):
153+ if not hasattr (self , '_slice' ):
154+ raise Exception ('No pixels_in call before!' )
155+ return self ._pixels_in
156+
157+
140158@property
141159def nb_pixel (self ):
142160 if not hasattr (self , '_pixels_in' ):
@@ -145,6 +163,8 @@ def nb_pixel(self):
145163
146164
147165BasePath .pixels_in = pixels_in
166+ BasePath .pixels_index = pixels_index
167+ BasePath .bbox_slice = bbox_slice
148168BasePath .nb_pixel = nb_pixel
149169
150170
@@ -379,10 +399,10 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
379399 levels = arange (z_min - z_min % step , z_max - z_max % step + 2 * step , step )
380400
381401 # Get x and y values
382- x , y = self .vars [ self . coordinates [ 0 ]] , self .vars [ self . coordinates [ 1 ]]
402+ x , y = self .x_c , self .y_c
383403
384404 # Compute ssh contour
385- contours = Contours (x , y , data , levels , bbox_surface_min_degree = bbox_surface_min_degree )
405+ contours = Contours (x , y , data , levels , bbox_surface_min_degree = bbox_surface_min_degree , wrap_x = self . is_circular () )
386406
387407 # Compute cyclonic and anticylonic research:
388408 a_and_c = list ()
@@ -432,7 +452,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
432452 continue
433453
434454 # Compute amplitude
435- reset_centroid , amp = self .get_amplitude (i_x_in , i_y_in , cvalues , data ,
455+ reset_centroid , amp = self .get_amplitude (current_contour , cvalues , data ,
436456 anticyclonic_search = anticyclonic_search ,
437457 level = contours .levels [corrected_coll_index ], step = step )
438458
@@ -441,7 +461,10 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
441461 continue
442462
443463 if reset_centroid :
444- centi = reset_centroid [0 ]
464+ if self .is_circular ():
465+ centi = self .normalize_x_indice (reset_centroid [0 ])
466+ else :
467+ centi = reset_centroid [0 ]
445468 centj = reset_centroid [1 ]
446469 # To move in regular and unregular grid
447470 if len (x .shape ) == 1 :
@@ -584,12 +607,11 @@ def _gaussian_filter(data, sigma, mode='reflect'):
584607 return ma .array (v / w , mask = w == 0 )
585608
586609 @staticmethod
587- def get_amplitude (i_x_in , i_y_in , contour_height , data , anticyclonic_search = True , level = None , step = None ):
610+ def get_amplitude (contour , contour_height , data , anticyclonic_search = True , level = None , step = None ):
588611 # Instantiate Amplitude object
589612 amp = Amplitude (
590613 # Indices of all pixels in contour
591- i_contour_x = i_x_in ,
592- i_contour_y = i_y_in ,
614+ contour = contour ,
593615 # Height of level
594616 contour_height = contour_height ,
595617 # All grid
@@ -621,7 +643,7 @@ def bbox_indice(self, vertices):
621643 return slice (i_x .min () - self .N , i_x .max () + self .N + 1 ), slice (i_y .min () - self .N , i_y .max () + self .N + 1 )
622644
623645 def get_pixels_in (self , contour ):
624- slice_x , slice_y = self . bbox_indice ( contour .vertices )
646+ slice_x , slice_y = contour .bbox_slice
625647 pts = array ((self .x_c [slice_x , slice_y ].reshape (- 1 ),
626648 self .y_c [slice_x , slice_y ].reshape (- 1 ))).T
627649 mask = contour .contains_points (pts ).reshape ((slice_x .stop - slice_x .start , - 1 ))
@@ -630,6 +652,10 @@ def get_pixels_in(self, contour):
630652 i_y += slice_y .start
631653 return i_x , i_y
632654
655+ def normalize_x_indice (self , indices ):
656+ """Not do"""
657+ return indices
658+
633659 def nearest_grd_indice (self , x , y ):
634660 dist , idx = self .index_interp .query ((x , y ), k = 1 )
635661 i_y = idx % self .x_c .shape [1 ]
@@ -705,8 +731,15 @@ class RegularGridDataset(GridDataset):
705731
706732 __slots__ = (
707733 '_speed_ev' ,
734+ '_is_circular' ,
735+ 'x_size' ,
708736 )
709737
738+ def __init__ (self , * args , ** kwargs ):
739+ super (RegularGridDataset , self ).__init__ (* args , ** kwargs )
740+ self ._is_circular = None
741+ self .x_size = self .x_c .shape [0 ]
742+
710743 def init_pos_interpolator (self ):
711744 """Create function to have a quick index interpolator
712745 """
@@ -724,27 +757,28 @@ def bbox_indice(self, vertices):
724757 return slice_x , slice_y
725758
726759 def get_pixels_in (self , contour ):
727- slice_x , slice_y = self .bbox_indice (contour .vertices )
728- x , y = meshgrid (self .x_c [slice_x ], self .y_c [slice_y ])
760+ slice_x , slice_y = contour .bbox_slice
761+ if slice_x .stop < slice_x .start :
762+ x_ref = contour .vertices [0 , 0 ]
763+ x_array = (concatenate ((self .x_c [slice_x .start :], self .x_c [:slice_x .stop ])) - x_ref + 180 ) % 360 + x_ref - 180
764+ else :
765+ x_array = self .x_c [slice_x ]
766+ x , y = meshgrid (x_array , self .y_c [slice_y ])
729767 pts = array ((x .reshape (- 1 ), y .reshape (- 1 ))).T
730768 mask = contour .contains_points (pts ).reshape (x .shape )
731769 i_x , i_y = where (mask .T )
732770 i_x += slice_x .start
733771 i_y += slice_y .start
772+ if slice_x .stop < slice_x .start :
773+ i_x %= self .x_size
734774 return i_x , i_y
735775
736- def nearest_grd_indice (self , x , y ):
737- """
738- Can use this version, which are faster without check
739- from numpy.core.multiarray import interp
740- Args:
741- x:
742- y:
743-
744- Returns:
776+ def normalize_x_indice (self , indices ):
777+ return indices % self .x_size
745778
746- """
747- return round (interp (x , self .x_bounds , self .xinterp )), round (interp (y , self .y_bounds , self .yinterp ))
779+ def nearest_grd_indice (self , x , y ):
780+ return int32 (floor (((x - self .x_c [0 ] + self .xstep ) % 360 ) // self .xstep )), \
781+ int32 (floor (((y - self .y_c [0 ] + self .ystep ) % 360 ) // self .ystep ))
748782
749783 @property
750784 def xstep (self ):
@@ -764,6 +798,7 @@ def compute_pixel_path(self, x0, y0, x1, y1):
764798 # First x of grid
765799 x_ori = self .x_var [0 ]
766800 # Float index
801+ # ?? Normaly not callable
767802 f_x0 = self .xinterp ((x0 - x_ori ) % 360 + x_ori )
768803 f_x1 = self .xinterp ((x1 - x_ori ) % 360 + x_ori )
769804 f_y0 = self .yinterp (y0 )
@@ -832,7 +867,9 @@ def clean_land(self):
832867 def is_circular (self ):
833868 """Check if grid is circular
834869 """
835- return abs ((self .x_bounds [0 ] % 360 ) - (self .x_bounds [- 1 ] % 360 )) < 0.0001
870+ if self ._is_circular is None :
871+ self ._is_circular = abs ((self .x_bounds [0 ] % 360 ) - (self .x_bounds [- 1 ] % 360 )) < 0.0001
872+ return self ._is_circular
836873
837874 def kernel_lanczos (self , lat , wave_length , order = 1 ):
838875 # Not really operational
@@ -1044,8 +1081,6 @@ def spectrum_lonlat(self, grid_name, area=None, ref=None, **kwargs):
10441081 return (lon_content [0 ], lon_content [1 ] / ref_lon_content [1 ]), \
10451082 (lat_content [0 ], lat_content [1 ] / ref_lat_content [1 ])
10461083
1047-
1048-
10491084 def add_uv (self , grid_height ):
10501085 """Compute a u and v grid
10511086 """
@@ -1107,3 +1142,44 @@ def init_speed_coef(self, uname='u', vname='v'):
11071142 # Evaluation near masked value will be smoothed to 0 !!!, not perfect
11081143 speed [speed .mask ] = 0
11091144 self ._speed_ev = RectBivariateSpline (self .x_c , self .y_c , speed , kx = 1 , ky = 1 ).ev
1145+
1146+ def interp (self , grid_name , lons , lats ):
1147+ """
1148+ Compute z over lons, lats
1149+ Args:
1150+ grid_name: Grid which will be interp
1151+ lons: new x
1152+ lats: new y
1153+
1154+ Returns:
1155+ new z
1156+ """
1157+ z = empty (lons .shape , dtype = 'f4' ).reshape (- 1 )
1158+ interp_numba (
1159+ self .x_c .astype ('f4' ),
1160+ self .y_c .astype ('f4' ),
1161+ self .grid (grid_name ).astype ('f4' ),
1162+ lons .reshape (- 1 ).astype ('f4' ),
1163+ lats .reshape (- 1 ).astype ('f4' ),
1164+ z ,
1165+ )
1166+ return z
1167+
1168+
1169+ @jit ("void(f4[:], f4[:], f4[:,:], f4[:], f4[:], f4[:])" , nopython = True )
1170+ def interp_numba (x_g , y_g , z , x , y , dest_z ):
1171+ x_ref = x_g [0 ]
1172+ y_ref = y_g [0 ]
1173+ x_step = x_g [1 ] - x_ref
1174+ y_step = y_g [1 ] - y_ref
1175+ for i in range (x .size ):
1176+ x_ = (x [i ] - x_ref ) / x_step
1177+ y_ = (y [i ] - y_ref ) / y_step
1178+ i0 = int (floor (x_ ))
1179+ i1 = i0 + 1
1180+ j0 = int (floor (y_ ))
1181+ j1 = j0 + 1
1182+ xd = (x_ - i0 )
1183+ yd = (y_ - j0 )
1184+ dest_z [i ] = (z [i0 , j0 ] * (1 - xd ) + (z [i1 , j0 ] * xd )) * (1 - yd ) + (z [i0 , j1 ] * (1 - xd ) + z [i1 , j1 ] * xd ) * yd
1185+
0 commit comments