Skip to content

Commit da32c9d

Browse files
committed
error is now compute outside of fitcircle
1 parent d5b6157 commit da32c9d

File tree

4 files changed

+117
-83
lines changed

4 files changed

+117
-83
lines changed

src/py_eddy_tracker/dataset/grid.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
from ..generic import (
5151
distance,
5252
interp2d_geo,
53-
fit_circle,
5453
uniform_resample,
5554
coordinates_to_local,
5655
local_to_coordinates,
@@ -63,6 +62,7 @@
6362
winding_number_poly,
6463
create_vertice,
6564
poly_area,
65+
fit_circle,
6666
)
6767

6868
logger = logging.getLogger("pet")
@@ -153,7 +153,7 @@ def _circle_from_equal_area(vertice):
153153
# logger.debug('%d coordinates %s,%s', len(lons),lons,
154154
# lats)
155155
return 0, -90, nan, nan
156-
return lon0, lat0, (poly_area(create_vertice(c_x, c_y)) / pi) ** 0.5, nan
156+
return lon0, lat0, (poly_area(c_x, c_y) / pi) ** 0.5, nan
157157

158158

159159
@njit(cache=True, fastmath=True)

src/py_eddy_tracker/generic.py

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
concatenate,
4242
)
4343
from numba import njit, prange, types as numba_types
44-
from numpy.linalg import lstsq
4544
from .poly import winding_number_grid_in_poly
4645

4746

@@ -195,79 +194,6 @@ def custom_convolution(data, mask, kernel):
195194
return out
196195

197196

198-
@njit(cache=True)
199-
def fit_circle(x_vec, y_vec):
200-
nb_elt = x_vec.shape[0]
201-
p_inon_x = empty(nb_elt)
202-
p_inon_y = empty(nb_elt)
203-
204-
# last coordinates == first
205-
x_mean = x_vec[1:].mean()
206-
y_mean = y_vec[1:].mean()
207-
208-
norme = (x_vec[1:] - x_mean) ** 2 + (y_vec[1:] - y_mean) ** 2
209-
norme_max = norme.max()
210-
scale = norme_max ** 0.5
211-
212-
# Form matrix equation and solve it
213-
# Maybe put f4
214-
datas = ones((nb_elt - 1, 3))
215-
datas[:, 0] = 2.0 * (x_vec[1:] - x_mean) / scale
216-
datas[:, 1] = 2.0 * (y_vec[1:] - y_mean) / scale
217-
218-
(center_x, center_y, radius), _, _, _ = lstsq(datas, norme / norme_max)
219-
220-
# Unscale data and get circle variables
221-
radius += center_x ** 2 + center_y ** 2
222-
radius **= 0.5
223-
center_x *= scale
224-
center_y *= scale
225-
# radius of fitted circle
226-
radius *= scale
227-
# center X-position of fitted circle
228-
center_x += x_mean
229-
# center Y-position of fitted circle
230-
center_y += y_mean
231-
232-
# area of fitted circle
233-
c_area = (radius ** 2) * pi
234-
# Find distance between circle center and contour points_inside_poly
235-
for i_elt in range(nb_elt):
236-
# Find distance between circle center and contour points_inside_poly
237-
dist_poly = (
238-
(x_vec[i_elt] - center_x) ** 2 + (y_vec[i_elt] - center_y) ** 2
239-
) ** 0.5
240-
# Indices of polygon points outside circle
241-
# p_inon_? : polygon x or y points inside & on the circle
242-
if dist_poly > radius:
243-
p_inon_y[i_elt] = center_y + radius * (y_vec[i_elt] - center_y) / dist_poly
244-
p_inon_x[i_elt] = center_x - (center_x - x_vec[i_elt]) * (
245-
center_y - p_inon_y[i_elt]
246-
) / (center_y - y_vec[i_elt])
247-
else:
248-
p_inon_x[i_elt] = x_vec[i_elt]
249-
p_inon_y[i_elt] = y_vec[i_elt]
250-
251-
# Area of closed contour/polygon enclosed by the circle
252-
p_area_incirc = 0
253-
p_area = 0
254-
for i_elt in range(nb_elt - 1):
255-
# Indices of polygon points outside circle
256-
# p_inon_? : polygon x or y points inside & on the circle
257-
p_area_incirc += (
258-
p_inon_x[i_elt] * p_inon_y[1 + i_elt]
259-
- p_inon_x[i_elt + 1] * p_inon_y[i_elt]
260-
)
261-
# Shape test
262-
# Area and centroid of closed contour/polygon
263-
p_area += x_vec[i_elt] * y_vec[1 + i_elt] - x_vec[1 + i_elt] * y_vec[i_elt]
264-
p_area = abs(p_area) * 0.5
265-
p_area_incirc = abs(p_area_incirc) * 0.5
266-
267-
a_err = (c_area - 2 * p_area_incirc + p_area) * 100.0 / c_area
268-
return center_x, center_y, radius, a_err
269-
270-
271197
@njit(cache=True, fastmath=True)
272198
def uniform_resample(x_val, y_val, num_fac=2, fixed_size=None):
273199
"""

src/py_eddy_tracker/poly.py

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
===========================================================================
2222
"""
2323

24-
from numpy import empty, where, array
24+
from numpy import empty, where, array, ones, pi
25+
from numpy.linalg import lstsq
2526
from numba import njit, prange, types as numba_types
2627
from Polygon import Polygon
2728

