lapy 0.3.0__py3-none-any.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.
- lapy/Conformal.py +441 -0
- lapy/DiffGeo.py +613 -0
- lapy/FuncIO.py +295 -0
- lapy/Heat.py +92 -0
- lapy/Plot.py +570 -0
- lapy/ShapeDNA.py +139 -0
- lapy/Solver.py +776 -0
- lapy/TetIO.py +209 -0
- lapy/TetMesh.py +231 -0
- lapy/TriaIO.py +470 -0
- lapy/TriaMesh.py +890 -0
- lapy/__init__.py +5 -0
- lapy/_version.py +5 -0
- lapy/commands/__init__.py +0 -0
- lapy/commands/sys_info.py +18 -0
- lapy/read_geometry.py +162 -0
- lapy/utils/__init__.py +1 -0
- lapy/utils/_config.py +99 -0
- lapy/utils/_imports.py +57 -0
- lapy-0.3.0.dist-info/LICENSE +21 -0
- lapy-0.3.0.dist-info/METADATA +156 -0
- lapy-0.3.0.dist-info/RECORD +25 -0
- lapy-0.3.0.dist-info/WHEEL +5 -0
- lapy-0.3.0.dist-info/entry_points.txt +2 -0
- lapy-0.3.0.dist-info/top_level.txt +1 -0
lapy/Conformal.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Adopted from Matlab code at
|
|
2
|
+
# https://github.com/garyptchoi/spherical-conformal-map
|
|
3
|
+
# with this
|
|
4
|
+
# Copyright (c) 2013-2020, Gary Pui-Tung Choi
|
|
5
|
+
# https://math.mit.edu/~ptchoi
|
|
6
|
+
# and has been distributed with the Apache 2 License
|
|
7
|
+
|
|
8
|
+
# If you use this code in your own work, please cite the following paper:
|
|
9
|
+
# [1] P. T. Choi, K. C. Lam, and L. M. Lui,
|
|
10
|
+
# "FLASH: Fast Landmark Aligned Spherical Harmonic Parameterization for Genus-0
|
|
11
|
+
# Closed Brain Surfaces."
|
|
12
|
+
# SIAM Journal on Imaging Sciences, vol. 8, no. 1, pp. 67-94, 2015.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from scipy import sparse
|
|
17
|
+
from scipy.optimize import minimize
|
|
18
|
+
|
|
19
|
+
from .Solver import Solver
|
|
20
|
+
from .TriaMesh import TriaMesh
|
|
21
|
+
from .utils._imports import import_optional_dependency
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def spherical_conformal_map(tria):
|
|
25
|
+
"""
|
|
26
|
+
A linear method for computing spherical conformal map of a genus-0 closed surface
|
|
27
|
+
|
|
28
|
+
Input: TriaMesh (vertices and faces)
|
|
29
|
+
Output:
|
|
30
|
+
mapped_vertices: nv x 3 vertex coordinates of the spherical conformal parameterization
|
|
31
|
+
|
|
32
|
+
If you use this code in your own work, please cite the following paper:
|
|
33
|
+
[1] P. T. Choi, K. C. Lam, and L. M. Lui,
|
|
34
|
+
"FLASH: Fast Landmark Aligned Spherical Harmonic Parameterization for Genus-0 Closed Brain Surfaces."
|
|
35
|
+
SIAM Journal on Imaging Sciences, vol. 8, no. 1, pp. 67-94, 2015.
|
|
36
|
+
|
|
37
|
+
Adopted from Matlab code at
|
|
38
|
+
https://github.com/garyptchoi/spherical-conformal-map
|
|
39
|
+
with this
|
|
40
|
+
Copyright (c) 2013-2020, Gary Pui-Tung Choi
|
|
41
|
+
https://math.mit.edu/~ptchoi
|
|
42
|
+
and has been distributed with the Apache 2 License
|
|
43
|
+
"""
|
|
44
|
+
# Check whether the input mesh is spherical topology (genus-0)
|
|
45
|
+
if tria.euler() != 2:
|
|
46
|
+
print("ERROR: The mesh is not a genus-0 closed surface ..")
|
|
47
|
+
raise ValueError("not genus-0")
|
|
48
|
+
|
|
49
|
+
# Find the most regular triangle as the "big triangle"
|
|
50
|
+
tquals = tria.tria_qualities()
|
|
51
|
+
bigtri = np.argmax(tquals)
|
|
52
|
+
# print(bigtri, tquals[bigtri])
|
|
53
|
+
# If it turns out that the spherical parameterization result is homogeneous
|
|
54
|
+
# you can try to change bigtri to the id of some other triangles with good quality
|
|
55
|
+
|
|
56
|
+
# North pole step: Compute spherical map by solving laplace equation on a big triangle
|
|
57
|
+
nv = tria.v.shape[0]
|
|
58
|
+
S = Solver(tria)
|
|
59
|
+
M = S.stiffness.astype(complex)
|
|
60
|
+
|
|
61
|
+
p0 = tria.t[bigtri, 0]
|
|
62
|
+
p1 = tria.t[bigtri, 1]
|
|
63
|
+
p2 = tria.t[bigtri, 2]
|
|
64
|
+
fixed = tria.t[bigtri, :]
|
|
65
|
+
|
|
66
|
+
# set all rows and cols with fixed vidxs to zero
|
|
67
|
+
# and set diag entries to 1
|
|
68
|
+
mrow, mcol, mval = sparse.find(M[fixed, :])
|
|
69
|
+
M = (
|
|
70
|
+
M
|
|
71
|
+
- sparse.csc_matrix((mval, (fixed[mrow], mcol)), shape=(nv, nv))
|
|
72
|
+
+ sparse.csc_matrix(((1, 1, 1), (fixed, fixed)), shape=(nv, nv))
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# find embedding of the bigtria (boundary condition later)
|
|
76
|
+
# arbitrarily set first two points
|
|
77
|
+
x0, y0, x1, y1 = 0, 0, 1, 0
|
|
78
|
+
a = tria.v[p1, :] - tria.v[p0, :]
|
|
79
|
+
b = tria.v[p2, :] - tria.v[p0, :]
|
|
80
|
+
sin1 = np.linalg.norm(np.cross(a, b)) / (np.linalg.norm(a) * np.linalg.norm(b))
|
|
81
|
+
ori_h = np.linalg.norm(b) * sin1
|
|
82
|
+
ratio = np.sqrt(((x0 - x1) ** 2 + (y0 - y1) ** 2)) / np.linalg.norm(a)
|
|
83
|
+
y2 = ori_h * ratio # compute the coordinates of the third vertex
|
|
84
|
+
x2 = np.sqrt(np.linalg.norm(b) ** 2 * ratio**2 - y2**2)
|
|
85
|
+
# should be around (0.5, sqrt(3)/2) if we found an equilateral bigtri
|
|
86
|
+
|
|
87
|
+
# Solve the Laplace equation to obtain a harmonic map
|
|
88
|
+
c = np.zeros((nv, 1))
|
|
89
|
+
c[p0], c[p1], c[p2] = x0, x1, x2
|
|
90
|
+
d = np.zeros((nv, 1))
|
|
91
|
+
d[p0], d[p1], d[p2] = y0, y1, y2
|
|
92
|
+
rhs = np.empty(c.shape[:-1], dtype=complex)
|
|
93
|
+
rhs.real = c.flatten()
|
|
94
|
+
rhs.imag = d.flatten()
|
|
95
|
+
|
|
96
|
+
z = sparse_symmetric_solve(M, rhs)
|
|
97
|
+
z = np.squeeze(np.array(z))
|
|
98
|
+
z = z - np.mean(z, axis=0)
|
|
99
|
+
|
|
100
|
+
# inverse stereographic projection (not scaled well)
|
|
101
|
+
S = inverse_stereographic(z)
|
|
102
|
+
|
|
103
|
+
# Find optimal big triangle size
|
|
104
|
+
w = np.empty(S.shape[:-1], dtype=complex)
|
|
105
|
+
w.real = (S[:, 0] / (1 + S[:, 2])).flatten()
|
|
106
|
+
w.imag = (S[:, 1] / (1 + S[:, 2])).flatten()
|
|
107
|
+
|
|
108
|
+
# find the index of the southernmost triangle
|
|
109
|
+
index = np.argsort(
|
|
110
|
+
np.abs(z[tria.t[:, 0]]) + np.abs(z[tria.t[:, 1]]) + np.abs(z[tria.t[:, 2]])
|
|
111
|
+
)
|
|
112
|
+
inner = index[0]
|
|
113
|
+
if inner == bigtri:
|
|
114
|
+
inner = index[1]
|
|
115
|
+
|
|
116
|
+
# Compute the size of the northern most and the southern most triangles
|
|
117
|
+
NorthTriSide = (
|
|
118
|
+
np.abs(z[tria.t[bigtri, 0]] - z[tria.t[bigtri, 1]])
|
|
119
|
+
+ np.abs(z[tria.t[bigtri, 1]] - z[tria.t[bigtri, 2]])
|
|
120
|
+
+ np.abs(z[tria.t[bigtri, 2]] - z[tria.t[bigtri, 0]])
|
|
121
|
+
) / 3.0
|
|
122
|
+
|
|
123
|
+
SouthTriSide = (
|
|
124
|
+
np.abs(w[tria.t[inner, 0]] - w[tria.t[inner, 1]])
|
|
125
|
+
+ np.abs(w[tria.t[inner, 1]] - w[tria.t[inner, 2]])
|
|
126
|
+
+ np.abs(w[tria.t[inner, 2]] - w[tria.t[inner, 0]])
|
|
127
|
+
) / 3.0
|
|
128
|
+
|
|
129
|
+
# rescale to get the best distribution
|
|
130
|
+
z = z * np.sqrt(NorthTriSide * SouthTriSide) / NorthTriSide
|
|
131
|
+
|
|
132
|
+
# inverse stereographic projection (now distributed well)
|
|
133
|
+
S = inverse_stereographic(z)
|
|
134
|
+
|
|
135
|
+
if np.isnan(np.sum(S)):
|
|
136
|
+
raise ValueError("Error: projection contains nan value(s)!")
|
|
137
|
+
# could revert to spherical tutte map here
|
|
138
|
+
|
|
139
|
+
# South pole step
|
|
140
|
+
idx = np.argsort(S[:, 2])
|
|
141
|
+
|
|
142
|
+
# number of points near the south pole to be fixed
|
|
143
|
+
# simply set it to be 1/10 of the total number of vertices (can be changed)
|
|
144
|
+
# In case the spherical parameterization is not good, change 10 to
|
|
145
|
+
# something smaller (e.g. 2)
|
|
146
|
+
fixnum = np.maximum(round(nv / 10), 3)
|
|
147
|
+
fixed = idx[0 : np.minimum(nv, fixnum)]
|
|
148
|
+
|
|
149
|
+
# south pole stereographic projection
|
|
150
|
+
P = np.column_stack(
|
|
151
|
+
(S[:, 0] / (1 + S[:, 2]), S[:, 1] / (1 + S[:, 2]), np.zeros(nv))
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# compute the Beltrami coefficient (value per triangle)
|
|
155
|
+
triasouth = TriaMesh(P, tria.t)
|
|
156
|
+
mu = beltrami_coefficient(triasouth, tria.v)
|
|
157
|
+
|
|
158
|
+
# compose the map with another quasi-conformal map to cancel the distortion
|
|
159
|
+
mapping = linear_beltrami_solver(triasouth, mu, fixed, P[fixed, :])
|
|
160
|
+
|
|
161
|
+
if np.isnan(np.sum(mapping)):
|
|
162
|
+
# if the result has NaN entries, then most probably the number of
|
|
163
|
+
# boundary constraints is not large enough
|
|
164
|
+
# increase the number of boundary constrains and run again
|
|
165
|
+
print("South pole compsed map has nan value(s)!")
|
|
166
|
+
fixnum = fixnum * 5 # again, this number can be changed
|
|
167
|
+
fixed = idx[0 : np.minimum(nv, fixnum)]
|
|
168
|
+
mapping = linear_beltrami_solver(triasouth, mu, fixed, P[fixed, :])
|
|
169
|
+
if np.isnan(np.sum(mapping)):
|
|
170
|
+
mapping = P # use the old result
|
|
171
|
+
|
|
172
|
+
# inverse south pole stereographic projection
|
|
173
|
+
mapping = inverse_stereographic(mapping)
|
|
174
|
+
return mapping
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def mobius_area_correction_spherical(tria, mapping):
|
|
178
|
+
"""
|
|
179
|
+
Find an optimal Mobius transformation for reducing the area distortion of a spherical conformal parameterization
|
|
180
|
+
using the method in [1].
|
|
181
|
+
|
|
182
|
+
Input:
|
|
183
|
+
tria : TriaMesh (vertices, triangle) of genus-0 closed triangle mesh
|
|
184
|
+
mapping: nv x 3 vertex coordinates of the spherical conformal parameterization
|
|
185
|
+
|
|
186
|
+
Output:
|
|
187
|
+
map_mobius: nv x 3 vertex coordinates of the updated spherical conformal parameterization
|
|
188
|
+
x: the optimal parameters for the Mobius transformation, where
|
|
189
|
+
f(z) = \frac{az+b}{cz+d}
|
|
190
|
+
= ((x(1)+x(2)*1j)*z+(x(3)+x(4)*1j))/((x(5)+x(6)*1j)*z+(x(7)+x(8)*1j))
|
|
191
|
+
|
|
192
|
+
If you use this code in your own work, please cite the following paper:
|
|
193
|
+
[1] G. P. T. Choi, Y. Leung-Liu, X. Gu, and L. M. Lui,
|
|
194
|
+
"Parallelizable global conformal parameterization of simply-connected surfaces via partial welding."
|
|
195
|
+
SIAM Journal on Imaging Sciences, 2020.
|
|
196
|
+
|
|
197
|
+
Adopted by Martin Reuter from Matlab code at
|
|
198
|
+
https://github.com/garyptchoi/spherical-conformal-map
|
|
199
|
+
with this
|
|
200
|
+
Copyright (c) 2019-2020, Gary Pui-Tung Choi
|
|
201
|
+
https://scholar.harvard.edu/choi
|
|
202
|
+
and has been distributed with the Apache 2 License
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
# Compute the tria areas with normalization
|
|
206
|
+
area_t = tria.tria_areas()
|
|
207
|
+
area_t = area_t / area_t.sum()
|
|
208
|
+
# Project the sphere onto the plane
|
|
209
|
+
z = stereographic(mapping)
|
|
210
|
+
|
|
211
|
+
def area_map(xx):
|
|
212
|
+
v = inverse_stereographic(
|
|
213
|
+
((xx[0] + xx[1] * 1j) * z + (xx[2] + xx[3] * 1j))
|
|
214
|
+
/ ((xx[4] + xx[5] * 1j) * z + (xx[6] + xx[7] * 1j))
|
|
215
|
+
)
|
|
216
|
+
area_v = TriaMesh(v, tria.t).tria_areas()
|
|
217
|
+
return area_v / area_v.sum()
|
|
218
|
+
|
|
219
|
+
# objective function: mean(abs(log(area_map/area_t)))
|
|
220
|
+
def d_area(xx):
|
|
221
|
+
a = np.abs(np.log(area_map(xx) / area_t))
|
|
222
|
+
return (a[np.isfinite(a)]).mean()
|
|
223
|
+
|
|
224
|
+
# Optimization setup
|
|
225
|
+
x0 = np.array([1, 0, 0, 0, 0, 0, 1, 0]) # initial guess
|
|
226
|
+
# lower and upper bounds
|
|
227
|
+
bnds = (
|
|
228
|
+
(-100, 100),
|
|
229
|
+
(-100, 100),
|
|
230
|
+
(-100, 100),
|
|
231
|
+
(-100, 100),
|
|
232
|
+
(-100, 100),
|
|
233
|
+
(-100, 100),
|
|
234
|
+
(-100, 100),
|
|
235
|
+
(-100, 100),
|
|
236
|
+
)
|
|
237
|
+
# Optimization (may further supply gradients for better result, not yet implemented)
|
|
238
|
+
# options = optimoptions('fmincon','Display','iter');
|
|
239
|
+
# x = fmincon(d_area,x0,[],[],[],[],lb,ub,[],options);
|
|
240
|
+
options = {"disp": True}
|
|
241
|
+
result = minimize(d_area, x0, bounds=bnds, options=options)
|
|
242
|
+
x = result.x
|
|
243
|
+
# obtain the conformal parameterization with area distortion corrected
|
|
244
|
+
fz = ((x[0] + x[1] * 1j) * z + (x[2] + x[3] * 1j)) / (
|
|
245
|
+
(x[4] + x[5] * 1j) * z + (x[6] + x[7] * 1j)
|
|
246
|
+
)
|
|
247
|
+
map_mobius = inverse_stereographic(fz)
|
|
248
|
+
return map_mobius, result
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def beltrami_coefficient(tria, mapping):
|
|
252
|
+
"""
|
|
253
|
+
Compute the Beltrami coefficient of a mapping.
|
|
254
|
+
If you use this code in your own work, please cite the following paper:
|
|
255
|
+
[1] P. T. Choi, K. C. Lam, and L. M. Lui,
|
|
256
|
+
"FLASH: Fast Landmark Aligned Spherical Harmonic Parameterization for Genus-0 Closed Brain Surfaces."
|
|
257
|
+
SIAM Journal on Imaging Sciences, vol. 8, no. 1, pp. 67-94, 2015.
|
|
258
|
+
|
|
259
|
+
Adopted by Martin Reuter from Matlab code at
|
|
260
|
+
https://github.com/garyptchoi/spherical-conformal-map
|
|
261
|
+
with this
|
|
262
|
+
Copyright (c) 2013-2020, Gary Pui-Tung Choi
|
|
263
|
+
https://math.mit.edu/~ptchoi
|
|
264
|
+
and has been distributed with the Apache 2 License
|
|
265
|
+
"""
|
|
266
|
+
# here we should be in the plane
|
|
267
|
+
if np.amax(tria.v[:, 2]) - np.amin(tria.v[:, 2]) > 0.001:
|
|
268
|
+
print("ERROR: mesh should be on complex plane ..")
|
|
269
|
+
raise ValueError("not planar")
|
|
270
|
+
|
|
271
|
+
# get 2d vetrices, edges and area
|
|
272
|
+
v0 = (tria.v[tria.t[:, 0], :])[:, :-1]
|
|
273
|
+
v1 = (tria.v[tria.t[:, 1], :])[:, :-1]
|
|
274
|
+
v2 = (tria.v[tria.t[:, 2], :])[:, :-1]
|
|
275
|
+
e0 = v2 - v1
|
|
276
|
+
e1 = v0 - v2
|
|
277
|
+
e2 = v1 - v0
|
|
278
|
+
# double areas
|
|
279
|
+
areas2 = np.cross(e0, e1) # returns z-component is length
|
|
280
|
+
|
|
281
|
+
# create tria,vertex matrices (summing area normalized edge coords)
|
|
282
|
+
nf = tria.t.shape[0]
|
|
283
|
+
tids = np.arange(nf)
|
|
284
|
+
i = np.column_stack((tids, tids, tids)).reshape(-1)
|
|
285
|
+
j = tria.t.reshape(-1)
|
|
286
|
+
datx = (
|
|
287
|
+
np.column_stack((e0[:, 1], e1[:, 1], e2[:, 1])) / areas2[:, np.newaxis]
|
|
288
|
+
).reshape(-1)
|
|
289
|
+
daty = -(
|
|
290
|
+
np.column_stack((e0[:, 0], e1[:, 0], e2[:, 0])) / areas2[:, np.newaxis]
|
|
291
|
+
).reshape(-1)
|
|
292
|
+
nv = tria.v.shape[0]
|
|
293
|
+
Dx = sparse.csr_matrix((datx, (i, j)), shape=(nf, nv))
|
|
294
|
+
Dy = sparse.csr_matrix((daty, (i, j)), shape=(nf, nv))
|
|
295
|
+
|
|
296
|
+
dXdu = Dx.dot(mapping[:, 0])
|
|
297
|
+
dXdv = Dy.dot(mapping[:, 0])
|
|
298
|
+
dYdu = Dx.dot(mapping[:, 1])
|
|
299
|
+
dYdv = Dy.dot(mapping[:, 1])
|
|
300
|
+
dZdu = Dx.dot(mapping[:, 2])
|
|
301
|
+
dZdv = Dy.dot(mapping[:, 2])
|
|
302
|
+
|
|
303
|
+
E = dXdu**2 + dYdu**2 + dZdu**2
|
|
304
|
+
G = dXdv**2 + dYdv**2 + dZdv**2
|
|
305
|
+
F = dXdu * dXdv + dYdu * dYdv + dZdu * dZdv
|
|
306
|
+
mu = (E - G + 2j * F) / (E + G + 2.0 * np.sqrt(E * G - F**2))
|
|
307
|
+
|
|
308
|
+
return mu
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def linear_beltrami_solver(tria, mu, landmark, target):
|
|
312
|
+
"""
|
|
313
|
+
Linear Beltrami solver
|
|
314
|
+
If you use this code in your own work, please cite the following paper:
|
|
315
|
+
[1] P. T. Choi, K. C. Lam, and L. M. Lui,
|
|
316
|
+
"FLASH: Fast Landmark Aligned Spherical Harmonic Parameterization for Genus-0 Closed Brain Surfaces."
|
|
317
|
+
SIAM Journal on Imaging Sciences, vol. 8, no. 1, pp. 67-94, 2015.
|
|
318
|
+
|
|
319
|
+
Adopted by Martin Reuter from Matlab code at
|
|
320
|
+
https://github.com/garyptchoi/spherical-conformal-map
|
|
321
|
+
with this
|
|
322
|
+
Copyright (c) 2013-2020, Gary Pui-Tung Choi
|
|
323
|
+
https://math.mit.edu/~ptchoi
|
|
324
|
+
and has been distributed with the Apache 2 License
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
# here we should be in the plane
|
|
328
|
+
if np.amax(tria.v[:, 2]) - np.amin(tria.v[:, 2]) > 0.001:
|
|
329
|
+
print("ERROR: mesh should be on complex plane ..")
|
|
330
|
+
raise ValueError("not planar")
|
|
331
|
+
|
|
332
|
+
af = (1.0 - 2 * np.real(mu) + np.abs(mu) ** 2) / (1.0 - np.abs(mu) ** 2)
|
|
333
|
+
bf = -2.0 * np.imag(mu) / (1.0 - np.abs(mu) ** 2)
|
|
334
|
+
gf = (1.0 + 2 * np.real(mu) + np.abs(mu) ** 2) / (1.0 - np.abs(mu) ** 2)
|
|
335
|
+
|
|
336
|
+
# get 2D vertices (drop 3rd dim)
|
|
337
|
+
t0 = tria.t[:, 0]
|
|
338
|
+
t1 = tria.t[:, 1]
|
|
339
|
+
t2 = tria.t[:, 2]
|
|
340
|
+
v0 = (tria.v[t0, :])[:, :-1]
|
|
341
|
+
v1 = (tria.v[t1, :])[:, :-1]
|
|
342
|
+
v2 = (tria.v[t2, :])[:, :-1]
|
|
343
|
+
|
|
344
|
+
uxv0 = v1[:, 1] - v2[:, 1]
|
|
345
|
+
uyv0 = v2[:, 0] - v1[:, 0]
|
|
346
|
+
uxv1 = v2[:, 1] - v0[:, 1]
|
|
347
|
+
uyv1 = v0[:, 0] - v2[:, 0]
|
|
348
|
+
uxv2 = v0[:, 1] - v1[:, 1]
|
|
349
|
+
uyv2 = v1[:, 0] - v0[:, 0]
|
|
350
|
+
|
|
351
|
+
c0 = np.sqrt(uxv0**2 + uyv0**2)
|
|
352
|
+
c1 = np.sqrt(uxv1**2 + uyv1**2)
|
|
353
|
+
c2 = np.sqrt(uxv2**2 + uyv2**2)
|
|
354
|
+
s = 0.5 * (c0 + c1 + c2)
|
|
355
|
+
area2 = 2 * np.sqrt(s * (s - c0) * (s - c1) * (s - c2))
|
|
356
|
+
|
|
357
|
+
v00 = (af * uxv0 * uxv0 + 2 * bf * uxv0 * uyv0 + gf * uyv0 * uyv0) / area2
|
|
358
|
+
v11 = (af * uxv1 * uxv1 + 2 * bf * uxv1 * uyv1 + gf * uyv1 * uyv1) / area2
|
|
359
|
+
v22 = (af * uxv2 * uxv2 + 2 * bf * uxv2 * uyv2 + gf * uyv2 * uyv2) / area2
|
|
360
|
+
v01 = (
|
|
361
|
+
af * uxv1 * uxv0 + bf * uxv1 * uyv0 + bf * uxv0 * uyv1 + gf * uyv1 * uyv0
|
|
362
|
+
) / area2
|
|
363
|
+
v12 = (
|
|
364
|
+
af * uxv2 * uxv1 + bf * uxv2 * uyv1 + bf * uxv1 * uyv2 + gf * uyv2 * uyv1
|
|
365
|
+
) / area2
|
|
366
|
+
v20 = (
|
|
367
|
+
af * uxv0 * uxv2 + bf * uxv0 * uyv2 + bf * uxv2 * uyv0 + gf * uyv0 * uyv2
|
|
368
|
+
) / area2
|
|
369
|
+
|
|
370
|
+
# create symmetric A
|
|
371
|
+
i = np.column_stack((t0, t1, t2, t0, t1, t1, t2, t2, t0)).reshape(-1)
|
|
372
|
+
j = np.column_stack((t0, t1, t2, t1, t0, t2, t1, t0, t2)).reshape(-1)
|
|
373
|
+
dat = np.column_stack((v00, v11, v22, v01, v01, v12, v12, v20, v20)).reshape(-1)
|
|
374
|
+
nv = tria.v.shape[0]
|
|
375
|
+
A = sparse.csc_matrix((dat, (i, j)), shape=(nv, nv), dtype=complex)
|
|
376
|
+
|
|
377
|
+
# convert target to complex and set b vector
|
|
378
|
+
targetc = target[:, 0] + 1j * target[:, 1]
|
|
379
|
+
b = -A[:, landmark] * targetc
|
|
380
|
+
b[landmark] = targetc
|
|
381
|
+
|
|
382
|
+
# set all rows and columns in landmark to zero and put diag 1
|
|
383
|
+
mrow, mcol, mval = sparse.find(A[landmark, :])
|
|
384
|
+
Azero = sparse.csc_matrix((mval, (landmark[mrow], mcol)), shape=(nv, nv))
|
|
385
|
+
A = A - Azero
|
|
386
|
+
mrow, mcol, mval = sparse.find(A[:, landmark])
|
|
387
|
+
Azero = sparse.csc_matrix((mval, (mrow, landmark[mcol])), shape=(nv, nv))
|
|
388
|
+
Aones = sparse.csr_matrix(
|
|
389
|
+
(np.ones(landmark.shape[0]), (landmark, landmark)), shape=(nv, nv)
|
|
390
|
+
)
|
|
391
|
+
A = A - Azero + Aones
|
|
392
|
+
A.eliminate_zeros()
|
|
393
|
+
|
|
394
|
+
x = sparse_symmetric_solve(A, b)
|
|
395
|
+
|
|
396
|
+
mapping = np.squeeze(np.array(x))
|
|
397
|
+
mapping = np.column_stack((np.real(mapping), np.imag(mapping)))
|
|
398
|
+
return mapping
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def sparse_symmetric_solve(A, b, use_cholmod=True):
|
|
402
|
+
sksparse = import_optional_dependency("sksparse", raise_error=use_cholmod)
|
|
403
|
+
if sksparse is not None:
|
|
404
|
+
print("Solver: Cholesky decomposition from scikit-sparse cholmod ...")
|
|
405
|
+
chol = sksparse.cholmod.cholesky(A)
|
|
406
|
+
x = chol(b)
|
|
407
|
+
else:
|
|
408
|
+
from scipy.sparse.linalg import splu
|
|
409
|
+
|
|
410
|
+
print("Solver: spsolve (LU decomposition) ...")
|
|
411
|
+
lu = splu(A)
|
|
412
|
+
x = lu.solve(b)
|
|
413
|
+
return x
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def stereographic(u):
|
|
417
|
+
# Map sphere to complex plane
|
|
418
|
+
# u has three columns (x,y,z)
|
|
419
|
+
# return z as array of complex numbers
|
|
420
|
+
x = u[:, 0]
|
|
421
|
+
y = u[:, 1]
|
|
422
|
+
z = u[:, 2]
|
|
423
|
+
v = np.empty(u.shape[:-1], dtype=complex)
|
|
424
|
+
v.real = (x / (1 - z)).flatten()
|
|
425
|
+
v.imag = (y / (1 - z)).flatten()
|
|
426
|
+
return v
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def inverse_stereographic(u):
|
|
430
|
+
# Computes mapping from complex plane to sphere
|
|
431
|
+
# u can be complex array, or two columns (real,img)
|
|
432
|
+
# returns v as (nv x 3) coordinates on sphere
|
|
433
|
+
if np.iscomplexobj(u):
|
|
434
|
+
x = u.real
|
|
435
|
+
y = u.imag
|
|
436
|
+
else:
|
|
437
|
+
x = u[:, 0]
|
|
438
|
+
y = u[:, 1]
|
|
439
|
+
z = 1 + x**2 + y**2
|
|
440
|
+
v = np.column_stack((2 * x / z, 2 * y / z, (-1 + x**2 + y**2) / z))
|
|
441
|
+
return v
|