Skip to content

Commit 07886f1

Browse files
committed
Correct problem of pixel index around bounds
1 parent 3ae69e1 commit 07886f1

File tree

3 files changed

+209
-60
lines changed

3 files changed

+209
-60
lines changed

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
'shapely',
4646
'pyyaml',
4747
'pyproj',
48-
'pint'
48+
'pint',
49+
'numba',
4950
],
5051
)

src/py_eddy_tracker/dataset/grid.py

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
import logging
55
from 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
89
from datetime import datetime
910
from scipy.special import j1
1011
from netCDF4 import Dataset
1112
from scipy.ndimage import gaussian_filter, convolve
1213
from scipy.interpolate import RectBivariateSpline, interp1d
1314
from scipy.spatial import cKDTree
1415
from scipy.signal import welch, convolve2d
16+
from numba import jit
1517
from matplotlib.path import Path as BasePath
1618
from matplotlib.contour import QuadContourSet as BaseQuadContourSet
1719
from pyproj import Proj
@@ -132,11 +134,27 @@ def _fit_circle_path(self):
132134

133135

134136
def 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
141159
def nb_pixel(self):
142160
if not hasattr(self, '_pixels_in'):
@@ -145,6 +163,8 @@ def nb_pixel(self):
145163

146164

147165
BasePath.pixels_in = pixels_in
166+
BasePath.pixels_index = pixels_index
167+
BasePath.bbox_slice = bbox_slice
148168
BasePath.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

Comments
 (0)