@@ -74,16 +75,27 @@ def poly_contain_poly(xy_poly_out, xy_poly_in):
7475

7576

7677
@njit(cache=True)
77-
def poly_area(vertice):
78+
def poly_area_vertice(v):
7879
"""
79-
:param vertice vertice: polygon vertice
80+
:param vertice v: polygon vertice
8081
:return: area of polygon in coordinates unit
8182
:rtype: float
8283
"""
83-
p_area = vertice[0, 0] * (vertice[1, 1] - vertice[1, -2])
84-
nb_elt = vertice.shape[0]
85-
for i_elt in range(1, nb_elt - 1):
86-
p_area += vertice[i_elt, 0] * (vertice[1 + i_elt, 1] - vertice[i_elt - 1, 1])
84+
return poly_area(v[:, 0], v[:, 1])
85+
86+
87+
@njit(cache=True)
88+
def poly_area(x, y):
89+
"""
90+
:param array x:
91+
:param array y:
92+
:return: area of polygon in coordinates unit
93+
:rtype: float
94+
"""
95+
p_area = x[0] * (y[1] - y[-2])
96+
nb = x.shape[0]
97+
for i in range(1, nb - 1):
98+
p_area += x[i] * (y[1 + i] - y[i - 1])
8799
return abs(p_area) * 0.5
88100

89101

@@ -295,3 +307,81 @@ def polygon_overlap(p0, p1, minimal_area=False):
295307
else:
296308
cost[i] = intersection / (p0 + p_).area()
297309
return cost
310+
311+
312+
@njit(cache=True)
313+
def fit_circle(x, y):
314+
"""
315+
From a polygon, function will fit a circle
316+
Must be call with local coordinates (in m, to get a radius in m)
317+
318+
:param array x: x of polygon
319+
:param array y: y of polygon
320+
:return: x0, y0, radius, shape_error
321+
:rtype: (float,float,float,float)
322+
"""
323+
nb_elt = x.shape[0]
324+
325+
# last coordinates == first
326+
x_mean = x[1:].mean()
327+
y_mean = y[1:].mean()
328+
329+
norme = (x[1:] - x_mean) ** 2 + (y[1:] - y_mean) ** 2
330+
norme_max = norme.max()
331+
scale = norme_max ** 0.5
332+
333+
# Form matrix equation and solve it
334+
# Maybe put f4
335+
datas = ones((nb_elt - 1, 3))
336+
datas[:, 0] = 2.0 * (x[1:] - x_mean) / scale
337+
datas[:, 1] = 2.0 * (y[1:] - y_mean) / scale
338+
339+
(x0, y0, radius), _, _, _ = lstsq(datas, norme / norme_max)
340+
341+
# Unscale data and get circle variables
342+
radius += x0 ** 2 + y0 ** 2
343+
radius **= 0.5
344+
x0 *= scale
345+
y0 *= scale
346+
# radius of fitted circle
347+
radius *= scale
348+
# center X-position of fitted circle
349+
x0 += x_mean
350+
# center Y-position of fitted circle
351+
y0 += y_mean
352+
353+
err = shape_error(x, y, x0, y0, radius)
354+
return x0, y0, radius, err
355+
356+
357+
@njit(cache=True, fastmath=True)
358+
def shape_error(x, y, x0, y0, r):
359+
"""
360+
With a polygon(x,y) in local coordinates
361+
and circle properties(x0, y0, r), function compute a shape error:
362+
363+
.. math:: ShapeError = \\frac{Polygon_{area} + Circle_{area} - 2 * Intersection_{area}}{Circle_{area}} * 100
364+
365+
When error > 100, area of difference is bigger than circle area
366+
367+
:param array x: x of polygon
368+
:param array y: y of polygon
369+
:param float x0: x center of circle
370+
:param float y0: y center of circle
371+
:param float r: radius of circle
372+
:return: shape error
373+
:rtype: float
374+
"""
375+
# circle area
376+
c_area = (r ** 2) * pi
377+
p_area = poly_area(x, y)
378+
nb = x.shape[0]
379+
x, y = x.copy(), y.copy()
380+
# Find distance between circle center and polygon
381+
for i in range(nb):
382+
dx, dy = x[i] - x0, y[i] - y0
383+
rd = r / (dx ** 2 + dy ** 2) ** 0.5
384+
if rd < 1:
385+
x[i] = x0 + dx * rd
386+
y[i] = y0 + dy * rd
387+
return 100 + (p_area - 2 * poly_area(x, y)) / c_area * 100

tests/test_poly.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from py_eddy_tracker.poly import poly_area_vertice, fit_circle
2+
from numpy import array, pi
3+
from pytest import approx
4+
5+
# Vertices for next test
6+
V = array(((2, 2, 3, 3, 2), (-10, -9, -9, -10, -10)))
7+
8+
9+
def test_poly_area():
10+
assert 1 == poly_area_vertice(V.T)
11+
12+
13+
def test_fit_circle():
14+
x0, y0, r, err = fit_circle(*V)
15+
assert x0 == approx(2.5, rel=1e-10)
16+
assert y0 == approx(-9.5, rel=1e-10)
17+
assert r == approx(2 ** 0.5 / 2, rel=1e-10)
18+
assert err == approx((r ** 2 * pi - 1) / (r ** 2 * pi) * 100, rel=1e-10)

0 commit comments

Comments
 (0)