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 , isnan
7
+ linspace , errstate , int_ , column_stack , interp , meshgrid , nan , ceil , sinc , float64 , isnan , \
8
+ floor
8
9
from datetime import datetime
9
10
from scipy .special import j1
10
11
from netCDF4 import Dataset
11
12
from scipy .ndimage import gaussian_filter , convolve
12
13
from scipy .interpolate import RectBivariateSpline , interp1d
13
14
from scipy .spatial import cKDTree
14
15
from scipy .signal import welch , convolve2d
16
+ from numba import jit
15
17
from matplotlib .path import Path as BasePath
16
18
from matplotlib .contour import QuadContourSet as BaseQuadContourSet
17
19
from pyproj import Proj
@@ -132,11 +134,27 @@ def _fit_circle_path(self):
132
134
133
135
134
136
def pixels_in (self , grid ):
137
+ if not hasattr (self , '_slice' ):
138
+ self ._slice = grid .bbox_indice (self .vertices )
135
139
if not hasattr (self , '_pixels_in' ):
136
140
self ._pixels_in = grid .get_pixels_in (self )
137
141
return self ._pixels_in
138
142
139
143
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
+
140
158
@property
141
159
def nb_pixel (self ):
142
160
if not hasattr (self , '_pixels_in' ):
@@ -145,6 +163,8 @@ def nb_pixel(self):
145
163
146
164
147
165
BasePath .pixels_in = pixels_in
166
+ BasePath .pixels_index = pixels_index
167
+ BasePath .bbox_slice = bbox_slice
148
168
BasePath .nb_pixel = nb_pixel
149
169
150
170
@@ -379,10 +399,10 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
379
399
levels = arange (z_min - z_min % step , z_max - z_max % step + 2 * step , step )
380
400
381
401
# 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
383
403
384
404
# 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 () )
386
406
387
407
# Compute cyclonic and anticylonic research:
388
408
a_and_c = list ()
@@ -432,7 +452,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
432
452
continue
433
453
434
454
# 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 ,
436
456
anticyclonic_search = anticyclonic_search ,
437
457
level = contours .levels [corrected_coll_index ], step = step )
438
458
@@ -441,7 +461,10 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
441
461
continue
442
462
443
463
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 ]
445
468
centj = reset_centroid [1 ]
446
469
# To move in regular and unregular grid
447
470
if len (x .shape ) == 1 :
@@ -584,12 +607,11 @@ def _gaussian_filter(data, sigma, mode='reflect'):
584
607
return ma .array (v / w , mask = w == 0 )
585
608
586
609
@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 ):
588
611
# Instantiate Amplitude object
589
612
amp = Amplitude (
590
613
# Indices of all pixels in contour
591
- i_contour_x = i_x_in ,
592
- i_contour_y = i_y_in ,
614
+ contour = contour ,
593
615
# Height of level
594
616
contour_height = contour_height ,
595
617
# All grid
@@ -621,7 +643,7 @@ def bbox_indice(self, vertices):
621
643
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 )
622
644
623
645
def get_pixels_in (self , contour ):
624
- slice_x , slice_y = self . bbox_indice ( contour .vertices )
646
+ slice_x , slice_y = contour .bbox_slice
625
647
pts = array ((self .x_c [slice_x , slice_y ].reshape (- 1 ),
626
648
self .y_c [slice_x , slice_y ].reshape (- 1 ))).T
627
649
mask = contour .contains_points (pts ).reshape ((slice_x .stop - slice_x .start , - 1 ))
@@ -630,6 +652,10 @@ def get_pixels_in(self, contour):
630
652
i_y += slice_y .start
631
653
return i_x , i_y
632
654
655
+ def normalize_x_indice (self , indices ):
656
+ """Not do"""
657
+ return indices
658
+
633
659
def nearest_grd_indice (self , x , y ):
634
660
dist , idx = self .index_interp .query ((x , y ), k = 1 )
635
661
i_y = idx % self .x_c .shape [1 ]
@@ -705,8 +731,15 @@ class RegularGridDataset(GridDataset):
705
731
706
732
__slots__ = (
707
733
'_speed_ev' ,
734
+ '_is_circular' ,
735
+ 'x_size' ,
708
736
)
709
737
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
+
710
743
def init_pos_interpolator (self ):
711
744
"""Create function to have a quick index interpolator
712
745
"""
@@ -724,27 +757,28 @@ def bbox_indice(self, vertices):
724
757
return slice_x , slice_y
725
758
726
759
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 ])
729
767
pts = array ((x .reshape (- 1 ), y .reshape (- 1 ))).T
730
768
mask = contour .contains_points (pts ).reshape (x .shape )
731
769
i_x , i_y = where (mask .T )
732
770
i_x += slice_x .start
733
771
i_y += slice_y .start
772
+ if slice_x .stop < slice_x .start :
773
+ i_x %= self .x_size
734
774
return i_x , i_y
735
775
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
745
778
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 ))
748
782
749
783
@property
750
784
def xstep (self ):
@@ -764,6 +798,7 @@ def compute_pixel_path(self, x0, y0, x1, y1):
764
798
# First x of grid
765
799
x_ori = self .x_var [0 ]
766
800
# Float index
801
+ # ?? Normaly not callable
767
802
f_x0 = self .xinterp ((x0 - x_ori ) % 360 + x_ori )
768
803
f_x1 = self .xinterp ((x1 - x_ori ) % 360 + x_ori )
769
804
f_y0 = self .yinterp (y0 )
@@ -832,7 +867,9 @@ def clean_land(self):
832
867
def is_circular (self ):
833
868
"""Check if grid is circular
834
869
"""
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
836
873
837
874
def kernel_lanczos (self , lat , wave_length , order = 1 ):
838
875
# Not really operational
@@ -1044,8 +1081,6 @@ def spectrum_lonlat(self, grid_name, area=None, ref=None, **kwargs):
1044
1081
return (lon_content [0 ], lon_content [1 ] / ref_lon_content [1 ]), \
1045
1082
(lat_content [0 ], lat_content [1 ] / ref_lat_content [1 ])
1046
1083
1047
-
1048
-
1049
1084
def add_uv (self , grid_height ):
1050
1085
"""Compute a u and v grid
1051
1086
"""
@@ -1107,3 +1142,44 @@ def init_speed_coef(self, uname='u', vname='v'):
1107
1142
# Evaluation near masked value will be smoothed to 0 !!!, not perfect
1108
1143
speed [speed .mask ] = 0
1109
1144
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