cardiac-geometriesx 0.1.2__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 +15 -1
- 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 +82 -32
- cardiac_geometries/gui.py +272 -0
- cardiac_geometries/mesh.py +45 -22
- cardiac_geometries/utils.py +343 -25
- cardiac_geometriesx-0.2.0.dist-info/LICENSE +21 -0
- cardiac_geometriesx-0.2.0.dist-info/METADATA +94 -0
- cardiac_geometriesx-0.2.0.dist-info/RECORD +16 -0
- {cardiac_geometriesx-0.1.2.dist-info → cardiac_geometriesx-0.2.0.dist-info}/WHEEL +1 -1
- cardiac_geometriesx-0.1.2.dist-info/METADATA +0 -51
- cardiac_geometriesx-0.1.2.dist-info/RECORD +0 -14
- {cardiac_geometriesx-0.1.2.dist-info → cardiac_geometriesx-0.2.0.dist-info}/entry_points.txt +0 -0
- {cardiac_geometriesx-0.1.2.dist-info → cardiac_geometriesx-0.2.0.dist-info}/top_level.txt +0 -0
cardiac_geometries/cli.py
CHANGED
|
@@ -8,8 +8,10 @@ from . import mesh
|
|
|
8
8
|
|
|
9
9
|
meta = metadata("cardiac-geometriesx")
|
|
10
10
|
__version__ = meta["Version"]
|
|
11
|
-
__author__ = meta["Author"]
|
|
11
|
+
__author__ = meta["Author-email"]
|
|
12
12
|
__license__ = meta["License"]
|
|
13
|
+
__email__ = meta["Author-email"]
|
|
14
|
+
__program_name__ = meta["Name"]
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
@click.group()
|
|
@@ -867,8 +869,20 @@ def slab_in_bath(
|
|
|
867
869
|
geo.save(outdir / "slab_in_bath.bp")
|
|
868
870
|
|
|
869
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
|
+
|
|
870
883
|
app.add_command(lv_ellipsoid)
|
|
871
884
|
app.add_command(biv_ellipsoid)
|
|
872
885
|
app.add_command(biv_ellipsoid_torso)
|
|
873
886
|
app.add_command(slab)
|
|
874
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
|
@@ -15,9 +15,11 @@ from . import utils
|
|
|
15
15
|
@dataclass # (frozen=True, slots=True)
|
|
16
16
|
class Geometry:
|
|
17
17
|
mesh: dolfinx.mesh.Mesh
|
|
18
|
-
markers: dict[str, tuple[
|
|
19
|
-
ffun: dolfinx.mesh.MeshTags | None = None
|
|
18
|
+
markers: dict[str, tuple[int, int]] = field(default_factory=dict)
|
|
20
19
|
cfun: dolfinx.mesh.MeshTags | None = None
|
|
20
|
+
ffun: dolfinx.mesh.MeshTags | None = None
|
|
21
|
+
efun: dolfinx.mesh.MeshTags | None = None
|
|
22
|
+
vfun: dolfinx.mesh.MeshTags | None = None
|
|
21
23
|
f0: dolfinx.fem.Function | None = None
|
|
22
24
|
s0: dolfinx.fem.Function | None = None
|
|
23
25
|
n0: dolfinx.fem.Function | None = None
|
|
@@ -25,8 +27,7 @@ class Geometry:
|
|
|
25
27
|
def save(self, path: str | Path) -> None:
|
|
26
28
|
path = Path(path)
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
shutil.rmtree(path)
|
|
30
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
30
31
|
self.mesh.comm.barrier()
|
|
31
32
|
adios4dolfinx.write_mesh(mesh=self.mesh, filename=path)
|
|
32
33
|
|
|
@@ -37,15 +38,37 @@ class Geometry:
|
|
|
37
38
|
attributes={k: np.array(v, dtype=np.uint8) for k, v in self.markers.items()},
|
|
38
39
|
)
|
|
39
40
|
|
|
41
|
+
if self.cfun is not None:
|
|
42
|
+
adios4dolfinx.write_meshtags(
|
|
43
|
+
meshtags=self.cfun,
|
|
44
|
+
mesh=self.mesh,
|
|
45
|
+
filename=path,
|
|
46
|
+
meshtag_name="Cell tags",
|
|
47
|
+
)
|
|
40
48
|
if self.ffun is not None:
|
|
41
49
|
adios4dolfinx.write_meshtags(
|
|
42
50
|
meshtags=self.ffun,
|
|
43
51
|
mesh=self.mesh,
|
|
44
52
|
filename=path,
|
|
53
|
+
meshtag_name="Facet tags",
|
|
54
|
+
)
|
|
55
|
+
if self.efun is not None:
|
|
56
|
+
adios4dolfinx.write_meshtags(
|
|
57
|
+
meshtags=self.efun,
|
|
58
|
+
mesh=self.mesh,
|
|
59
|
+
filename=path,
|
|
60
|
+
meshtag_name="Edge tags",
|
|
61
|
+
)
|
|
62
|
+
if self.vfun is not None:
|
|
63
|
+
adios4dolfinx.write_meshtags(
|
|
64
|
+
meshtags=self.vfun,
|
|
65
|
+
mesh=self.mesh,
|
|
66
|
+
filename=path,
|
|
67
|
+
meshtag_name="Vertex tags",
|
|
45
68
|
)
|
|
46
69
|
|
|
47
70
|
if self.f0 is not None:
|
|
48
|
-
el = self.f0.ufl_element()
|
|
71
|
+
el = self.f0.ufl_element()
|
|
49
72
|
arr = utils.element2array(el)
|
|
50
73
|
|
|
51
74
|
adios4dolfinx.write_attributes(
|
|
@@ -60,26 +83,49 @@ class Geometry:
|
|
|
60
83
|
if self.n0 is not None:
|
|
61
84
|
adios4dolfinx.write_function(u=self.n0, filename=path, name="n0")
|
|
62
85
|
|
|
86
|
+
self.mesh.comm.barrier()
|
|
87
|
+
|
|
63
88
|
@classmethod
|
|
64
89
|
def from_file(cls, comm: MPI.Intracomm, path: str | Path) -> "Geometry":
|
|
65
90
|
path = Path(path)
|
|
91
|
+
|
|
66
92
|
mesh = adios4dolfinx.read_mesh(comm=comm, filename=path)
|
|
67
93
|
markers = adios4dolfinx.read_attributes(comm=comm, filename=path, name="markers")
|
|
68
|
-
|
|
94
|
+
tags = {}
|
|
95
|
+
for name, meshtag_name in (
|
|
96
|
+
("cfun", "Cell tags"),
|
|
97
|
+
("ffun", "Facet tags"),
|
|
98
|
+
("efun", "Edge tags"),
|
|
99
|
+
("vfun", "Vertex tags"),
|
|
100
|
+
):
|
|
101
|
+
try:
|
|
102
|
+
tags[name] = adios4dolfinx.read_meshtags(
|
|
103
|
+
mesh=mesh, meshtag_name=meshtag_name, filename=path
|
|
104
|
+
)
|
|
105
|
+
except KeyError:
|
|
106
|
+
tags[name] = None
|
|
107
|
+
|
|
108
|
+
functions = {}
|
|
69
109
|
function_space = adios4dolfinx.read_attributes(
|
|
70
110
|
comm=comm, filename=path, name="function_space"
|
|
71
111
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
for name, el in function_space.items():
|
|
113
|
+
element = utils.array2element(el)
|
|
114
|
+
V = dolfinx.fem.functionspace(mesh, element)
|
|
115
|
+
f = dolfinx.fem.Function(V, name=name)
|
|
116
|
+
try:
|
|
117
|
+
adios4dolfinx.read_function(u=f, filename=path, name=name)
|
|
118
|
+
except KeyError:
|
|
119
|
+
continue
|
|
120
|
+
else:
|
|
121
|
+
functions[name] = f
|
|
122
|
+
|
|
123
|
+
return cls(
|
|
124
|
+
mesh=mesh,
|
|
125
|
+
markers=markers,
|
|
126
|
+
**functions,
|
|
127
|
+
**tags,
|
|
128
|
+
)
|
|
83
129
|
|
|
84
130
|
@classmethod
|
|
85
131
|
def from_folder(cls, comm: MPI.Intracomm, folder: str | Path) -> "Geometry":
|
|
@@ -87,7 +133,7 @@ class Geometry:
|
|
|
87
133
|
|
|
88
134
|
# Read mesh
|
|
89
135
|
if (folder / "mesh.xdmf").exists():
|
|
90
|
-
mesh,
|
|
136
|
+
mesh, tags = utils.read_mesh(comm=comm, filename=folder / "mesh.xdmf")
|
|
91
137
|
else:
|
|
92
138
|
raise ValueError("No mesh file found")
|
|
93
139
|
|
|
@@ -101,22 +147,26 @@ class Geometry:
|
|
|
101
147
|
else:
|
|
102
148
|
markers = {}
|
|
103
149
|
|
|
150
|
+
functions = {}
|
|
104
151
|
microstructure_path = folder / "microstructure.bp"
|
|
105
152
|
if microstructure_path.exists():
|
|
106
153
|
function_space = adios4dolfinx.read_attributes(
|
|
107
154
|
comm=MPI.COMM_WORLD, filename=microstructure_path, name="function_space"
|
|
108
155
|
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
for name, el in function_space.items():
|
|
157
|
+
element = utils.array2element(el)
|
|
158
|
+
V = dolfinx.fem.functionspace(mesh, element)
|
|
159
|
+
f = dolfinx.fem.Function(V, name=name)
|
|
160
|
+
try:
|
|
161
|
+
adios4dolfinx.read_function(u=f, filename=microstructure_path, name=name)
|
|
162
|
+
except KeyError:
|
|
163
|
+
continue
|
|
164
|
+
else:
|
|
165
|
+
functions[name] = f
|
|
166
|
+
|
|
167
|
+
return cls(
|
|
168
|
+
mesh=mesh,
|
|
169
|
+
markers=markers,
|
|
170
|
+
**functions,
|
|
171
|
+
**tags,
|
|
172
|
+
)
|
|
@@ -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
|
+
)
|