cardiac-geometriesx 0.1.3__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of cardiac-geometriesx might be problematic. Click here for more details.
- cardiac_geometries/cli.py +12 -0
- cardiac_geometries/fibers/lv_ellipsoid.py +10 -12
- cardiac_geometries/fibers/slab.py +10 -12
- cardiac_geometries/fibers/utils.py +13 -8
- cardiac_geometries/geometry.py +1 -1
- cardiac_geometries/gui.py +272 -0
- cardiac_geometries/utils.py +82 -14
- {cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/METADATA +8 -1
- cardiac_geometriesx-0.2.0.dist-info/RECORD +16 -0
- cardiac_geometriesx-0.1.3.dist-info/RECORD +0 -15
- {cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/LICENSE +0 -0
- {cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/WHEEL +0 -0
- {cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/entry_points.txt +0 -0
- {cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/top_level.txt +0 -0
cardiac_geometries/cli.py
CHANGED
|
@@ -869,8 +869,20 @@ def slab_in_bath(
|
|
|
869
869
|
geo.save(outdir / "slab_in_bath.bp")
|
|
870
870
|
|
|
871
871
|
|
|
872
|
+
@click.command("gui")
|
|
873
|
+
def gui():
|
|
874
|
+
# Make sure we can import the required packages
|
|
875
|
+
from . import gui # noqa: F401
|
|
876
|
+
|
|
877
|
+
gui_path = Path(__file__).parent.joinpath("gui.py")
|
|
878
|
+
import subprocess as sp
|
|
879
|
+
|
|
880
|
+
sp.run(["streamlit", "run", gui_path.as_posix()])
|
|
881
|
+
|
|
882
|
+
|
|
872
883
|
app.add_command(lv_ellipsoid)
|
|
873
884
|
app.add_command(biv_ellipsoid)
|
|
874
885
|
app.add_command(biv_ellipsoid_torso)
|
|
875
886
|
app.add_command(slab)
|
|
876
887
|
app.add_command(slab_in_bath)
|
|
888
|
+
app.add_command(gui)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
import basix
|
|
4
3
|
import dolfinx
|
|
5
4
|
import numpy as np
|
|
6
5
|
|
|
6
|
+
from ..utils import space_from_string
|
|
7
7
|
from . import utils
|
|
8
8
|
|
|
9
9
|
|
|
@@ -82,14 +82,9 @@ def compute_system(
|
|
|
82
82
|
s0 = np.cross(f0, n0, axis=0)
|
|
83
83
|
s0 = utils.normalize(s0)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
element.family_name,
|
|
87
|
-
mesh.ufl_cell().cellname(),
|
|
88
|
-
degree=element.degree,
|
|
89
|
-
discontinuous=element.discontinuous,
|
|
90
|
-
shape=(mesh.geometry.dim,),
|
|
85
|
+
Vv = space_from_string(
|
|
86
|
+
space_string=f"{element.family_name}_{element.degree}", mesh=mesh, dim=mesh.geometry.dim
|
|
91
87
|
)
|
|
92
|
-
Vv = dolfinx.fem.functionspace(mesh, el)
|
|
93
88
|
|
|
94
89
|
fiber = dolfinx.fem.Function(Vv)
|
|
95
90
|
norm_f = np.linalg.norm(f0, axis=0)
|
|
@@ -131,10 +126,13 @@ def create_microstructure(
|
|
|
131
126
|
)
|
|
132
127
|
|
|
133
128
|
if outdir is not None:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
file
|
|
129
|
+
try:
|
|
130
|
+
with dolfinx.io.VTXWriter(
|
|
131
|
+
mesh.comm, Path(outdir) / "laplace.bp", [t], engine="BP4"
|
|
132
|
+
) as file:
|
|
133
|
+
file.write(0.0)
|
|
134
|
+
except RuntimeError:
|
|
135
|
+
pass
|
|
138
136
|
|
|
139
137
|
system = compute_system(
|
|
140
138
|
t,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
import basix
|
|
4
3
|
import dolfinx
|
|
5
4
|
import numpy as np
|
|
6
5
|
|
|
6
|
+
from ..utils import space_from_string
|
|
7
7
|
from . import utils
|
|
8
8
|
|
|
9
9
|
|
|
@@ -59,14 +59,9 @@ def compute_system(
|
|
|
59
59
|
n0 = np.cross(f0, s0, axis=0)
|
|
60
60
|
n0 = utils.normalize(n0)
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
element.family_name,
|
|
64
|
-
mesh.ufl_cell().cellname(),
|
|
65
|
-
degree=element.degree,
|
|
66
|
-
discontinuous=element.discontinuous,
|
|
67
|
-
shape=(mesh.geometry.dim,),
|
|
62
|
+
Vv = space_from_string(
|
|
63
|
+
space_string=f"{element.family_name}_{element.degree}", mesh=mesh, dim=mesh.geometry.dim
|
|
68
64
|
)
|
|
69
|
-
Vv = dolfinx.fem.functionspace(mesh, el)
|
|
70
65
|
|
|
71
66
|
fiber = dolfinx.fem.Function(Vv)
|
|
72
67
|
norm_f = np.linalg.norm(f0, axis=0)
|
|
@@ -134,10 +129,13 @@ def create_microstructure(
|
|
|
134
129
|
function_space=function_space,
|
|
135
130
|
)
|
|
136
131
|
if outdir is not None:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
file
|
|
132
|
+
try:
|
|
133
|
+
with dolfinx.io.VTXWriter(
|
|
134
|
+
mesh.comm, Path(outdir) / "laplace.bp", [t], engine="BP4"
|
|
135
|
+
) as file:
|
|
136
|
+
file.write(0.0)
|
|
137
|
+
except RuntimeError:
|
|
138
|
+
pass
|
|
141
139
|
|
|
142
140
|
system = compute_system(
|
|
143
141
|
t,
|
|
@@ -7,6 +7,8 @@ import numpy as np
|
|
|
7
7
|
import ufl
|
|
8
8
|
from dolfinx.fem.petsc import LinearProblem
|
|
9
9
|
|
|
10
|
+
from ..utils import space_from_string
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class Microstructure(NamedTuple):
|
|
12
14
|
f0: dolfinx.fem.Function
|
|
@@ -20,17 +22,20 @@ def save_microstructure(
|
|
|
20
22
|
from ..utils import element2array
|
|
21
23
|
|
|
22
24
|
# Save for paraview visualization
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
file
|
|
25
|
+
try:
|
|
26
|
+
with dolfinx.io.VTXWriter(
|
|
27
|
+
mesh.comm, Path(outdir) / "microstructure-viz.bp", functions, engine="BP4"
|
|
28
|
+
) as file:
|
|
29
|
+
file.write(0.0)
|
|
30
|
+
except RuntimeError:
|
|
31
|
+
pass
|
|
27
32
|
|
|
28
33
|
# Save with proper function space
|
|
29
34
|
filename = Path(outdir) / "microstructure.bp"
|
|
30
35
|
for function in functions:
|
|
31
36
|
adios4dolfinx.write_function(u=function, filename=filename)
|
|
32
37
|
|
|
33
|
-
attributes = {f.name: element2array(f.ufl_element()
|
|
38
|
+
attributes = {f.name: element2array(f.ufl_element()) for f in functions}
|
|
34
39
|
adios4dolfinx.write_attributes(
|
|
35
40
|
comm=mesh.comm,
|
|
36
41
|
filename=filename,
|
|
@@ -73,10 +78,10 @@ def laplace(
|
|
|
73
78
|
uh = problem.solve()
|
|
74
79
|
|
|
75
80
|
if function_space != "P_1":
|
|
76
|
-
|
|
77
|
-
W = dolfinx.fem.functionspace(mesh, (family, int(degree)))
|
|
81
|
+
W = space_from_string(function_space, mesh, dim=1)
|
|
78
82
|
t = dolfinx.fem.Function(W)
|
|
79
|
-
|
|
83
|
+
expr = dolfinx.fem.Expression(uh, W.element.interpolation_points())
|
|
84
|
+
t.interpolate(expr)
|
|
80
85
|
else:
|
|
81
86
|
t = uh
|
|
82
87
|
|
cardiac_geometries/geometry.py
CHANGED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
try:
|
|
2
|
+
import streamlit as st
|
|
3
|
+
except ImportError:
|
|
4
|
+
print("Please install streamlit - python3 -m pip install streamlit")
|
|
5
|
+
exit(1)
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import pyvista as pv
|
|
9
|
+
except ImportError:
|
|
10
|
+
msg = (
|
|
11
|
+
"Please install pyvista - python3 -m pip install pyvista. "
|
|
12
|
+
"Note if you using ARM Mac, then check out the following link "
|
|
13
|
+
"on how to install vtk: https://github.com/KitwareMedical/VTKPythonPackage/issues/42"
|
|
14
|
+
)
|
|
15
|
+
print(msg)
|
|
16
|
+
exit(1)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from stpyvista import stpyvista
|
|
20
|
+
except ImportError:
|
|
21
|
+
print("Please install stpyvista - python3 -m pip install stpyvista")
|
|
22
|
+
exit(1)
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
|
|
26
|
+
os.environ["GMSH_INTERRUPTIBLE"] = "0"
|
|
27
|
+
|
|
28
|
+
import math
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
import mpi4py
|
|
32
|
+
|
|
33
|
+
import dolfinx
|
|
34
|
+
|
|
35
|
+
import cardiac_geometries
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def return_none(*args, **kwargs):
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_geometry(folder: str):
|
|
43
|
+
comm = mpi4py.MPI.COMM_WORLD
|
|
44
|
+
try:
|
|
45
|
+
return cardiac_geometries.geometry.Geometry.from_folder(comm, folder)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
st.error(f"Error loading geometry: {e}")
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def plot_geometry(geo):
|
|
52
|
+
V = dolfinx.fem.functionspace(geo.mesh, ("Lagrange", 1))
|
|
53
|
+
|
|
54
|
+
pv.start_xvfb()
|
|
55
|
+
|
|
56
|
+
# Plot the mesh with cell tags
|
|
57
|
+
mesh_plotter = pv.Plotter()
|
|
58
|
+
mesh_plotter.background_color = "white"
|
|
59
|
+
mesh_plotter.window_size = [600, 400]
|
|
60
|
+
|
|
61
|
+
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(V)
|
|
62
|
+
|
|
63
|
+
grid = pv.UnstructuredGrid(topology, cell_types, geometry)
|
|
64
|
+
if geo.cfun is not None:
|
|
65
|
+
grid.cell_data["Cell tags"] = geo.cfun.values
|
|
66
|
+
grid.set_active_scalars("Cell tags")
|
|
67
|
+
mesh_plotter.add_mesh(grid, show_edges=True)
|
|
68
|
+
|
|
69
|
+
mesh_plotter.view_isometric()
|
|
70
|
+
st.header("Mesh and cell tags")
|
|
71
|
+
stpyvista(mesh_plotter)
|
|
72
|
+
|
|
73
|
+
if geo.ffun is not None:
|
|
74
|
+
vtk_bmesh = dolfinx.plot.vtk_mesh(geo.mesh, geo.ffun.dim, geo.ffun.indices)
|
|
75
|
+
bgrid = pv.UnstructuredGrid(*vtk_bmesh)
|
|
76
|
+
bgrid.cell_data["Facet tags"] = geo.ffun.values
|
|
77
|
+
bgrid.set_active_scalars("Facet tags")
|
|
78
|
+
facet_plotter = pv.Plotter()
|
|
79
|
+
facet_plotter.background_color = "white"
|
|
80
|
+
facet_plotter.window_size = [600, 400]
|
|
81
|
+
facet_plotter.add_mesh(bgrid, show_edges=True)
|
|
82
|
+
facet_plotter.view_isometric()
|
|
83
|
+
st.header("Facet tags")
|
|
84
|
+
stpyvista(facet_plotter)
|
|
85
|
+
|
|
86
|
+
if geo.f0 is not None:
|
|
87
|
+
st.header("Fibers")
|
|
88
|
+
size_arrows_fibers = st.slider(
|
|
89
|
+
"Arrow size fibers", min_value=0.1, max_value=10.0, value=2.0
|
|
90
|
+
)
|
|
91
|
+
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(geo.f0.function_space)
|
|
92
|
+
values = geo.f0.x.array.real.reshape((geometry.shape[0], len(geo.f0)))
|
|
93
|
+
function_grid = pv.UnstructuredGrid(topology, cell_types, geometry)
|
|
94
|
+
function_grid["u"] = values
|
|
95
|
+
glyphs = function_grid.glyph(orient="u", factor=size_arrows_fibers)
|
|
96
|
+
fiber_plotter = pv.Plotter()
|
|
97
|
+
fiber_plotter.background_color = "white"
|
|
98
|
+
fiber_plotter.window_size = [600, 400]
|
|
99
|
+
fiber_plotter.add_mesh(glyphs, show_edges=True)
|
|
100
|
+
fiber_plotter.view_isometric()
|
|
101
|
+
stpyvista(fiber_plotter)
|
|
102
|
+
|
|
103
|
+
if geo.s0 is not None:
|
|
104
|
+
st.header("Sheets")
|
|
105
|
+
size_arrows_sheets = st.slider(
|
|
106
|
+
"Arrow size sheets", min_value=0.1, max_value=10.0, value=2.0
|
|
107
|
+
)
|
|
108
|
+
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(geo.s0.function_space)
|
|
109
|
+
values = geo.s0.x.array.real.reshape((geometry.shape[0], len(geo.s0)))
|
|
110
|
+
function_grid = pv.UnstructuredGrid(topology, cell_types, geometry)
|
|
111
|
+
function_grid["u"] = values
|
|
112
|
+
glyphs = function_grid.glyph(orient="u", factor=size_arrows_sheets)
|
|
113
|
+
sheet_plotter = pv.Plotter()
|
|
114
|
+
sheet_plotter.background_color = "white"
|
|
115
|
+
sheet_plotter.window_size = [600, 400]
|
|
116
|
+
sheet_plotter.add_mesh(glyphs, show_edges=True)
|
|
117
|
+
sheet_plotter.view_isometric()
|
|
118
|
+
stpyvista(sheet_plotter)
|
|
119
|
+
|
|
120
|
+
if geo.n0 is not None:
|
|
121
|
+
st.header("Sheet Normal")
|
|
122
|
+
size_arrows_normal = st.slider(
|
|
123
|
+
"Arrow size sheet normal", min_value=0.1, max_value=10.0, value=2.0
|
|
124
|
+
)
|
|
125
|
+
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(geo.n0.function_space)
|
|
126
|
+
values = geo.n0.x.array.real.reshape((geometry.shape[0], len(geo.n0)))
|
|
127
|
+
function_grid = pv.UnstructuredGrid(topology, cell_types, geometry)
|
|
128
|
+
function_grid["u"] = values
|
|
129
|
+
glyphs = function_grid.glyph(orient="u", factor=size_arrows_normal)
|
|
130
|
+
normal_plotter = pv.Plotter()
|
|
131
|
+
normal_plotter.background_color = "white"
|
|
132
|
+
normal_plotter.window_size = [600, 400]
|
|
133
|
+
normal_plotter.add_mesh(glyphs, show_edges=True)
|
|
134
|
+
normal_plotter.view_isometric()
|
|
135
|
+
stpyvista(normal_plotter)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def load():
|
|
139
|
+
st.title("Load existing geometries")
|
|
140
|
+
|
|
141
|
+
cwd = Path.cwd()
|
|
142
|
+
folders = [f.name for f in cwd.iterdir() if f.is_dir()]
|
|
143
|
+
# Select a folder
|
|
144
|
+
|
|
145
|
+
folder = st.selectbox("Select a folder", folders)
|
|
146
|
+
geo = load_geometry(folder)
|
|
147
|
+
|
|
148
|
+
if geo is not None:
|
|
149
|
+
plot_geometry(geo)
|
|
150
|
+
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_lv():
|
|
155
|
+
st.title("Create lv geometry")
|
|
156
|
+
|
|
157
|
+
outdir = st.text_input("Output directory", value="lv_ellipsoid")
|
|
158
|
+
col1, col2, col3 = st.columns(3)
|
|
159
|
+
|
|
160
|
+
with col1:
|
|
161
|
+
st.header("Radius")
|
|
162
|
+
r_short_endo = st.number_input("r_short_endo", value=7.0)
|
|
163
|
+
r_short_epi = st.number_input("r_short_epi", value=10.0)
|
|
164
|
+
r_long_endo = st.number_input("r_long_endo", value=17.0)
|
|
165
|
+
r_long_epi = st.number_input("r_long_epi", value=20.0)
|
|
166
|
+
p_size_ref = st.number_input("p_size_ref", value=3.0)
|
|
167
|
+
|
|
168
|
+
with col2:
|
|
169
|
+
st.header("Angles")
|
|
170
|
+
mu_apex_endo = st.number_input("mu_apex_endo", value=-math.pi)
|
|
171
|
+
mu_base_endo = st.number_input("mu_base_endo", value=-math.acos(5 / 17))
|
|
172
|
+
mu_apex_epi = st.number_input("mu_apex_epi", value=-math.pi)
|
|
173
|
+
mu_base_epi = st.number_input("mu_base_epi", value=-math.acos(5 / 20))
|
|
174
|
+
|
|
175
|
+
with col3:
|
|
176
|
+
st.header("Fibers")
|
|
177
|
+
create_fibers = st.checkbox("Create fibers", value=True)
|
|
178
|
+
if create_fibers:
|
|
179
|
+
fiber_space = st.selectbox("Fiber space", ["P_1", "P_2"])
|
|
180
|
+
fiber_angle_endo = st.number_input("fiber_angle_endo", value=60)
|
|
181
|
+
fiber_angle_epi = st.number_input("fiber_angle_epi", value=-60)
|
|
182
|
+
|
|
183
|
+
if st.button("Create"):
|
|
184
|
+
args = [
|
|
185
|
+
"geox",
|
|
186
|
+
"lv-ellipsoid",
|
|
187
|
+
"--r-short-endo",
|
|
188
|
+
str(r_short_endo),
|
|
189
|
+
"--r-short-epi",
|
|
190
|
+
str(r_short_epi),
|
|
191
|
+
"--r-long-endo",
|
|
192
|
+
str(r_long_endo),
|
|
193
|
+
"--r-long-epi",
|
|
194
|
+
str(r_long_epi),
|
|
195
|
+
"--mu-apex-endo",
|
|
196
|
+
str(mu_apex_endo),
|
|
197
|
+
"--mu-base-endo",
|
|
198
|
+
str(mu_base_endo),
|
|
199
|
+
"--mu-apex-epi",
|
|
200
|
+
str(mu_apex_epi),
|
|
201
|
+
"--mu-base-epi",
|
|
202
|
+
str(mu_base_epi),
|
|
203
|
+
"--psize-ref",
|
|
204
|
+
str(p_size_ref),
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
if create_fibers:
|
|
208
|
+
args.extend(
|
|
209
|
+
[
|
|
210
|
+
"--create-fibers",
|
|
211
|
+
"--fiber-space",
|
|
212
|
+
fiber_space,
|
|
213
|
+
"--fiber-angle-endo",
|
|
214
|
+
str(fiber_angle_endo),
|
|
215
|
+
"--fiber-angle-epi",
|
|
216
|
+
str(fiber_angle_epi),
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
args.append(str(outdir))
|
|
221
|
+
st.markdown(f"```{' '.join(args)}```")
|
|
222
|
+
|
|
223
|
+
if Path(outdir).exists():
|
|
224
|
+
st.warning(f"Folder {outdir} already exists. Overwriting...")
|
|
225
|
+
import shutil
|
|
226
|
+
|
|
227
|
+
shutil.rmtree(outdir)
|
|
228
|
+
|
|
229
|
+
import subprocess as sp
|
|
230
|
+
|
|
231
|
+
ret = sp.run(
|
|
232
|
+
args,
|
|
233
|
+
capture_output=True,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
st.markdown(f"```{ret.stdout.decode()}```")
|
|
237
|
+
|
|
238
|
+
if st.button("Visualize folder"):
|
|
239
|
+
geo = load_geometry(outdir)
|
|
240
|
+
|
|
241
|
+
if geo is not None:
|
|
242
|
+
plot_geometry(geo)
|
|
243
|
+
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# Page settings
|
|
248
|
+
st.set_page_config(page_title="simcardems")
|
|
249
|
+
|
|
250
|
+
# Sidebar settings
|
|
251
|
+
pages = {
|
|
252
|
+
"Load": load,
|
|
253
|
+
"Create LV geometry": create_lv,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
st.sidebar.title("Cardiac geometries")
|
|
257
|
+
|
|
258
|
+
# Radio buttons to select desired option
|
|
259
|
+
page = st.sidebar.radio("Pages", tuple(pages.keys()))
|
|
260
|
+
|
|
261
|
+
pages[page]()
|
|
262
|
+
|
|
263
|
+
# About
|
|
264
|
+
st.sidebar.markdown(
|
|
265
|
+
"""
|
|
266
|
+
- [Source code](https://github.com/ComputationalPhysiology/cardiac-geometriesx)
|
|
267
|
+
- [Documentation](http://computationalphysiology.github.io/cardiac-geometriesx)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
Copyright © 2024 Henrik Finsberg @ Simula Research Laboratory
|
|
271
|
+
""",
|
|
272
|
+
)
|
cardiac_geometries/utils.py
CHANGED
|
@@ -13,6 +13,9 @@ from structlog import get_logger
|
|
|
13
13
|
|
|
14
14
|
logger = get_logger()
|
|
15
15
|
|
|
16
|
+
quads = ("Quadrature", "Q", "Quad", "quadrature", "q", "quad")
|
|
17
|
+
QUADNR = 42
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class GMshModel(NamedTuple):
|
|
18
21
|
mesh: dolfinx.mesh.Mesh
|
|
@@ -246,11 +249,70 @@ def model_to_mesh(
|
|
|
246
249
|
return GMshModel(mesh, ct, ft, et, vt)
|
|
247
250
|
|
|
248
251
|
|
|
249
|
-
def
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
def parse_element(space_string: str, mesh: dolfinx.mesh.Mesh, dim: int) -> basix.ufl._ElementBase:
|
|
253
|
+
"""
|
|
254
|
+
Parse a string representation of a basix element family
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
family_str, degree_str = space_string.split("_")
|
|
258
|
+
kwargs = {"degree": int(degree_str), "cell": mesh.ufl_cell().cellname()}
|
|
259
|
+
if dim > 1:
|
|
260
|
+
if family_str in quads:
|
|
261
|
+
kwargs["value_shape"] = (dim,)
|
|
262
|
+
else:
|
|
263
|
+
kwargs["shape"] = (dim,)
|
|
264
|
+
|
|
265
|
+
if family_str in ["Lagrange", "P", "CG"]:
|
|
266
|
+
el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=False, **kwargs)
|
|
267
|
+
elif family_str in ["Discontinuous Lagrange", "DG", "dP"]:
|
|
268
|
+
el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=True, **kwargs)
|
|
269
|
+
|
|
270
|
+
elif family_str in quads:
|
|
271
|
+
el = basix.ufl.quadrature_element(scheme="default", **kwargs)
|
|
272
|
+
else:
|
|
273
|
+
families = list(basix.ElementFamily.__members__.keys())
|
|
274
|
+
msg = f"Unknown element family: {family_str!r}, available families: {families}"
|
|
275
|
+
raise ValueError(msg)
|
|
276
|
+
return el
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def space_from_string(
|
|
280
|
+
space_string: str, mesh: dolfinx.mesh.Mesh, dim: int
|
|
281
|
+
) -> dolfinx.fem.functionspace:
|
|
282
|
+
"""
|
|
283
|
+
Constructed a finite elements space from a string
|
|
284
|
+
representation of the space
|
|
285
|
+
|
|
286
|
+
Arguments
|
|
287
|
+
---------
|
|
288
|
+
space_string : str
|
|
289
|
+
A string on the form {family}_{degree} which
|
|
290
|
+
determines the space. Example 'Lagrange_1'.
|
|
291
|
+
mesh : df.Mesh
|
|
292
|
+
The mesh
|
|
293
|
+
dim : int
|
|
294
|
+
1 for scalar space, 3 for vector space.
|
|
295
|
+
"""
|
|
296
|
+
el = parse_element(space_string, mesh, dim)
|
|
297
|
+
return dolfinx.fem.functionspace(mesh, el)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def element2array(el: basix.ufl._BlockedElement) -> np.ndarray:
|
|
301
|
+
if el.family_name in quads:
|
|
302
|
+
return np.array(
|
|
303
|
+
[QUADNR, int(el.cell_type), int(el.degree), 0],
|
|
304
|
+
dtype=np.uint8,
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
return np.array(
|
|
308
|
+
[
|
|
309
|
+
int(el.basix_element.family),
|
|
310
|
+
int(el.cell_type),
|
|
311
|
+
int(el.degree),
|
|
312
|
+
int(el.basix_element.discontinuous),
|
|
313
|
+
],
|
|
314
|
+
dtype=np.uint8,
|
|
315
|
+
)
|
|
254
316
|
|
|
255
317
|
|
|
256
318
|
def number2Enum(num: int, enum: Iterable) -> Enum:
|
|
@@ -261,18 +323,24 @@ def number2Enum(num: int, enum: Iterable) -> Enum:
|
|
|
261
323
|
|
|
262
324
|
|
|
263
325
|
def array2element(arr: np.ndarray) -> basix.finite_element.FiniteElement:
|
|
264
|
-
family = number2Enum(arr[0], basix.ElementFamily)
|
|
265
326
|
cell_type = number2Enum(arr[1], basix.CellType)
|
|
266
327
|
degree = int(arr[2])
|
|
267
328
|
discontinuous = bool(arr[3])
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
329
|
+
if arr[0] == QUADNR:
|
|
330
|
+
return basix.ufl.quadrature_element(
|
|
331
|
+
scheme="default", degree=degree, value_shape=(3,), cell=cell_type
|
|
332
|
+
)
|
|
333
|
+
else:
|
|
334
|
+
family = number2Enum(arr[0], basix.ElementFamily)
|
|
335
|
+
|
|
336
|
+
# TODO: Shape is hardcoded to (3,) for now, but this should also be stored
|
|
337
|
+
return basix.ufl.element(
|
|
338
|
+
family=family,
|
|
339
|
+
cell=cell_type,
|
|
340
|
+
degree=degree,
|
|
341
|
+
discontinuous=discontinuous,
|
|
342
|
+
shape=(3,),
|
|
343
|
+
)
|
|
276
344
|
|
|
277
345
|
|
|
278
346
|
def handle_mesh_name(mesh_name: str = "") -> Path:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cardiac-geometriesx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A python library for cardiac geometries
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -32,6 +32,13 @@ Requires-Dist: pyvista[all] >=0.43.0 ; extra == 'docs'
|
|
|
32
32
|
Requires-Dist: trame-vuetify ; extra == 'docs'
|
|
33
33
|
Requires-Dist: ipywidgets ; extra == 'docs'
|
|
34
34
|
Requires-Dist: fenicsx-ldrb ; extra == 'docs'
|
|
35
|
+
Provides-Extra: gui
|
|
36
|
+
Requires-Dist: streamlit ; extra == 'gui'
|
|
37
|
+
Requires-Dist: stpyvista ; extra == 'gui'
|
|
38
|
+
Requires-Dist: pyvista[all] >=0.43.0 ; extra == 'gui'
|
|
39
|
+
Requires-Dist: trame-vuetify ; extra == 'gui'
|
|
40
|
+
Requires-Dist: ipywidgets ; extra == 'gui'
|
|
41
|
+
Requires-Dist: fenicsx-ldrb ; extra == 'gui'
|
|
35
42
|
Provides-Extra: test
|
|
36
43
|
Requires-Dist: pre-commit ; extra == 'test'
|
|
37
44
|
Requires-Dist: pytest ; extra == 'test'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
cardiac_geometries/__init__.py,sha256=2W_ywAeLjyRk5MqSPAodHa4UN2lOnW1h8tmGLQ3gaJ0,150
|
|
2
|
+
cardiac_geometries/cli.py,sha256=GKcsMNhVFoxWDr-o3jiDCXmTvPgCSEB9Kws8AvYIK4Q,19678
|
|
3
|
+
cardiac_geometries/geometry.py,sha256=iK8lydZEqU3sz0983yucW9IzQ2VqSXSy4D5ubdl9bOs,5636
|
|
4
|
+
cardiac_geometries/gui.py,sha256=9WYR850wLrqsUrVUC37E2SaO0OWA_oagSe-YNrsxz3k,8376
|
|
5
|
+
cardiac_geometries/mesh.py,sha256=JSm69BTS7WRUldwaQ9-Gnjq_KbX66FTnl2ouFKRtFhA,26825
|
|
6
|
+
cardiac_geometries/utils.py,sha256=EwEXAuB4XiJaqlZaGZ1cc6sIBSPJLCx-43As1NUcJrA,16488
|
|
7
|
+
cardiac_geometries/fibers/__init__.py,sha256=WpRrn9Iakl-3m8IGtFkqP0LXGjw5EZHZ8Eg9JCnCdrg,137
|
|
8
|
+
cardiac_geometries/fibers/lv_ellipsoid.py,sha256=jFloAhrFRkKrhsWVKcOiZrGioPLBN3_yiIXiyr1mHCQ,3665
|
|
9
|
+
cardiac_geometries/fibers/slab.py,sha256=5tMvOSqXQ4_nbdUUtho_tQjDrmICxZfN7SXNq4FKdlY,3836
|
|
10
|
+
cardiac_geometries/fibers/utils.py,sha256=j1ERqXcdaWCwO2yjbYGlgTt2G1DxZCVzbNHuDhh_QrM,2541
|
|
11
|
+
cardiac_geometriesx-0.2.0.dist-info/LICENSE,sha256=lo5K2rJPZOSv6luutGHbzzi3IpXNaB9E2UWq60qvNx0,1111
|
|
12
|
+
cardiac_geometriesx-0.2.0.dist-info/METADATA,sha256=VHm0LNhB1GsjrcY8fPTJH0C6UzvV01FW07kIxgmVAwc,4163
|
|
13
|
+
cardiac_geometriesx-0.2.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
14
|
+
cardiac_geometriesx-0.2.0.dist-info/entry_points.txt,sha256=xOBnlc6W-H9oCDYLNz3kpki26OmpfYSoFSrmi_4V-Ec,52
|
|
15
|
+
cardiac_geometriesx-0.2.0.dist-info/top_level.txt,sha256=J0gQxkWR2my5Vf7Qt8buDY8ZOjYdVfIweVunCGXWKNE,19
|
|
16
|
+
cardiac_geometriesx-0.2.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
cardiac_geometries/__init__.py,sha256=2W_ywAeLjyRk5MqSPAodHa4UN2lOnW1h8tmGLQ3gaJ0,150
|
|
2
|
-
cardiac_geometries/cli.py,sha256=hOv1muw33MY1GedXnIR1xvh488lWIzRDq29LajK6PY0,19394
|
|
3
|
-
cardiac_geometries/geometry.py,sha256=UWOasa3l7BM1tdr3au5Bu0hPnOe1TyoP8KMMrwtJjyM,5650
|
|
4
|
-
cardiac_geometries/mesh.py,sha256=JSm69BTS7WRUldwaQ9-Gnjq_KbX66FTnl2ouFKRtFhA,26825
|
|
5
|
-
cardiac_geometries/utils.py,sha256=vO9Ov385JBfODH-3exVVgK9xKvAoNLThi_QyiM2w7iE,14312
|
|
6
|
-
cardiac_geometries/fibers/__init__.py,sha256=WpRrn9Iakl-3m8IGtFkqP0LXGjw5EZHZ8Eg9JCnCdrg,137
|
|
7
|
-
cardiac_geometries/fibers/lv_ellipsoid.py,sha256=jdRek3v_V2a9IQHqH7P-lK0JBwa1dUNpBBmABzKGje4,3690
|
|
8
|
-
cardiac_geometries/fibers/slab.py,sha256=h_iVs_e1F9LJaXJOzam336f0LcTAXXBvApsFZGZSnZs,3861
|
|
9
|
-
cardiac_geometries/fibers/utils.py,sha256=hMTAKy32ow10tpNE1-WuvCYk7PVXO1qL0dHPa3LjecQ,2434
|
|
10
|
-
cardiac_geometriesx-0.1.3.dist-info/LICENSE,sha256=lo5K2rJPZOSv6luutGHbzzi3IpXNaB9E2UWq60qvNx0,1111
|
|
11
|
-
cardiac_geometriesx-0.1.3.dist-info/METADATA,sha256=M9ntsIWoEz5VILHZa2nWKMDjvdM25_7QsV9kvvFDFWI,3871
|
|
12
|
-
cardiac_geometriesx-0.1.3.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
13
|
-
cardiac_geometriesx-0.1.3.dist-info/entry_points.txt,sha256=xOBnlc6W-H9oCDYLNz3kpki26OmpfYSoFSrmi_4V-Ec,52
|
|
14
|
-
cardiac_geometriesx-0.1.3.dist-info/top_level.txt,sha256=J0gQxkWR2my5Vf7Qt8buDY8ZOjYdVfIweVunCGXWKNE,19
|
|
15
|
-
cardiac_geometriesx-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{cardiac_geometriesx-0.1.3.dist-info → cardiac_geometriesx-0.2.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|