Skip to content

Commit 890d38e

Browse files
committed
use numba to fit circle
1 parent 0bbc74f commit 890d38e

File tree

2 files changed

+81
-105
lines changed

2 files changed

+81
-105
lines changed

src/py_eddy_tracker/dataset/grid.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
sin, deg2rad, pi, ones, cos, ma, int8, histogram2d, arange, float_, \
77
linspace, errstate, int_, column_stack, interp, meshgrid, nan, ceil, sinc, float64, isnan, \
88
floor, percentile, zeros
9+
from numpy.linalg import lstsq
910
from datetime import datetime
1011
from scipy.special import j1
1112
from netCDF4 import Dataset
@@ -14,13 +15,12 @@
1415
from scipy.spatial import cKDTree
1516
from scipy.signal import welch
1617
from cv2 import filter2D
17-
from numba import jit
18+
from numba import njit, prange
1819
from matplotlib.path import Path as BasePath
1920
from matplotlib.contour import QuadContourSet as BaseQuadContourSet
2021
from pyproj import Proj
2122
from pint import UnitRegistry
22-
from ..tools import fit_circle_c, distance_vector, winding_number_poly, poly_contain_poly, \
23-
distance, distance_point_vector
23+
from ..tools import distance_vector, winding_number_poly, poly_contain_poly, distance, distance_point_vector
2424
from ..observations import EddiesObservations
2525
from ..eddy_feature import Amplitude, Contours
2626
from .. import VAR_DESCR
@@ -115,7 +115,7 @@ def _fit_circle_path(self):
115115

116116
c_x, c_y = proj(self.lon, self.lat)
117117
try:
118-
centlon_e, centlat_e, eddy_radius_e, aerr = fit_circle_c(c_x, c_y)
118+
centlon_e, centlat_e, eddy_radius_e, aerr = fit_circle_c_numba(c_x, c_y)
119119
centlon_e, centlat_e = proj(centlon_e, centlat_e, inverse=True)
120120
centlon_e = (centlon_e - lon_mean + 180) % 360 + lon_mean - 180
121121
self._circle_params = centlon_e, centlat_e, eddy_radius_e, aerr
@@ -130,6 +130,74 @@ def _fit_circle_path(self):
130130
self._circle_params = 0, -90, nan, nan
131131

132132

133+
@njit(cache=True)
134+
def fit_circle_c_numba(x_vec, y_vec):
135+
nb_elt = x_vec.shape[0]
136+
p_inon_x = empty(nb_elt)
137+
p_inon_y = empty(nb_elt)
138+
139+
x_mean = x_vec.mean()
140+
y_mean = y_vec.mean()
141+
142+
norme = (x_vec - x_mean) ** 2 + (y_vec - y_mean) ** 2
143+
norme_max = norme.max()
144+
scale = norme_max ** .5
145+
146+
# Form matrix equation and solve it
147+
# Maybe put f4
148+
datas = ones((nb_elt, 3))
149+
datas[:, 0] = 2. * (x_vec - x_mean) / scale
150+
datas[:, 1] = 2. * (y_vec - y_mean) / scale
151+
152+
(center_x, center_y, radius), residuals, rank, s = lstsq(datas, norme / norme_max)
153+
154+
# Unscale data and get circle variables
155+
radius += center_x ** 2 + center_y ** 2
156+
radius **= .5
157+
center_x *= scale
158+
center_y *= scale
159+
# radius of fitted circle
160+
radius *= scale
161+
# center X-position of fitted circle
162+
center_x += x_mean
163+
# center Y-position of fitted circle
164+
center_y += y_mean
165+
166+
# area of fitted circle
167+
c_area = (radius ** 2) * pi
168+
169+
# Find distance between circle center and contour points_inside_poly
170+
for i_elt in range(nb_elt):
171+
# Find distance between circle center and contour points_inside_poly
172+
dist_poly = ((x_vec[i_elt] - center_x) ** 2 + (y_vec[i_elt] - center_y) ** 2) ** .5
173+
# Indices of polygon points outside circle
174+
# p_inon_? : polygon x or y points inside & on the circle
175+
if dist_poly > radius:
176+
p_inon_y[i_elt] = center_y + radius * (y_vec[i_elt] - center_y) / dist_poly
177+
p_inon_x[i_elt] = center_x - (center_x - x_vec[i_elt]) * (center_y - p_inon_y[i_elt]) / (
178+
center_y - y_vec[i_elt])
179+
else:
180+
p_inon_x[i_elt] = x_vec[i_elt]
181+
p_inon_y[i_elt] = y_vec[i_elt]
182+
183+
# Area of closed contour/polygon enclosed by the circle
184+
p_area_incirc = 0
185+
p_area = 0
186+
for i_elt in range(nb_elt - 1):
187+
# Indices of polygon points outside circle
188+
# p_inon_? : polygon x or y points inside & on the circle
189+
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]
190+
# Shape test
191+
# Area and centroid of closed contour/polygon
192+
p_area += x_vec[i_elt] * y_vec[1 + i_elt] - x_vec[1 + i_elt] * y_vec[i_elt]
193+
p_area = abs(p_area) * .5
194+
195+
p_area_incirc = abs(p_area_incirc) * .5
196+
197+
a_err = (c_area - 2 * p_area_incirc + p_area) * 100. / c_area
198+
return center_x, center_y, radius, a_err
199+
200+
133201
BasePath.fit_circle = fit_circle_path
134202
BasePath._fit_circle_path = _fit_circle_path
135203

