geone 1.3.0__py313-none-manylinux_2_35_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geone/__init__.py +32 -0
- geone/_version.py +6 -0
- geone/blockdata.py +250 -0
- geone/covModel.py +15529 -0
- geone/customcolors.py +508 -0
- geone/deesse_core/__init__.py +5 -0
- geone/deesse_core/_deesse.so +0 -0
- geone/deesse_core/deesse.py +2450 -0
- geone/deesseinterface.py +11323 -0
- geone/geosclassic_core/__init__.py +5 -0
- geone/geosclassic_core/_geosclassic.so +0 -0
- geone/geosclassic_core/geosclassic.py +1429 -0
- geone/geosclassicinterface.py +20092 -0
- geone/grf.py +5927 -0
- geone/img.py +7152 -0
- geone/imgplot.py +1464 -0
- geone/imgplot3d.py +1918 -0
- geone/markovChain.py +666 -0
- geone/multiGaussian.py +388 -0
- geone/pgs.py +1258 -0
- geone/randProcess.py +1258 -0
- geone/srf.py +3661 -0
- geone/tools.py +861 -0
- geone-1.3.0.dist-info/METADATA +207 -0
- geone-1.3.0.dist-info/RECORD +28 -0
- geone-1.3.0.dist-info/WHEEL +5 -0
- geone-1.3.0.dist-info/licenses/LICENSE +58 -0
- geone-1.3.0.dist-info/top_level.txt +1 -0
geone/tools.py
ADDED
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# -------------------------------------------------------------------------
|
|
5
|
+
# Python module: 'tools.py'
|
|
6
|
+
# author: Julien Straubhaar
|
|
7
|
+
# date: nov-2023
|
|
8
|
+
# -------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Module for miscellaneous tools.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import multiprocessing
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
from matplotlib.backend_bases import MouseButton
|
|
20
|
+
|
|
21
|
+
from geone import img
|
|
22
|
+
|
|
23
|
+
# -----------------------------------------------------------------------------
|
|
24
|
+
def add_path_by_drawing(
|
|
25
|
+
path_list,
|
|
26
|
+
close=False,
|
|
27
|
+
show_instructions=True,
|
|
28
|
+
last_point_marker='o',
|
|
29
|
+
last_point_color='red',
|
|
30
|
+
**kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Add paths in a list, by interatively drawing on a plot.
|
|
33
|
+
|
|
34
|
+
The first argument of the function is a list that is updated when the
|
|
35
|
+
function is called by appending one (or more) path(s) (see notes below).
|
|
36
|
+
A path is a 2D array of floats with two columns, each row is (the x and y
|
|
37
|
+
coordinates of) a point. A path is interactively determined on the plot on
|
|
38
|
+
the current axis (get with `matplotlib.pyplot.gca()`), with the following
|
|
39
|
+
rules:
|
|
40
|
+
|
|
41
|
+
- left click: add the next point (or first one)
|
|
42
|
+
- right click: remove the last point
|
|
43
|
+
|
|
44
|
+
When pressing a key:
|
|
45
|
+
|
|
46
|
+
- key n/N: terminate the current path, and start a new path
|
|
47
|
+
- key ENTER (or other): terminate the current path and exits
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
path_list : list
|
|
52
|
+
list of paths, that will be updated by appending the path interactively
|
|
53
|
+
drawn in the current axis (one can start with `path_list = []`)
|
|
54
|
+
|
|
55
|
+
close : bool, default: False
|
|
56
|
+
if `True`: when a path is terminated, the first points of the path is
|
|
57
|
+
replicated at the end of the path to form a close line / path
|
|
58
|
+
|
|
59
|
+
show_instructions : bool, default: True
|
|
60
|
+
if `True`: instructions are printed in the standard output
|
|
61
|
+
|
|
62
|
+
last_point_marker : "matplotlib marker", default: 'o'
|
|
63
|
+
marker used for highlighting the last clicked point
|
|
64
|
+
|
|
65
|
+
last_point_color : "matplotlib color", default: 'red'
|
|
66
|
+
color used for highlighting the last clicked point
|
|
67
|
+
|
|
68
|
+
kwargs : dict
|
|
69
|
+
keyword arguments passed to `matplotlib.pyplot.plot` to plot the path(s)
|
|
70
|
+
|
|
71
|
+
Notes
|
|
72
|
+
-----
|
|
73
|
+
* The function does not return anything. The first argument, `path_list` is \
|
|
74
|
+
updated, with the path(s) drawn interactively, for example `path_list[-1]` \
|
|
75
|
+
is the last path, a 2D array, where `path_list[-1][i]` is the i-th point \
|
|
76
|
+
(1D array of two floats) of that path
|
|
77
|
+
* An interactive maplotlib backend must be used, so that this function works \
|
|
78
|
+
properly
|
|
79
|
+
"""
|
|
80
|
+
# fname = 'add_path_by_drawing'
|
|
81
|
+
|
|
82
|
+
ax = plt.gca()
|
|
83
|
+
obj_drawn = []
|
|
84
|
+
x, y = [], []
|
|
85
|
+
|
|
86
|
+
set_default_color = False
|
|
87
|
+
if 'color' not in kwargs.keys() and 'c' not in kwargs.keys():
|
|
88
|
+
set_default_color = True
|
|
89
|
+
col = plt.rcParams['axes.prop_cycle'].by_key()['color']
|
|
90
|
+
ncol = len(col)
|
|
91
|
+
col_ind = [0]
|
|
92
|
+
kwargs['color'] = col[col_ind[0]] # default color for lines (segments)
|
|
93
|
+
# if 'color' not in kwargs.keys() and 'c' not in kwargs.keys():
|
|
94
|
+
# kwargs['color'] = 'tab:blue' # default color for lines (segments)
|
|
95
|
+
|
|
96
|
+
if show_instructions:
|
|
97
|
+
instruct0 = '\n'.join([' Left click: add next point', ' Right click: remove last point', ' Key n/N: select a new line', ' key ENTER (or other): finish (quit)'])
|
|
98
|
+
print('\n'.join(['Draw path', instruct0]))
|
|
99
|
+
sys.stdout.flush()
|
|
100
|
+
|
|
101
|
+
def on_click(event):
|
|
102
|
+
if not event.inaxes:
|
|
103
|
+
return
|
|
104
|
+
if event.button is MouseButton.LEFT:
|
|
105
|
+
if len(x):
|
|
106
|
+
# remove last point from obj_drawn
|
|
107
|
+
ax.lines[-1].remove()
|
|
108
|
+
# add clicked point
|
|
109
|
+
x.append(event.xdata)
|
|
110
|
+
y.append(event.ydata)
|
|
111
|
+
if len(x) > 1:
|
|
112
|
+
# add line (segment) to obj_drawn
|
|
113
|
+
obj_drawn.append(plt.plot(x[-2:], y[-2:], **kwargs))
|
|
114
|
+
# add point to obj_drawn
|
|
115
|
+
obj_drawn.append(plt.plot(x[-1], y[-1], marker=last_point_marker, color=last_point_color))
|
|
116
|
+
if event.button is MouseButton.RIGHT:
|
|
117
|
+
if len(x):
|
|
118
|
+
# remove last clicked point
|
|
119
|
+
del x[-1], y[-1]
|
|
120
|
+
# remove last point from obj_drawn
|
|
121
|
+
ax.lines[-1].remove()
|
|
122
|
+
if len(x):
|
|
123
|
+
# point(s) are still in the line
|
|
124
|
+
# remove last line (segment) from obj_drawn
|
|
125
|
+
ax.lines[-1].remove()
|
|
126
|
+
# add last point to obj_drawn
|
|
127
|
+
obj_drawn.append(plt.plot(x[-1], y[-1], marker=last_point_marker, color=last_point_color))
|
|
128
|
+
|
|
129
|
+
def on_key(event):
|
|
130
|
+
if len(x):
|
|
131
|
+
# remove last point from obj_drawn
|
|
132
|
+
ax.lines[-1].remove()
|
|
133
|
+
if close:
|
|
134
|
+
# close the line
|
|
135
|
+
if len(x):
|
|
136
|
+
x.append(x[0])
|
|
137
|
+
y.append(y[0])
|
|
138
|
+
# add line (closing segment) to obj_drawn
|
|
139
|
+
obj_drawn.append(plt.plot(x[-2:], y[-2:], **kwargs))
|
|
140
|
+
# add path to path_list
|
|
141
|
+
if len(x):
|
|
142
|
+
path_list.append(np.array((x, y)).T)
|
|
143
|
+
if event.key.lower() == 'n':
|
|
144
|
+
if show_instructions:
|
|
145
|
+
print('\n'.join(['Draw next path', instruct0]))
|
|
146
|
+
sys.stdout.flush()
|
|
147
|
+
x.clear() # Do not use: x = [], because x is global for this function!
|
|
148
|
+
y.clear()
|
|
149
|
+
if set_default_color:
|
|
150
|
+
# Set color for next line
|
|
151
|
+
col_ind[0] = (col_ind[0]+1)%ncol
|
|
152
|
+
kwargs['color'] = col[col_ind[0]] # default color for lines (segments)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
plt.disconnect(cid_click)
|
|
156
|
+
plt.disconnect(cid_key)
|
|
157
|
+
return
|
|
158
|
+
# if
|
|
159
|
+
# path_list.append(np.array((x, y)).T)
|
|
160
|
+
# cid_click = plt.connect('button_press_event', on_click)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
cid_click = plt.connect('button_press_event', on_click)
|
|
164
|
+
cid_key = plt.connect('key_press_event', on_key)
|
|
165
|
+
# -----------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
# -----------------------------------------------------------------------------
|
|
168
|
+
def is_in_polygon(x, vertices, wrap=None, return_sum_of_angles=False, **kwargs):
|
|
169
|
+
"""
|
|
170
|
+
Checks if point(s) is (are) in a polygon given by its vertices forming a close line.
|
|
171
|
+
|
|
172
|
+
To check if a point is in the polygon, the method consists in computing
|
|
173
|
+
the vectors from the given point to the vertices of the polygon and
|
|
174
|
+
the sum of signed angles between two successives vectors (and the last
|
|
175
|
+
and first one). Then, the point is in the polygon if and only if this sum
|
|
176
|
+
is equal to +/- 2 pi.
|
|
177
|
+
|
|
178
|
+
Note that if the sum of angles is +2 pi (resp. -2 pi), then the vertices form
|
|
179
|
+
a close line counterclockwise (resp. clockwise); this can be checked by
|
|
180
|
+
specifying a point `x` in the polygon and `return_sum_of_angles=True`.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
x : 2D array-like or 1D array-like
|
|
185
|
+
point(s) coordinates, each row `x[i]` (if 2D array-like) (or `x` if
|
|
186
|
+
1D array-like) contains the two coordinates of one point
|
|
187
|
+
|
|
188
|
+
vertices : 2D array
|
|
189
|
+
vertices of a polygon in 2D, each row of `vertices` contains the two
|
|
190
|
+
coordinates of one vertex; the segments of the polygon are obtained by
|
|
191
|
+
linking two successive vertices (as well as the last one with the first
|
|
192
|
+
one, if `wrap=True` (see below)), so that the vertices form a close line
|
|
193
|
+
(clockwise or counterclockwise)
|
|
194
|
+
|
|
195
|
+
wrap : bool, optional
|
|
196
|
+
- if `True`: last and first vertices has to be linked to form a close line
|
|
197
|
+
- if `False`: last and first vertices should be the same ones (i.e. the \
|
|
198
|
+
vertices form a close line);
|
|
199
|
+
|
|
200
|
+
by default (`None`): `wrap` is automatically computed
|
|
201
|
+
|
|
202
|
+
return_sum_of_angles : bool, default: False
|
|
203
|
+
if `True`, the sum of angles (computed by the method) is returned
|
|
204
|
+
|
|
205
|
+
kwargs :
|
|
206
|
+
keyword arguments passed to function `numpy.isclose`
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
out : 1D array of bools, or bool
|
|
211
|
+
indicates for each point in `x` if it is inside (True) or outside (False)
|
|
212
|
+
the polygon;
|
|
213
|
+
note: if `x` is of shape (m, 2), then `out` is a 1D array of shape (m, ),
|
|
214
|
+
and if `x` is of shape (2, ) (one point), `out` is bool
|
|
215
|
+
|
|
216
|
+
sum_of_angles : 1D array of floats, or float
|
|
217
|
+
returned if `return_sum_of_angles=True`; for each point in `x`, the sum
|
|
218
|
+
of angles computed by the method is returned (`nan` if the sum is not
|
|
219
|
+
computed);
|
|
220
|
+
note: `sum_of_angles` is an array of same shape as `out` or a float
|
|
221
|
+
"""
|
|
222
|
+
# fname = 'is_in_polygon'
|
|
223
|
+
|
|
224
|
+
# Set wrap (and adjust vertices) if needed
|
|
225
|
+
if wrap is None:
|
|
226
|
+
wrap = ~np.isclose(np.sqrt(((vertices[-1] - vertices[0])**2).sum()), 0.0)
|
|
227
|
+
if not wrap:
|
|
228
|
+
# remove last vertice (should be equal to the first one)
|
|
229
|
+
vertices = np.delete(vertices, -1, axis=0)
|
|
230
|
+
|
|
231
|
+
# Array of shifted indices (on vertices)
|
|
232
|
+
ind = np.hstack((np.arange(1, vertices.shape[0]), [0]))
|
|
233
|
+
|
|
234
|
+
# Initialization
|
|
235
|
+
xx = np.atleast_2d(x)
|
|
236
|
+
res = np.zeros(xx.shape[0], dtype='bool')
|
|
237
|
+
if return_sum_of_angles:
|
|
238
|
+
res_sum = np.full((xx.shape[0],), np.nan)
|
|
239
|
+
|
|
240
|
+
xmin, ymin = vertices.min(axis=0)
|
|
241
|
+
xmax, ymax = vertices.max(axis=0)
|
|
242
|
+
|
|
243
|
+
for j, xj in enumerate(xx):
|
|
244
|
+
if xj[0] < xmin or xj[0] > xmax or xj[1] < ymin or xj[1] > ymax:
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
# Set a, b, c: edge of the triangles to compute angle between a and b
|
|
248
|
+
a = xj - vertices
|
|
249
|
+
|
|
250
|
+
# a_norm2: square norm of a
|
|
251
|
+
a_norm2 = (a**2).sum(axis=1)
|
|
252
|
+
|
|
253
|
+
b = a[ind]
|
|
254
|
+
b_norm2 = a_norm2[ind]
|
|
255
|
+
ab_norm = np.sqrt(a_norm2*b_norm2)
|
|
256
|
+
|
|
257
|
+
if np.any(np.isclose(ab_norm, 0, **kwargs)):
|
|
258
|
+
# xj on the border of the polygon
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
# Compute the sum of angles using the theorem of cosine
|
|
262
|
+
c = b - a
|
|
263
|
+
c_norm2 = (c**2).sum(axis=1)
|
|
264
|
+
sign = 2*(a[:,0]*b[:,1] - a[:,1]*b[:,0] > 0) - 1
|
|
265
|
+
sum_angles = np.sum(sign*np.arccos(np.minimum(1.0, np.maximum(-1.0, (a_norm2 + b_norm2 - c_norm2)/(2.0*ab_norm)))))
|
|
266
|
+
|
|
267
|
+
res[j] = np.isclose(np.abs(sum_angles), 2*np.pi)
|
|
268
|
+
if return_sum_of_angles:
|
|
269
|
+
res_sum[j] = sum_angles
|
|
270
|
+
|
|
271
|
+
if np.asarray(x).ndim == 1:
|
|
272
|
+
res = res[0]
|
|
273
|
+
if return_sum_of_angles:
|
|
274
|
+
res_sum = res_sum[0]
|
|
275
|
+
|
|
276
|
+
if return_sum_of_angles:
|
|
277
|
+
return res, res_sum
|
|
278
|
+
|
|
279
|
+
return res
|
|
280
|
+
# -----------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
# -----------------------------------------------------------------------------
|
|
283
|
+
def is_in_polygon_mp(x, vertices, wrap=None, return_sum_of_angles=False, nproc=-1, **kwargs):
|
|
284
|
+
"""
|
|
285
|
+
Computes the same as the function :func:`is_in_polygon`, using multiprocessing.
|
|
286
|
+
|
|
287
|
+
All the parameters except `nproc` are the same as those of the function
|
|
288
|
+
:func:`is_in_polygon`.
|
|
289
|
+
|
|
290
|
+
The number of processes used (in parallel) is n, and determined by the
|
|
291
|
+
parameter `nproc` (int, optional) as follows:
|
|
292
|
+
|
|
293
|
+
- if `nproc > 0`: n = `nproc`,
|
|
294
|
+
- if `nproc <= 0`: n = max(nmax+`nproc`, 1), where nmax is the total \
|
|
295
|
+
number of cpu(s) of the system (retrieved by `multiprocessing.cpu_count()`), \
|
|
296
|
+
i.e. all cpus except `-nproc` is used (but at least one)
|
|
297
|
+
|
|
298
|
+
See function :func:`is_in_polygon`.
|
|
299
|
+
"""
|
|
300
|
+
# fname = 'is_in_polygon_mp'
|
|
301
|
+
|
|
302
|
+
# Set wrap (and adjust vertices) if needed
|
|
303
|
+
if wrap is None:
|
|
304
|
+
wrap = ~np.isclose(np.sqrt(((vertices[-1] - vertices[0])**2).sum()), 0.0)
|
|
305
|
+
if not wrap:
|
|
306
|
+
# remove last vertice (should be equal to the first one)
|
|
307
|
+
vertices = np.delete(vertices, -1, axis=0)
|
|
308
|
+
|
|
309
|
+
# Set wrap key in keywords arguments
|
|
310
|
+
kwargs['wrap'] = True
|
|
311
|
+
|
|
312
|
+
# Set return_sum_of_angles key in keywords arguments
|
|
313
|
+
kwargs['return_sum_of_angles'] = return_sum_of_angles
|
|
314
|
+
|
|
315
|
+
# Initialization
|
|
316
|
+
xx = np.atleast_2d(x)
|
|
317
|
+
|
|
318
|
+
# Set number of processes (n)
|
|
319
|
+
if nproc > 0:
|
|
320
|
+
n = nproc
|
|
321
|
+
else:
|
|
322
|
+
n = min(multiprocessing.cpu_count()+nproc, 1)
|
|
323
|
+
|
|
324
|
+
# Set index for distributing tasks
|
|
325
|
+
q, r = np.divmod(xx.shape[0], n)
|
|
326
|
+
ids_proc = [i*q + min(i, r) for i in range(n+1)]
|
|
327
|
+
|
|
328
|
+
# Set pool of n workers
|
|
329
|
+
pool = multiprocessing.Pool(n)
|
|
330
|
+
out_pool = []
|
|
331
|
+
for i in range(n):
|
|
332
|
+
# Set i-th process
|
|
333
|
+
out_pool.append(pool.apply_async(is_in_polygon, args=(xx[ids_proc[i]:ids_proc[i+1]], vertices), kwds=kwargs))
|
|
334
|
+
|
|
335
|
+
# Properly end working process
|
|
336
|
+
pool.close() # Prevents any more tasks from being submitted to the pool,
|
|
337
|
+
pool.join() # then, wait for the worker processes to exit.
|
|
338
|
+
|
|
339
|
+
# Get result from each process
|
|
340
|
+
if return_sum_of_angles:
|
|
341
|
+
res = []
|
|
342
|
+
res_sum = []
|
|
343
|
+
for w in out_pool:
|
|
344
|
+
r, s = w.get()
|
|
345
|
+
res.append(r)
|
|
346
|
+
res_sum.append(s)
|
|
347
|
+
res = np.hstack(res)
|
|
348
|
+
res_sum = np.hstack(res_sum)
|
|
349
|
+
else:
|
|
350
|
+
res = np.hstack([w.get() for w in out_pool])
|
|
351
|
+
|
|
352
|
+
if np.asarray(x).ndim == 1:
|
|
353
|
+
res = res[0]
|
|
354
|
+
if return_sum_of_angles:
|
|
355
|
+
res_sum = res_sum[0]
|
|
356
|
+
|
|
357
|
+
if return_sum_of_angles:
|
|
358
|
+
return res, res_sum
|
|
359
|
+
|
|
360
|
+
return res
|
|
361
|
+
# -----------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
# -----------------------------------------------------------------------------
|
|
364
|
+
def rasterize_polygon_2d(
|
|
365
|
+
vertices,
|
|
366
|
+
nx=None, ny=None,
|
|
367
|
+
sx=None, sy=None,
|
|
368
|
+
ox=None, oy=None,
|
|
369
|
+
xmin_ext=0.0, xmax_ext=0.0,
|
|
370
|
+
ymin_ext=0.0, ymax_ext=0.0,
|
|
371
|
+
wrap=None,
|
|
372
|
+
logger=None,
|
|
373
|
+
**kwargs):
|
|
374
|
+
"""
|
|
375
|
+
Rasterizes a polygon (close line) in a 2D grid.
|
|
376
|
+
|
|
377
|
+
This function returns an image with one variable indicating for each cell
|
|
378
|
+
if it is inside (1) or outside (0) the polygon defined by the given
|
|
379
|
+
vertices.
|
|
380
|
+
|
|
381
|
+
The grid geometry of the output image is set by the given parameters or
|
|
382
|
+
computed from the vertices, as in function :func:`geone.img.imageFromPoints`,
|
|
383
|
+
i.e. for the x axis (similar for y):
|
|
384
|
+
|
|
385
|
+
- `ox` (origin), `nx` (number of cells) and `sx` (resolution, cell size)
|
|
386
|
+
- or only `nx`: `ox` and `sx` automatically computed
|
|
387
|
+
- or only `sx`: `ox` and `nx` automatically computed
|
|
388
|
+
|
|
389
|
+
In the two last cases, the parameters `xmin_ext`, `xmax_ext`, are used and
|
|
390
|
+
the approximate limit of the grid along x axis is set to x0, x1, where
|
|
391
|
+
|
|
392
|
+
- x0: min x coordinate of the vertices minus `xmin_ext`
|
|
393
|
+
- x1: max x coordinate of the vertices plus `xmax_ext`
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
vertices : 2D array
|
|
398
|
+
vertices of a polygon in 2D, each row of `vertices` contains the two
|
|
399
|
+
coordinates of one vertex; the segments of the polygon are obtained by
|
|
400
|
+
linking two successive vertices (as well as the last one with the first
|
|
401
|
+
one, if `wrap=True` (see below)), so that the vertices form a close line
|
|
402
|
+
(clockwise or counterclockwise)
|
|
403
|
+
|
|
404
|
+
nx : int, optional
|
|
405
|
+
number of grid cells along x axis; see above for possible inputs
|
|
406
|
+
|
|
407
|
+
ny : int, optional
|
|
408
|
+
number of grid cells along y axis; see above for possible inputs
|
|
409
|
+
|
|
410
|
+
sx : float, optional
|
|
411
|
+
cell size along x axis; see above for possible inputs
|
|
412
|
+
|
|
413
|
+
sy : float, optional
|
|
414
|
+
cell size along y axis; see above for possible inputs
|
|
415
|
+
|
|
416
|
+
ox : float, optional
|
|
417
|
+
origin of the grid along x axis (x coordinate of cell border);
|
|
418
|
+
see above for possible
|
|
419
|
+
|
|
420
|
+
oy : float, optional
|
|
421
|
+
origin of the grid along y axis (y coordinate of cell border);
|
|
422
|
+
see above for possible
|
|
423
|
+
|
|
424
|
+
Note: `(ox, oy)` is the "lower-left" corner of the grid
|
|
425
|
+
|
|
426
|
+
xmin_ext : float, default: 0.0
|
|
427
|
+
extension beyond the min x coordinate of the vertices (see above)
|
|
428
|
+
|
|
429
|
+
xmax_ext : float, default: 0.0
|
|
430
|
+
extension beyond the max x coordinate of the vertices (see above)
|
|
431
|
+
|
|
432
|
+
ymin_ext : float, default: 0.0
|
|
433
|
+
extension beyond the min y coordinate of the vertices (see above)
|
|
434
|
+
|
|
435
|
+
ymax_ext : float, default: 0.0
|
|
436
|
+
extension beyond the max y coordinate of the vertices (see above)
|
|
437
|
+
|
|
438
|
+
wrap : bool, optional
|
|
439
|
+
- if `True`: last and first vertices has to be linked to form a close line
|
|
440
|
+
- if `False`: last and first vertices should be the same ones (i.e. the \
|
|
441
|
+
vertices form a close line);
|
|
442
|
+
|
|
443
|
+
by default (`None`): `wrap` is automatically computed
|
|
444
|
+
|
|
445
|
+
logger : :class:`logging.Logger`, optional
|
|
446
|
+
logger (see package `logging`)
|
|
447
|
+
if specified, messages are written via `logger` (no print)
|
|
448
|
+
|
|
449
|
+
kwargs:
|
|
450
|
+
keyword arguments passed to function :func:`is_in_polygon`
|
|
451
|
+
|
|
452
|
+
Returns
|
|
453
|
+
-------
|
|
454
|
+
im : :class:`geone.img.Img`
|
|
455
|
+
output image (see above);
|
|
456
|
+
note: the image grid is defined in 3D with `nz=1`, `sz=1.0`, `oz=-0.5`
|
|
457
|
+
"""
|
|
458
|
+
# fname = 'rasterize_polygon_2d'
|
|
459
|
+
|
|
460
|
+
# Define grid geometry (image with no variable)
|
|
461
|
+
im = img.imageFromPoints(vertices,
|
|
462
|
+
nx=nx, ny=ny, sx=sx, sy=sy, ox=ox, oy=oy,
|
|
463
|
+
xmin_ext=xmin_ext, xmax_ext=xmax_ext,
|
|
464
|
+
ymin_ext=ymin_ext, ymax_ext=ymax_ext,
|
|
465
|
+
logger=logger)
|
|
466
|
+
|
|
467
|
+
# Rasterize: for each cell, check if its center is within the grid
|
|
468
|
+
v = np.asarray(is_in_polygon(np.array((im.xx().reshape(-1), im.yy().reshape(-1))).T, vertices, wrap=wrap, **kwargs)).astype('float')
|
|
469
|
+
im.append_var(v, varname='in', logger=logger)
|
|
470
|
+
|
|
471
|
+
return im
|
|
472
|
+
# -----------------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
# -----------------------------------------------------------------------------
|
|
475
|
+
def rasterize_polygon_2d_mp(
|
|
476
|
+
vertices,
|
|
477
|
+
nx=None, ny=None,
|
|
478
|
+
sx=None, sy=None,
|
|
479
|
+
ox=None, oy=None,
|
|
480
|
+
xmin_ext=0.0, xmax_ext=0.0,
|
|
481
|
+
ymin_ext=0.0, ymax_ext=0.0,
|
|
482
|
+
wrap=None,
|
|
483
|
+
nproc=-1,
|
|
484
|
+
logger=None,
|
|
485
|
+
**kwargs):
|
|
486
|
+
"""
|
|
487
|
+
Computes the same as the function :func:`rasterize_polygon_2d`, using multiprocessing.
|
|
488
|
+
|
|
489
|
+
All the parameters except `nproc` are the same as those of the function
|
|
490
|
+
:func:`rasterize_polygon_2d`.
|
|
491
|
+
|
|
492
|
+
The number of processes used (in parallel) is n, and determined by the
|
|
493
|
+
parameter `nproc` (int, optional) as follows:
|
|
494
|
+
|
|
495
|
+
- if `nproc > 0`: n = `nproc`,
|
|
496
|
+
- if `nproc <= 0`: n = max(nmax+`nproc`, 1), where nmax is the total \
|
|
497
|
+
number of cpu(s) of the system (retrieved by `multiprocessing.cpu_count()`), \
|
|
498
|
+
i.e. all cpus except `-nproc` is used (but at least one).
|
|
499
|
+
|
|
500
|
+
See function :func:`rasterize_polygon_2d`.
|
|
501
|
+
"""
|
|
502
|
+
# fname = 'rasterize_polygon_2d_mp'
|
|
503
|
+
|
|
504
|
+
# Define grid geometry (image with no variable)
|
|
505
|
+
im = img.imageFromPoints(vertices,
|
|
506
|
+
nx=nx, ny=ny, sx=sx, sy=sy, ox=ox, oy=oy,
|
|
507
|
+
xmin_ext=xmin_ext, xmax_ext=xmax_ext,
|
|
508
|
+
ymin_ext=ymin_ext, ymax_ext=ymax_ext,
|
|
509
|
+
logger=logger)
|
|
510
|
+
|
|
511
|
+
# Rasterize: for each cell, check if its center is within the grid
|
|
512
|
+
v = np.asarray(is_in_polygon_mp(np.array((im.xx().reshape(-1), im.yy().reshape(-1))).T, vertices, wrap=wrap, nproc=nproc, **kwargs)).astype('float')
|
|
513
|
+
im.append_var(v, varname='in', logger=logger)
|
|
514
|
+
|
|
515
|
+
return im
|
|
516
|
+
# -----------------------------------------------------------------------------
|
|
517
|
+
|
|
518
|
+
# -----------------------------------------------------------------------------
|
|
519
|
+
def curv_coord_2d_from_center_line(
|
|
520
|
+
x, cl_position, im_cl_dist,
|
|
521
|
+
cl_u=None,
|
|
522
|
+
gradx=None,
|
|
523
|
+
grady=None,
|
|
524
|
+
dg=None,
|
|
525
|
+
gradtol=1.e-5,
|
|
526
|
+
path_len_max=10000,
|
|
527
|
+
return_path=False,
|
|
528
|
+
verbose=1,
|
|
529
|
+
logger=None):
|
|
530
|
+
"""
|
|
531
|
+
Computes curvilinear coordinates in 2D from a center line, for points given in standard coordinates.
|
|
532
|
+
|
|
533
|
+
This functions allows to change coordinates system in 2D. For a point in 2D,
|
|
534
|
+
let the coordinates
|
|
535
|
+
|
|
536
|
+
- u = (u1, u2) (in 2D) in curvilinear system
|
|
537
|
+
- x = (x1, x2) (in 2D) in standard system
|
|
538
|
+
|
|
539
|
+
The curvilinear coordinates system (u) is defined according to a center line
|
|
540
|
+
in a 2D grid as follows:
|
|
541
|
+
|
|
542
|
+
- considering the distance map (geone image `im_cl_dist`) of L2 distance \
|
|
543
|
+
to the center line (`cl_position`)
|
|
544
|
+
- the path from x to the point I on the center line is computed, descending \
|
|
545
|
+
the gradient (`gradx`, `grady`) of the distance map
|
|
546
|
+
- u = (u1, u2) is defined as:
|
|
547
|
+
- u1: the distance along the center line to the point I,
|
|
548
|
+
- u2: +/-the value of the distance map at x, with
|
|
549
|
+
* sign + for point "at left" of the center line and,
|
|
550
|
+
* sign - for point "at right" of the center line.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
x : 2D array-like or 1D array-like
|
|
555
|
+
points coordinates in standard system (should be in the grid of the image
|
|
556
|
+
`im_cl_dist`, see below), each row `x[i]` (if 2D array-like) (or `x` if
|
|
557
|
+
1D array-like) contains the two coordinates of one point
|
|
558
|
+
|
|
559
|
+
cl_position : 2D array of shape (n, 2)
|
|
560
|
+
position of the points of the center line (in standard system);
|
|
561
|
+
note: the distance between two successive points of the center line gives
|
|
562
|
+
the resolution of the u1 coordinate
|
|
563
|
+
|
|
564
|
+
im_cl_dist : :class:`geone.img.Img`
|
|
565
|
+
image of the distance to the center line:
|
|
566
|
+
|
|
567
|
+
- its grid is the "support" of standard coordinate system and it \
|
|
568
|
+
should contain all the points `x`
|
|
569
|
+
- the center line (`cl_position`) should "separate" the image grid \
|
|
570
|
+
in two (disconnected) regions
|
|
571
|
+
- this image can be computed using the function \
|
|
572
|
+
`geone.geosclassicinterface.imgDistanceImage`
|
|
573
|
+
|
|
574
|
+
cl_u : 1D array-like of length n, optional
|
|
575
|
+
distance along the center line (automatically computed if not given
|
|
576
|
+
(`None`)), used for determining u1 coordinate
|
|
577
|
+
|
|
578
|
+
gradx, grady : 2D array-like, optional
|
|
579
|
+
gradient of the distance to the centerline, array of shape
|
|
580
|
+
`(im_cl_dist.ny, im_cl_dist.nx)` (automatically computed if not given
|
|
581
|
+
(`None`));
|
|
582
|
+
`gradx[iy, ix], grady[iy, ix]`: gradient in grid cell of index `iy`, `ix`
|
|
583
|
+
along x and y axes respectively
|
|
584
|
+
|
|
585
|
+
dg : float, optional
|
|
586
|
+
step (length) used for descending the gradient map; by default (`None`):
|
|
587
|
+
`dg` is set to the minimal distance between two successive points in
|
|
588
|
+
`cl_position`, (i.e. minimal difference between two succesive values in
|
|
589
|
+
`cl_u`)
|
|
590
|
+
|
|
591
|
+
gradtol : float, default: 1.e-5
|
|
592
|
+
tolerance for the gradient magnitude (if the magintude of the gradient
|
|
593
|
+
is below `gradtol`, it is considered as zero vector)
|
|
594
|
+
|
|
595
|
+
path_len_max : int, default: 10000
|
|
596
|
+
maximal length of the path from the initial point(s) (`x`) to the center
|
|
597
|
+
line
|
|
598
|
+
|
|
599
|
+
return_path : bool, default: False
|
|
600
|
+
indicates if the path(s) from the initial point(s) (`x`) to the point(s)
|
|
601
|
+
I of the centerline (descending the gradient of the distance map) is
|
|
602
|
+
(are) retrieved
|
|
603
|
+
|
|
604
|
+
verbose : int, default: 1
|
|
605
|
+
verbose mode, integer >=0, higher implies more display
|
|
606
|
+
|
|
607
|
+
logger : :class:`logging.Logger`, optional
|
|
608
|
+
logger (see package `logging`)
|
|
609
|
+
if specified, messages are written via `logger` (no print)
|
|
610
|
+
|
|
611
|
+
Returns
|
|
612
|
+
-------
|
|
613
|
+
u : 2D array or 1D array (same shape as `x`)
|
|
614
|
+
coordinates in curvilinear system of the input point(s) `x` (see above)
|
|
615
|
+
|
|
616
|
+
x_path : list of 2D arrays (or 2D array), optional
|
|
617
|
+
path(s) from the initial point(s) to the point(s) I of the centerline
|
|
618
|
+
(descending the gradient of the distance map):
|
|
619
|
+
|
|
620
|
+
- `x_path[i]` : 2D array of floats with 2 columns
|
|
621
|
+
* `xpath[i][j]` is the coordinates (in standard system) of the \
|
|
622
|
+
j-th point of the path from `x[i]` to the center line
|
|
623
|
+
|
|
624
|
+
note: if `x` is reduced to one point and given as 1D array-like, then
|
|
625
|
+
`x_path` is a 2D array of floats with 2 columns containing the path from
|
|
626
|
+
`x` to the center line;
|
|
627
|
+
returned if `returned_path=True`
|
|
628
|
+
"""
|
|
629
|
+
fname = 'curv_coord_2d_from_center_line'
|
|
630
|
+
|
|
631
|
+
if cl_u is None:
|
|
632
|
+
cl_u = np.insert(np.cumsum(np.sqrt(((cl_position[1:,:] - cl_position[:-1,:])**2).sum(axis=1))), 0, 0.0)
|
|
633
|
+
|
|
634
|
+
if gradx is None or grady is None:
|
|
635
|
+
# Gradient of distance map
|
|
636
|
+
grady, gradx = np.gradient(im_cl_dist.val[0,0])
|
|
637
|
+
gradx = gradx / im_cl_dist.sx
|
|
638
|
+
grady = grady / im_cl_dist.sy
|
|
639
|
+
|
|
640
|
+
if dg is None:
|
|
641
|
+
dg = np.diff(cl_u).min()
|
|
642
|
+
|
|
643
|
+
x = np.asarray(x)
|
|
644
|
+
x_ndim = x.ndim
|
|
645
|
+
x = np.atleast_2d(x)
|
|
646
|
+
u = np.zeros_like(x)
|
|
647
|
+
|
|
648
|
+
if return_path:
|
|
649
|
+
x_path = []
|
|
650
|
+
|
|
651
|
+
for i in range(x.shape[0]):
|
|
652
|
+
# Treat the i-th point
|
|
653
|
+
x_cur = x[i]
|
|
654
|
+
if return_path:
|
|
655
|
+
xi_path = [x_cur]
|
|
656
|
+
|
|
657
|
+
u2 = 0.0
|
|
658
|
+
d_prev = np.inf
|
|
659
|
+
|
|
660
|
+
for j in range(path_len_max):
|
|
661
|
+
# index in the grid (and interpolation factor) of the "current" point
|
|
662
|
+
tx = max(0, min((x_cur[0] - im_cl_dist.ox)/im_cl_dist.sx - 0.5, im_cl_dist.nx - 1.00001))
|
|
663
|
+
ix = int(tx)
|
|
664
|
+
tx = tx - int(tx)
|
|
665
|
+
|
|
666
|
+
ty = max(0, min((x_cur[1] - im_cl_dist.oy)/im_cl_dist.sy - 0.5, im_cl_dist.ny - 1.00001))
|
|
667
|
+
iy = int(ty)
|
|
668
|
+
ty = ty - int(ty)
|
|
669
|
+
|
|
670
|
+
# "current" distance to the center line
|
|
671
|
+
d_cur = (1.-ty)*((1.-tx)*im_cl_dist.val[0, 0, iy, ix] + tx*im_cl_dist.val[0, 0, iy, ix+1]) + ty*((1.-tx)*im_cl_dist.val[0, 0, iy+1, ix] + tx*im_cl_dist.val[0, 0, iy+1, ix+1])
|
|
672
|
+
|
|
673
|
+
# if np.abs(d_cur) < dg or d_cur*d_prev < 0:
|
|
674
|
+
if d_cur < dg or d_cur > d_prev:
|
|
675
|
+
break
|
|
676
|
+
|
|
677
|
+
# "current" gradient
|
|
678
|
+
gradx_cur = (1.-ty)*((1.-tx)*gradx[iy, ix] + tx*gradx[iy, ix+1]) + ty*((1.-tx)*gradx[iy+1, ix] + tx*gradx[iy+1, ix+1])
|
|
679
|
+
grady_cur = (1.-ty)*((1.-tx)*grady[iy, ix] + tx*grady[iy, ix+1]) + ty*((1.-tx)*grady[iy+1, ix] + tx*grady[iy+1, ix+1])
|
|
680
|
+
|
|
681
|
+
gradl = np.sqrt(gradx_cur**2 + grady_cur**2)
|
|
682
|
+
if gradl < gradtol:
|
|
683
|
+
break
|
|
684
|
+
|
|
685
|
+
# compute next point descending the gradient
|
|
686
|
+
# x_cur = x_cur - np.sign(d_cur) * dg*np.array([gradx_cur, grady_cur])/gradl
|
|
687
|
+
x_cur = x_cur - dg*np.array([gradx_cur, grady_cur])/gradl
|
|
688
|
+
if return_path:
|
|
689
|
+
xi_path.append(x_cur)
|
|
690
|
+
|
|
691
|
+
u2 = u2 + dg
|
|
692
|
+
d_prev = d_cur
|
|
693
|
+
|
|
694
|
+
# Finalize the computation of coordinate (u1, u2)
|
|
695
|
+
d_cur = ((cl_position[:-1,:] - x_cur)**2).sum(axis=1)
|
|
696
|
+
try:
|
|
697
|
+
k = np.where(d_cur == d_cur.min())[0][0]
|
|
698
|
+
except:
|
|
699
|
+
# k = len(cl_position[-2])
|
|
700
|
+
k = len(cl_position) - 2
|
|
701
|
+
if verbose > 0:
|
|
702
|
+
if logger:
|
|
703
|
+
logger.warning(f'{fname}: closest point on center line not found (last segment selected)')
|
|
704
|
+
else:
|
|
705
|
+
print(f'{fname}: WARNING: closest point on center line not found (last segment selected)')
|
|
706
|
+
|
|
707
|
+
u1 = cl_u[k]
|
|
708
|
+
|
|
709
|
+
s = np.sign(np.linalg.det(np.vstack((cl_position[k+1] - cl_position[k], x[i] - x_cur))))
|
|
710
|
+
u2 = s*u2
|
|
711
|
+
|
|
712
|
+
u[i] = np.array([u1, u2])
|
|
713
|
+
|
|
714
|
+
if return_path:
|
|
715
|
+
x_path.append(np.asarray(xi_path))
|
|
716
|
+
|
|
717
|
+
if return_path:
|
|
718
|
+
if x_ndim == 1:
|
|
719
|
+
u = u[0]
|
|
720
|
+
x_path = x_path[0]
|
|
721
|
+
return u, x_path
|
|
722
|
+
else:
|
|
723
|
+
if x_ndim == 1:
|
|
724
|
+
u = u[0]
|
|
725
|
+
return u
|
|
726
|
+
# -----------------------------------------------------------------------------
|
|
727
|
+
|
|
728
|
+
# -----------------------------------------------------------------------------
|
|
729
|
+
def curv_coord_2d_from_center_line_mp(
|
|
730
|
+
x, cl_position, im_cl_dist,
|
|
731
|
+
cl_u=None,
|
|
732
|
+
gradx=None,
|
|
733
|
+
grady=None,
|
|
734
|
+
dg=None,
|
|
735
|
+
gradtol=1.e-5,
|
|
736
|
+
path_len_max=10000,
|
|
737
|
+
return_path=False,
|
|
738
|
+
nproc=-1,
|
|
739
|
+
logger=None):
|
|
740
|
+
"""
|
|
741
|
+
Computes the same as the function :func:`curv_coord_2d_from_center_line`, using multiprocessing.
|
|
742
|
+
|
|
743
|
+
All the parameters except `nproc` are the same as those of the function
|
|
744
|
+
:func:`curv_coord_2d_from_center_line`.
|
|
745
|
+
|
|
746
|
+
The number of processes used (in parallel) is n, and determined by the
|
|
747
|
+
parameter `nproc` (int, optional) as follows:
|
|
748
|
+
|
|
749
|
+
- if `nproc > 0`: n = `nproc`,
|
|
750
|
+
- if `nproc <= 0`: n = max(nmax+`nproc`, 1), where nmax is the total \
|
|
751
|
+
number of cpu(s) of the system (retrieved by `multiprocessing.cpu_count()`), \
|
|
752
|
+
i.e. all cpus except `-nproc` is used (but at least one).
|
|
753
|
+
|
|
754
|
+
See function :func:`curv_coord_2d_from_center_line`.
|
|
755
|
+
"""
|
|
756
|
+
# fname = 'curv_coord_2d_from_center_line_mp'
|
|
757
|
+
|
|
758
|
+
# Initialization
|
|
759
|
+
xx = np.atleast_2d(x)
|
|
760
|
+
|
|
761
|
+
# Set number of processes (n)
|
|
762
|
+
if nproc > 0:
|
|
763
|
+
n = nproc
|
|
764
|
+
else:
|
|
765
|
+
n = min(multiprocessing.cpu_count()+nproc, 1)
|
|
766
|
+
|
|
767
|
+
# Set index for distributing tasks
|
|
768
|
+
q, r = np.divmod(xx.shape[0], n)
|
|
769
|
+
ids_proc = [i*q + min(i, r) for i in range(n+1)]
|
|
770
|
+
|
|
771
|
+
kwargs = dict(cl_u=cl_u, gradx=gradx, grady=grady, dg=dg, gradtol=gradtol, path_len_max=path_len_max, return_path=return_path, verbose=0, logger=logger)
|
|
772
|
+
# Set pool of n workers
|
|
773
|
+
pool = multiprocessing.Pool(n)
|
|
774
|
+
out_pool = []
|
|
775
|
+
for i in range(n):
|
|
776
|
+
# Set i-th process
|
|
777
|
+
out_pool.append(pool.apply_async(curv_coord_2d_from_center_line, args=(xx[ids_proc[i]:ids_proc[i+1]], cl_position, im_cl_dist), kwds=kwargs))
|
|
778
|
+
|
|
779
|
+
# Properly end working process
|
|
780
|
+
pool.close() # Prevents any more tasks from being submitted to the pool,
|
|
781
|
+
pool.join() # then, wait for the worker processes to exit.
|
|
782
|
+
|
|
783
|
+
# Get result from each process
|
|
784
|
+
if return_path:
|
|
785
|
+
u = []
|
|
786
|
+
x_path = []
|
|
787
|
+
for p in out_pool:
|
|
788
|
+
u_p, x_path_p = p.get()
|
|
789
|
+
u.extend(u_p)
|
|
790
|
+
x_path.extend(x_path_p)
|
|
791
|
+
if np.asarray(x).ndim == 1:
|
|
792
|
+
u = u[0]
|
|
793
|
+
x_path = x_path[0]
|
|
794
|
+
return u, x_path
|
|
795
|
+
else:
|
|
796
|
+
u = np.vstack([p.get() for p in out_pool])
|
|
797
|
+
if np.asarray(x).ndim == 1:
|
|
798
|
+
u = u[0]
|
|
799
|
+
return u
|
|
800
|
+
# -----------------------------------------------------------------------------
|
|
801
|
+
|
|
802
|
+
##### OLD BELOW #####
|
|
803
|
+
# # -----------------------------------------------------------------------------
|
|
804
|
+
# def sector_angle(x, line, **kwargs):
|
|
805
|
+
# """
|
|
806
|
+
# Checks if point(s) (`x`) is (are) in a polygon given by its vertices
|
|
807
|
+
# `vertices` forming a close line.
|
|
808
|
+
#
|
|
809
|
+
# To check if a point is in the polygon, the method consists in computing
|
|
810
|
+
# the vectors from the given point to the vertices of the polygon and
|
|
811
|
+
# the sum of signed angles between two successives vectors (and the last
|
|
812
|
+
# and first one). The point is in the polygon if and only if this sum is
|
|
813
|
+
# equal to +/- 2 pi.
|
|
814
|
+
#
|
|
815
|
+
# :param x: (1d-array of 2 floats, or 2d-array of shape (m, 2))
|
|
816
|
+
# one or several points in 2D
|
|
817
|
+
# :param vertices:(2d-array of shape (n,2)) vertices of a polygon in 2D,
|
|
818
|
+
# the segments of the polygon are obtained by linking
|
|
819
|
+
# two successive vertices (and the last one with the
|
|
820
|
+
# first one, if `wrap` is True (see below) or automatically
|
|
821
|
+
# checked if it is needed), so that the vertices form a
|
|
822
|
+
# close line (clockwise or counterclockwise)
|
|
823
|
+
# :param wrap: (bool)
|
|
824
|
+
# - if True, last and first vertices has to be linked
|
|
825
|
+
# to form a close line,
|
|
826
|
+
# - if False, last and first vertices should be the same
|
|
827
|
+
# ones (i.e. the vertices form a close line)
|
|
828
|
+
# - if None, automatically computed
|
|
829
|
+
# :param kwargs: keyword arguments passed to `numpy.isclose` (see below)
|
|
830
|
+
#
|
|
831
|
+
# :return: (bool or 1d-array of bool of shape (m,)) True / False
|
|
832
|
+
# value(s) indicating if the point(s) `x` is (are)
|
|
833
|
+
# inside / outside the polygon
|
|
834
|
+
# """
|
|
835
|
+
#
|
|
836
|
+
# # Initialization
|
|
837
|
+
# xx = np.atleast_2d(x)
|
|
838
|
+
# res = np.zeros(xx.shape[0], dtype='bool')
|
|
839
|
+
#
|
|
840
|
+
# for j, xj in enumerate(xx):
|
|
841
|
+
# # Set a, b, c: edge of the triangles to compute angle between a and b
|
|
842
|
+
# v = xj - vertices
|
|
843
|
+
#
|
|
844
|
+
# # v_norm2: square norm of v
|
|
845
|
+
# v_norm2 = (a**2).sum(axis=1)
|
|
846
|
+
#
|
|
847
|
+
# a = v[:-1]
|
|
848
|
+
# b = v[1:]
|
|
849
|
+
# # b_norm2 = v_norm2[1:]
|
|
850
|
+
# ab_norm = np.sqrt(v_norm2[:-1]*v_norm2[1:])
|
|
851
|
+
#
|
|
852
|
+
# ind = np.isclose(ab_norm, 0, **kwargs)
|
|
853
|
+
# # Compute the sum of angles using the theorem of cosine
|
|
854
|
+
# c = b - a
|
|
855
|
+
# c_norm2 = (c**2).sum(axis=1)
|
|
856
|
+
# sign = 2*(a[:,0]*b[:,1] - a[:,1]*b[:,0] > 0) - 1
|
|
857
|
+
# sum_angles = np.sum(sign[ind]*np.arccos(np.minimum(1.0, np.maximum(-1.0, (a_norm2[ind] + b_norm2[ind] - c_norm2[ind])/(2.0*ab_norm[ind])))))
|
|
858
|
+
# res[i] = sum_angles > 0
|
|
859
|
+
#
|
|
860
|
+
# return res
|
|
861
|
+
# # -----------------------------------------------------------------------------
|