Skip to content

Commit 6cceb97

Browse files
committed
Several update:
- Fix some bug around x bounds - Numba replace more cython function - Merge Grid dataset class - ...
1 parent 437ee6d commit 6cceb97

File tree

11 files changed

+621
-789
lines changed

11 files changed

+621
-789
lines changed

src/py_eddy_tracker/dataset/grid.py

Lines changed: 123 additions & 336 deletions
Large diffs are not rendered by default.

src/py_eddy_tracker/eddy_feature.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@
2727
"""
2828

2929
import logging
30-
from numpy import where, empty, array, concatenate, ma, zeros, unique, round, ones
31-
from scipy.ndimage import minimum_filter
30+
from numpy import empty, array, concatenate, ma, zeros, unique, round, ones, int_
3231
from matplotlib.figure import Figure
3332
from numba import njit, types as numba_types
34-
from .tools import index_from_nearest_path_with_pt_in_bbox
3533

3634

3735
class Amplitude(object):
@@ -468,7 +466,7 @@ def get_index_nearest_path_bbox_contain_pt(self, level, xpt, ypt):
468466
469467
overhead of python is huge with numba, cython little bit best??
470468
"""
471-
index = index_from_nearest_path_with_pt_in_bbox(
469+
index = index_from_nearest_path_with_pt_in_bbox_(
472470
level,
473471
self.level_index,
474472
self.nb_contour_per_level,
@@ -483,7 +481,7 @@ def get_index_nearest_path_bbox_contain_pt(self, level, xpt, ypt):
483481
xpt,
484482
ypt
485483
)
486-
if index is None:
484+
if index == -1:
487485
return None
488486
else:
489487
return self.contours.collections[level]._paths[index]
@@ -500,7 +498,7 @@ def display(self, ax, **kwargs):
500498
ax.autoscale_view()
501499

502500

503-
@njit(cache=True)
501+
@njit(cache=True, fastmath=True)
504502
def index_from_nearest_path_with_pt_in_bbox_(
505503
level_index,
506504
l_i,
@@ -537,7 +535,15 @@ def index_from_nearest_path_with_pt_in_bbox_(
537535
# We iterate over contour in the same level
538536
for i_elt_c in range(i_start_c, i_end_c):
539537
# if bbox of contour doesn't contain pt, we skip this contour
540-
if y_min_per_c[i_elt_c] > ypt or y_max_per_c[i_elt_c] < ypt or x_min_per_c[i_elt_c] > xpt or x_max_per_c[i_elt_c] < xpt:
538+
if y_min_per_c[i_elt_c] > ypt:
539+
continue
540+
if y_max_per_c[i_elt_c] < ypt:
541+
continue
542+
x_min = x_min_per_c[i_elt_c]
543+
xpt_ = (xpt - x_min) % 360 + x_min
544+
if x_min > xpt_:
545+
continue
546+
if x_max_per_c[i_elt_c] < xpt_:
541547
continue
542548
# Indice of first pt of contour
543549
i_start_pt = indices_of_first_pts[i_elt_c]
@@ -549,13 +555,15 @@ def index_from_nearest_path_with_pt_in_bbox_(
549555
# We do iteration on pt to check dist, if it's inferior we store
550556
# index of contour
551557
for i_elt_pt in range(i_start_pt, i_end_pt):
552-
dist = (x_value[i_elt_pt] - xpt) ** 2 + (y_value[i_elt_pt] - ypt) ** 2
558+
d_x = x_value[i_elt_pt] - xpt_
559+
if abs(d_x) > 180:
560+
d_x = (d_x + 180) % 360 - 180
561+
dist = d_x ** 2 + (y_value[i_elt_pt] - ypt) ** 2
553562
if dist < dist_ref:
554563
dist_ref = dist
555564
i_ref = i_elt_c
556565
# No iteration on contour, we return no index of contour
557566
if find_contour == 0:
558-
return -1
567+
return int_(-1)
559568
# We return index of contour, for the specific level
560-
return i_ref - i_start_c
561-
569+
return int_(i_ref - i_start_c)

src/py_eddy_tracker/featured_tracking/old_tracker_reference.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
from ..observations import EddiesObservations as Model
2-
from ..observations import GridDataset
2+
from ..dataset.grid import RegularGridDataset
33
from numpy import where, meshgrid, bincount, ones, unique, bool_, arange
4-
import logging
4+
from numba import njit
55
from os import path
66

77

88
class CheltonTracker(Model):
9-
GROUND = GridDataset(path.join(path.dirname(__file__), '/data/adelepoulle/Test/Test_eddy/20180220_high_res_mask/mask_1_60.nc'), 'lon', 'lat')
9+
GROUND = RegularGridDataset(path.join(path.dirname(__file__), '/data/adelepoulle/Test/Test_eddy/20180220_high_res_mask/mask_1_60.nc'), 'lon', 'lat')
1010

1111
@staticmethod
1212
def cost_function(records_in, records_out, distance):
1313
"""We minimize on distance between two obs
1414
"""
1515
return distance
1616

17-
def mask_function(self, other):
17+
def mask_function(self, other, distance):
1818
"""We mask link with ellips and ratio
1919
"""
2020
# Compute Parameter of ellips
@@ -34,18 +34,8 @@ def mask_function(self, other):
3434
minor=minor, # Minor can be bigger than major??
3535
major=y)
3636

37-
# We check ratio
38-
other_amplitude, self_amplitude = meshgrid(
39-
other.obs['amplitude'], self.obs['amplitude'])
40-
other_radius, self_radius = meshgrid(
41-
other.obs['radius_e'], self.obs['radius_e'])
42-
ratio_amplitude = other_amplitude / self_amplitude
43-
ratio_radius = other_radius / self_radius
44-
mask *= \
45-
(ratio_amplitude < 2.5) * \
46-
(ratio_amplitude > 1 / 2.5) * \
47-
(ratio_radius < 2.5) * \
48-
(ratio_radius > 1 / 2.5)
37+
# We check ratio (maybe not usefull)
38+
check_ratio(mask, self.obs['amplitude'], other.obs['amplitude'], self.obs['radius_e'], other.obs['radius_e'])
4939
indexs_closest = where(mask)
5040
mask[indexs_closest] = self.across_ground(self.obs[indexs_closest[0]], other.obs[indexs_closest[1]])
5141
return mask
@@ -87,3 +77,29 @@ def post_process_link(self, other, i_self, i_other):
8777
i_self = i_self[mask]
8878
i_other = i_other[mask]
8979
return i_self, i_other
80+
81+
82+
@njit(cache=True)
83+
def check_ratio(current_mask, self_amplitude, other_amplitude, self_radius, other_radius):
84+
"""
85+
Only very few case are remove with selection
86+
:param current_mask:
87+
:param self_amplitude:
88+
:param other_amplitude:
89+
:param self_radius:
90+
:param other_radius:
91+
:return:
92+
"""
93+
self_shape, other_shape = current_mask.shape
94+
r_min = 1 / 2.5
95+
r_max = 2.5
96+
for i in range(self_shape):
97+
for j in range(other_shape):
98+
if current_mask[i, j]:
99+
r_amplitude = other_amplitude[j] / self_amplitude[i]
100+
if r_amplitude >= r_max or r_amplitude <= r_min:
101+
current_mask[i, j] = False
102+
continue
103+
r_radius = other_radius[j] / self_radius[i]
104+
if r_radius >= r_max or r_radius <= r_min:
105+
current_mask[i, j] = False

src/py_eddy_tracker/generic.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
"""
4+
from numpy import sin, pi, cos, arctan2, empty, nan, absolute, floor, ones, linspace, interp
5+
from numba import njit, prange
6+
from numpy.linalg import lstsq
7+
8+
9+
@njit(cache=True, fastmath=True, parallel=False)
10+
def distance_grid(lon0, lat0, lon1, lat1):
11+
"""
12+
Args:
13+
lon0:
14+
lat0:
15+
lon1:
16+
lat1:
17+
18+
Returns:
19+
nan value for far away point, and km for other
20+
"""
21+
nb_0 = lon0.shape[0]
22+
nb_1 = lon1.shape[0]
23+
dist = empty((nb_0, nb_1))
24+
D2R = pi / 180.
25+
for i in prange(nb_0):
26+
for j in prange(nb_1):
27+
dlat = absolute(lat1[j] - lat0[i])
28+
if dlat > 15:
29+
dist[i, j] = nan
30+
continue
31+
dlon = absolute(lon1[j] - lon0[i])
32+
if dlon > 180:
33+
dlon = absolute((dlon + 180) % 360 - 180)
34+
if dlon > 20:
35+
dist[i, j] = nan
36+
continue
37+
sin_dlat = sin((dlat) * 0.5 * D2R)
38+
sin_dlon = sin((dlon) * 0.5 * D2R)
39+
cos_lat1 = cos(lat0[i] * D2R)
40+
cos_lat2 = cos(lat1[j] * D2R)
41+
a_val = sin_dlon ** 2 * cos_lat1 * cos_lat2 + sin_dlat ** 2
42+
dist[i, j] = 6370.997 * 2 * arctan2(a_val ** 0.5, (1 - a_val) ** 0.5)
43+
return dist
44+
45+
46+
@njit(cache=True, fastmath=True)
47+
def distance(lon0, lat0, lon1, lat1):
48+
D2R = pi / 180.
49+
sin_dlat = sin((lat1 - lat0) * 0.5 * D2R)
50+
sin_dlon = sin((lon1 - lon0) * 0.5 * D2R)
51+
cos_lat1 = cos(lat0 * D2R)
52+
cos_lat2 = cos(lat1 * D2R)
53+
a_val = sin_dlon ** 2 * cos_lat1 * cos_lat2 + sin_dlat ** 2
54+
return 6370997.0 * 2 * arctan2(a_val ** 0.5, (1 - a_val) ** 0.5)
55+
56+
57+
@njit(cache=True)
58+
def distance_vincenty(lon0, lat0, lon1, lat1):
59+
""" better than haversine but buggy ??"""
60+
D2R = pi / 180.
61+
dlon = (lon1 - lon0) * D2R
62+
cos_dlon = cos(dlon)
63+
cos_lat1 = cos(lat0 * D2R)
64+
cos_lat2 = cos(lat1 * D2R)
65+
sin_lat1 = sin(lat0 * D2R)
66+
sin_lat2 = sin(lat1 * D2R)
67+
return 6370997.0 * arctan2(
68+
((cos_lat2 * sin(dlon) ** 2) + (cos_lat1 * sin_lat2 - sin_lat1 * cos_lat2 * cos_dlon) ** 2) ** .5,
69+
sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_dlon)
70+
71+
72+
@njit(cache=True, fastmath=True)
73+
def interp2d_geo(x_g, y_g, z_g, m_g, x, y):
74+
"""For geographic grid, test of cicularity
75+
Maybe test if we are out of bounds
76+
"""
77+
x_ref = x_g[0]
78+
y_ref = y_g[0]
79+
x_step = x_g[1] - x_ref
80+
y_step = y_g[1] - y_ref
81+
nb_x = x_g.shape[0]
82+
is_circular = (x_g[-1] + x_step) % 360 == x_g[0] % 360
83+
z = empty(x.shape)
84+
for i in prange(x.size):
85+
x_ = (x[i] - x_ref) / x_step
86+
y_ = (y[i] - y_ref) / y_step
87+
i0 = int(floor(x_))
88+
i1 = i0 + 1
89+
if is_circular:
90+
xd = (x_ - i0)
91+
i0 %= nb_x
92+
i1 %= nb_x
93+
j0 = int(floor(y_))
94+
j1 = j0 + 1
95+
yd = (y_ - j0)
96+
z00 = z_g[i0, j0]
97+
z01 = z_g[i0, j1]
98+
z10 = z_g[i1, j0]
99+
z11 = z_g[i1, j1]
100+
if m_g[i0, j0] or m_g[i0, j1] or m_g[i1, j0] or m_g[i1, j1]:
101+
z[i] = nan
102+
else:
103+
z[i] = (z00 * (1 - xd) + (z10 * xd)) * (1 - yd) + (z01 * (1 - xd) + z11 * xd) * yd
104+
return z
105+
106+
@njit(cache=True, fastmath=True, parallel=True)
107+
def custom_convolution(data, mask, kernel):
108+
"""do sortin at high lattitude big part of value are masked"""
109+
nb_x = kernel.shape[0]
110+
demi_x = int((nb_x - 1) / 2)
111+
demi_y = int((kernel.shape[1] - 1) / 2)
112+
out = empty(data.shape[0] - nb_x + 1)
113+
for i in prange(out.shape[0]):
114+
if mask[i + demi_x, demi_y] == 1:
115+
w = (mask[i:i + nb_x] * kernel).sum()
116+
if w != 0:
117+
out[i] = (data[i:i + nb_x] * kernel).sum() / w
118+
else:
119+
out[i] = nan
120+
else:
121+
out[i] = nan
122+
return out
123+
124+
125+
@njit(cache=True)
126+
def fit_circle(x_vec, y_vec):
127+
nb_elt = x_vec.shape[0]
128+
p_inon_x = empty(nb_elt)
129+
p_inon_y = empty(nb_elt)
130+
131+
x_mean = x_vec.mean()
132+
y_mean = y_vec.mean()
133+
134+
norme = (x_vec - x_mean) ** 2 + (y_vec - y_mean) ** 2
135+
norme_max = norme.max()
136+
scale = norme_max ** .5
137+
138+
# Form matrix equation and solve it
139+
# Maybe put f4
140+
datas = ones((nb_elt, 3))
141+
datas[:, 0] = 2. * (x_vec - x_mean) / scale
142+
datas[:, 1] = 2. * (y_vec - y_mean) / scale
143+
144+
(center_x, center_y, radius), residuals, rank, s = lstsq(datas, norme / norme_max)
145+
146+
# Unscale data and get circle variables
147+
radius += center_x ** 2 + center_y ** 2
148+
radius **= .5
149+
center_x *= scale
150+
center_y *= scale
151+
# radius of fitted circle
152+
radius *= scale
153+
# center X-position of fitted circle
154+
center_x += x_mean
155+
# center Y-position of fitted circle
156+
center_y += y_mean
157+
158+
# area of fitted circle
159+
c_area = (radius ** 2) * pi
160+
# Find distance between circle center and contour points_inside_poly
161+
for i_elt in range(nb_elt):
162+
# Find distance between circle center and contour points_inside_poly
163+
dist_poly = ((x_vec[i_elt] - center_x) ** 2 + (y_vec[i_elt] - center_y) ** 2) ** .5
164+
# Indices of polygon points outside circle
165+
# p_inon_? : polygon x or y points inside & on the circle
166+
if dist_poly > radius:
167+
p_inon_y[i_elt] = center_y + radius * (y_vec[i_elt] - center_y) / dist_poly
168+
p_inon_x[i_elt] = center_x - (center_x - x_vec[i_elt]) * (center_y - p_inon_y[i_elt]) / (
169+
center_y - y_vec[i_elt])
170+
else:
171+
p_inon_x[i_elt] = x_vec[i_elt]
172+
p_inon_y[i_elt] = y_vec[i_elt]
173+
174+
# Area of closed contour/polygon enclosed by the circle
175+
p_area_incirc = 0
176+
p_area = 0
177+
for i_elt in range(nb_elt - 1):
178+
# Indices of polygon points outside circle
179+
# p_inon_? : polygon x or y points inside & on the circle
180+
p_area_incirc += p_inon_x[i_elt] * p_inon_y[1 + i_elt] - p_inon_x[i_elt + 1] * p_inon_y[i_elt]
181+
# Shape test
182+
# Area and centroid of closed contour/polygon
183+
p_area += x_vec[i_elt] * y_vec[1 + i_elt] - x_vec[1 + i_elt] * y_vec[i_elt]
184+
p_area = abs(p_area) * .5
185+
p_area_incirc = abs(p_area_incirc) * .5
186+
187+
a_err = (c_area - 2 * p_area_incirc + p_area) * 100. / c_area
188+
return center_x, center_y, radius, a_err
189+
190+
191+
@njit(cache=True, fastmath=True)
192+
def uniform_resample(x_val, y_val, num_fac=2, fixed_size=None):
193+
"""
194+
Resample contours to have (nearly) equal spacing
195+
x_val, y_val : input contour coordinates
196+
num_fac : factor to increase lengths of output coordinates
197+
"""
198+
# Get distances
199+
dist = empty(x_val.shape)
200+
dist[0] = 0
201+
dist[1:] = distance(x_val[:-1], y_val[:-1], x_val[1:], y_val[1:])
202+
# To be still monotonous (dist is store in m)
203+
dist[1:][dist[1:]<1e-3] = 1e-3
204+
dist = dist.cumsum()
205+
# Get uniform distances
206+
if fixed_size is None:
207+
fixed_size = dist.size * num_fac
208+
d_uniform = linspace(0, dist[-1], fixed_size)
209+
x_new = interp(d_uniform, dist, x_val)
210+
y_new = interp(d_uniform, dist, y_val)
211+
return x_new, y_new

0 commit comments

Comments
 (0)