@@ -502,12 +570,12 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
502570
# First, get position based on innermost
503571
# contour
504572
c_x, c_y = proj(inner_contour.lon, inner_contour.lat)
505-
centx_s, centy_s, _, _ = fit_circle_c(c_x, c_y)
573+
centx_s, centy_s, _, _ = fit_circle_c_numba(c_x, c_y)
506574
centlon_s, centlat_s = proj(centx_s, centy_s, inverse=True)
507575
# Second, get speed-based radius based on
508576
# contour of max uavg
509577
c_x, c_y = proj(speed_contour.lon, speed_contour.lat)
510-
_, _, eddy_radius_s, aerr_s = fit_circle_c(c_x, c_y)
578+
_, _, eddy_radius_s, aerr_s = fit_circle_c_numba(c_x, c_y)
511579

512580
# Instantiate new EddyObservation object
513581
properties = EddiesObservations(size=1, track_extra_variables=track_extra_variables,
@@ -1192,26 +1260,26 @@ def interp(self, grid_name, lons, lats):
11921260
new z
11931261
"""
11941262
z = empty(lons.shape, dtype='f4').reshape(-1)
1195-
g = self.grid(grid_name).astype('f4')
1263+
g = self.grid(grid_name)
11961264
interp_numba(
1197-
self.x_c.astype('f4'),
1198-
self.y_c.astype('f4'),
1265+
self.x_c,
1266+
self.y_c,
11991267
g,
1200-
lons.reshape(-1).astype('f4'),
1201-
lats.reshape(-1).astype('f4'),
1268+
lons.reshape(-1),
1269+
lats.reshape(-1),
12021270
z,
12031271
g.fill_value
12041272
)
12051273
return z
12061274

12071275

1208-
@jit("void(f4[:], f4[:], f4[:,:], f4[:], f4[:], f4[:], f4)", nopython=True)
1276+
@njit(parralel=True, cache=True)
12091277
def interp_numba(x_g, y_g, z, x, y, dest_z, fill_value):
12101278
x_ref = x_g[0]
12111279
y_ref = y_g[0]
12121280
x_step = x_g[1] - x_ref
12131281
y_step = y_g[1] - y_ref
1214-
for i in range(x.size):
1282+
for i in prange(x.size):
12151283
x_ = (x[i] - x_ref) / x_step
12161284
y_ = (y[i] - y_ref) / y_step
12171285
i0 = int(floor(x_))

src/py_eddy_tracker/tools.pyx

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -12,101 +12,9 @@ ctypedef double DTYPE_coord
1212

1313

1414
cdef DTYPE_coord D2R = 0.017453292519943295
15-
cdef DTYPE_coord PI = 3.141592653589793
1615
cdef DTYPE_coord EARTH_DIAMETER = 6371.3150 * 2
1716

1817

19-
@wraparound(False)
20-
@boundscheck(False)
21-
def fit_circle_c(
22-
ndarray[DTYPE_coord] x_vec,
23-
ndarray[DTYPE_coord] y_vec
24-
):
25-
"""
26-
Fit the circle
27-
Adapted from ETRACK (KCCMC11)
28-
"""
29-
cdef DTYPE_ui i_elt, i_start, i_end, nb_elt
30-
cdef DTYPE_coord x_mean, y_mean, scale, norme_max, center_x, center_y, radius
31-
cdef DTYPE_coord p_area, c_area, a_err, p_area_incirc, dist_poly
32-
nb_elt = x_vec.shape[0]
33-
34-
cdef DTYPE_coord * p_inon_x = <DTYPE_coord * >malloc(nb_elt * sizeof(DTYPE_coord))
35-
if not p_inon_x:
36-
raise MemoryError()
37-
cdef DTYPE_coord * p_inon_y = <DTYPE_coord * >malloc(nb_elt * sizeof(DTYPE_coord))
38-
if not p_inon_y:
39-
raise MemoryError()
40-
41-
x_mean = 0
42-
y_mean = 0
43-
44-
for i_elt from 0 <= i_elt < nb_elt:
45-
x_mean += x_vec[i_elt]
46-
y_mean += y_vec[i_elt]
47-
y_mean /= nb_elt
48-
x_mean /= nb_elt
49-
50-
norme = (x_vec - x_mean) ** 2 + (y_vec - y_mean) ** 2
51-
norme_max = norme.max()
52-
scale = norme_max ** .5
53-
54-
# Form matrix equation and solve it
55-
# Maybe put f4
56-
datas = ones((nb_elt, 3), dtype='f8')
57-
for i_elt from 0 <= i_elt < nb_elt:
58-
datas[i_elt, 0] = 2. * (x_vec[i_elt] - x_mean) / scale
59-
datas[i_elt, 1] = 2. * (y_vec[i_elt] - y_mean) / scale
60-
61-
(center_x, center_y, radius), _, _, _ = lstsq(datas, norme / norme_max, rcond=None)
62-
63-
# Unscale data and get circle variables
64-
radius += center_x ** 2 + center_y ** 2
65-
radius **= .5
66-
center_x *= scale
67-
center_y *= scale
68-
# radius of fitted circle
69-
radius *= scale
70-
# center X-position of fitted circle
71-
center_x += x_mean
72-
# center Y-position of fitted circle
73-
center_y += y_mean
74-
75-
# area of fitted circle
76-
c_area = (radius ** 2) * PI
77-
78-
# Find distance between circle center and contour points_inside_poly
79-
for i_elt from 0 <= i_elt < nb_elt:
80-
# Find distance between circle center and contour points_inside_poly
81-
dist_poly = ((x_vec[i_elt] - center_x) ** 2 + (y_vec[i_elt] - center_y) ** 2) ** .5
82-
# Indices of polygon points outside circle
83-
# p_inon_? : polygon x or y points inside & on the circle
84-
if dist_poly > radius:
85-
p_inon_y[i_elt] = center_y + radius * (y_vec[i_elt] - center_y) / dist_poly
86-
p_inon_x[i_elt] = center_x - (center_x - x_vec[i_elt]) * (center_y - p_inon_y[i_elt]) / (center_y - y_vec[i_elt])
87-
else:
88-
p_inon_x[i_elt] = x_vec[i_elt]
89-
p_inon_y[i_elt] = y_vec[i_elt]
90-
91-
# Area of closed contour/polygon enclosed by the circle
92-
p_area_incirc = 0
93-
p_area = 0
94-
for i_elt from 0 <= i_elt < (nb_elt - 1):
95-
# Indices of polygon points outside circle
96-
# p_inon_? : polygon x or y points inside & on the circle
97-
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]
98-
# Shape test
99-
# Area and centroid of closed contour/polygon
100-
p_area += x_vec[i_elt] * y_vec[1 + i_elt] - x_vec[1 + i_elt] * y_vec[i_elt]
101-
p_area = abs(p_area) * .5
102-
free(p_inon_x)
103-
free(p_inon_y)
104-
p_area_incirc = abs(p_area_incirc) * .5
105-
106-
a_err = (c_area - 2 * p_area_incirc + p_area) * 100. / c_area
107-
return center_x, center_y, radius, a_err
108-
109-
11018
@wraparound(False)
11119
@boundscheck(False)
11220
cdef is_left(

0 commit comments

Comments
 (0)