cardiac-geometriesx 0.5.1__tar.gz → 0.5.2__tar.gz
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_geometriesx-0.5.1/src/cardiac_geometriesx.egg-info → cardiac_geometriesx-0.5.2}/PKG-INFO +6 -2
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/README.md +5 -1
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/pyproject.toml +2 -2
- cardiac_geometriesx-0.5.2/src/cardiac_geometries/fibers/__init__.py +66 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/cylinder.py +1 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/lv_ellipsoid.py +1 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/slab.py +1 -1
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/geometry.py +183 -1
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/mesh.py +203 -78
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/utils.py +1 -1
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2/src/cardiac_geometriesx.egg-info}/PKG-INFO +6 -2
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/SOURCES.txt +1 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/tests/test_cli.py +5 -1
- cardiac_geometriesx-0.5.2/tests/test_refinement.py +96 -0
- cardiac_geometriesx-0.5.1/src/cardiac_geometries/fibers/__init__.py +0 -4
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/LICENSE +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/setup.cfg +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/__init__.py +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/cli.py +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/utils.py +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/gui.py +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/dependency_links.txt +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/entry_points.txt +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/not-zip-safe +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/requires.txt +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometriesx.egg-info/top_level.txt +0 -0
- {cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/tests/test_save_load.py +0 -0
{cardiac_geometriesx-0.5.1/src/cardiac_geometriesx.egg-info → cardiac_geometriesx-0.5.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardiac-geometriesx
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A python library for cardiac geometries
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -77,7 +77,11 @@ To install the package you can use `pip`
|
|
|
77
77
|
```
|
|
78
78
|
python3 -m pip install cardiac-geometriesx
|
|
79
79
|
```
|
|
80
|
-
however, this assumes that you already have `dolfinx` pre-installed. You can also use
|
|
80
|
+
however, this assumes that you already have `dolfinx` pre-installed. You can also use `conda`
|
|
81
|
+
```
|
|
82
|
+
conda install -c conda-forge cardiac-geometriesx
|
|
83
|
+
```
|
|
84
|
+
or the provided docker image
|
|
81
85
|
```
|
|
82
86
|
docker pull ghcr.io/computationalphysiology/cardiac-geometriesx:latest
|
|
83
87
|
```
|
|
@@ -26,7 +26,11 @@ To install the package you can use `pip`
|
|
|
26
26
|
```
|
|
27
27
|
python3 -m pip install cardiac-geometriesx
|
|
28
28
|
```
|
|
29
|
-
however, this assumes that you already have `dolfinx` pre-installed. You can also use
|
|
29
|
+
however, this assumes that you already have `dolfinx` pre-installed. You can also use `conda`
|
|
30
|
+
```
|
|
31
|
+
conda install -c conda-forge cardiac-geometriesx
|
|
32
|
+
```
|
|
33
|
+
or the provided docker image
|
|
30
34
|
```
|
|
31
35
|
docker pull ghcr.io/computationalphysiology/cardiac-geometriesx:latest
|
|
32
36
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cardiac-geometriesx"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "A python library for cardiac geometries"
|
|
9
9
|
authors = [{name = "Henrik Finsberg", email = "henriknf@simula.no"}]
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -177,7 +177,7 @@ tag = true
|
|
|
177
177
|
sign_tags = false
|
|
178
178
|
tag_name = "v{new_version}"
|
|
179
179
|
tag_message = "Bump version: {current_version} → {new_version}"
|
|
180
|
-
current_version = "0.5.
|
|
180
|
+
current_version = "0.5.2"
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
[[tool.bumpversion.files]]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import dolfinx
|
|
4
|
+
|
|
5
|
+
from . import cylinder, lv_ellipsoid, slab, utils
|
|
6
|
+
from .utils import Microstructure
|
|
7
|
+
|
|
8
|
+
__all__ = ["lv_ellipsoid", "slab", "cylinder", "utils", "Microstructure"]
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
supported_mesh_types = ("slab", "cylinder", "lv_ellipsoid", "biv_ellipsoid", "ukb", "lv", "biv")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def generate_fibers(mesh_type: str, mesh: dolfinx.mesh.Mesh, **kwargs) -> Microstructure:
|
|
17
|
+
"""Generate fibers based on the mesh type."""
|
|
18
|
+
|
|
19
|
+
kwargs = kwargs.copy()
|
|
20
|
+
|
|
21
|
+
# Map fiber_angle_endo and fiber_angle_epi to alpha_endo and alpha_epi
|
|
22
|
+
if "fiber_angle_endo" in kwargs:
|
|
23
|
+
kwargs["alpha_endo"] = kwargs.pop("fiber_angle_endo")
|
|
24
|
+
if "fiber_angle_epi" in kwargs:
|
|
25
|
+
kwargs["alpha_epi"] = kwargs.pop("fiber_angle_epi")
|
|
26
|
+
if "fiber_space" in kwargs:
|
|
27
|
+
kwargs["function_space"] = kwargs.pop("fiber_space")
|
|
28
|
+
|
|
29
|
+
if mesh_type == "slab":
|
|
30
|
+
return slab.create_microstructure(mesh, **kwargs)
|
|
31
|
+
elif mesh_type == "cylinder":
|
|
32
|
+
return cylinder.create_microstructure(mesh, **kwargs)
|
|
33
|
+
elif mesh_type == "lv_ellipsoid":
|
|
34
|
+
return lv_ellipsoid.create_microstructure(mesh, **kwargs)
|
|
35
|
+
else:
|
|
36
|
+
if mesh_type not in supported_mesh_types:
|
|
37
|
+
logger.warning(
|
|
38
|
+
f"Mesh type {mesh_type!r} is not recognized. "
|
|
39
|
+
f"Supported mesh types are: {supported_mesh_types!r}. "
|
|
40
|
+
"Lets try with LDRB algorithm to generate fibers.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Try with LDRB
|
|
44
|
+
try:
|
|
45
|
+
import ldrb
|
|
46
|
+
except ImportError as ex:
|
|
47
|
+
msg = (
|
|
48
|
+
"To create fibers you need to install the ldrb package "
|
|
49
|
+
"which you can install with pip install fenicsx-ldrb"
|
|
50
|
+
)
|
|
51
|
+
raise ImportError(msg) from ex
|
|
52
|
+
|
|
53
|
+
markers = kwargs.pop("markers", None)
|
|
54
|
+
clipped = kwargs.pop("clipped", False)
|
|
55
|
+
|
|
56
|
+
if mesh_type == "ukb" and markers is not None:
|
|
57
|
+
from ..mesh import transform_markers
|
|
58
|
+
|
|
59
|
+
markers = transform_markers(markers, clipped=clipped)
|
|
60
|
+
|
|
61
|
+
system = ldrb.dolfinx_ldrb(mesh=mesh, markers=markers, **kwargs)
|
|
62
|
+
return Microstructure(
|
|
63
|
+
f0=system.f0,
|
|
64
|
+
s0=system.s0,
|
|
65
|
+
n0=system.n0,
|
|
66
|
+
)
|
{cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/slab.py
RENAMED
|
@@ -11,7 +11,6 @@ def compute_system(
|
|
|
11
11
|
t_func: dolfinx.fem.Function,
|
|
12
12
|
alpha_endo: float = -60,
|
|
13
13
|
alpha_epi: float = 60,
|
|
14
|
-
**kwargs,
|
|
15
14
|
) -> utils.Microstructure:
|
|
16
15
|
"""Compute ldrb system for slab, assuming linear
|
|
17
16
|
angle between endo and epi
|
|
@@ -90,6 +89,7 @@ def create_microstructure(
|
|
|
90
89
|
alpha_epi: float,
|
|
91
90
|
function_space: str = "P_1",
|
|
92
91
|
outdir: str | Path | None = None,
|
|
92
|
+
**kwargs: dict,
|
|
93
93
|
) -> utils.Microstructure:
|
|
94
94
|
"""Generate microstructure for slab using LDRB algorithm
|
|
95
95
|
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import shutil
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from mpi4py import MPI
|
|
8
9
|
|
|
@@ -10,6 +11,7 @@ import adios2
|
|
|
10
11
|
import adios4dolfinx
|
|
11
12
|
import dolfinx
|
|
12
13
|
import numpy as np
|
|
14
|
+
import ufl
|
|
13
15
|
from packaging.version import Version
|
|
14
16
|
|
|
15
17
|
from . import utils
|
|
@@ -17,7 +19,7 @@ from . import utils
|
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
@dataclass
|
|
22
|
+
@dataclass
|
|
21
23
|
class Geometry:
|
|
22
24
|
mesh: dolfinx.mesh.Mesh
|
|
23
25
|
markers: dict[str, tuple[int, int]] = field(default_factory=dict)
|
|
@@ -28,8 +30,17 @@ class Geometry:
|
|
|
28
30
|
f0: dolfinx.fem.Function | None = None
|
|
29
31
|
s0: dolfinx.fem.Function | None = None
|
|
30
32
|
n0: dolfinx.fem.Function | None = None
|
|
33
|
+
info: dict[str, Any] = field(default_factory=dict)
|
|
31
34
|
|
|
32
35
|
def save(self, path: str | Path) -> None:
|
|
36
|
+
"""Save the geometry to a file using adios4dolfinx.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
path : str | Path
|
|
41
|
+
The path to the file where the geometry will be saved.
|
|
42
|
+
The file will be created if it does not exist, or overwritten if it does.
|
|
43
|
+
"""
|
|
33
44
|
path = Path(path)
|
|
34
45
|
|
|
35
46
|
shutil.rmtree(path, ignore_errors=True)
|
|
@@ -97,6 +108,123 @@ class Geometry:
|
|
|
97
108
|
|
|
98
109
|
self.mesh.comm.barrier()
|
|
99
110
|
|
|
111
|
+
@property
|
|
112
|
+
def dx(self):
|
|
113
|
+
"""Volume measure for the mesh using
|
|
114
|
+
the cell function `cfun` if it exists as subdomain data.
|
|
115
|
+
"""
|
|
116
|
+
return ufl.Measure("dx", domain=self.mesh, subdomain_data=self.cfun)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def ds(self):
|
|
120
|
+
"""Surface measure for the mesh using
|
|
121
|
+
the facet function `ffun` if it exists as subdomain data.
|
|
122
|
+
"""
|
|
123
|
+
return ufl.Measure("ds", domain=self.mesh, subdomain_data=self.ffun)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def facet_normal(self) -> ufl.FacetNormal:
|
|
127
|
+
"""Facet normal vector for the mesh."""
|
|
128
|
+
return ufl.FacetNormal(self.mesh)
|
|
129
|
+
|
|
130
|
+
def refine(self, n=1, outdir: Path | None = None) -> "Geometry":
|
|
131
|
+
"""
|
|
132
|
+
Refine the mesh and transfer the meshtags to new geometry.
|
|
133
|
+
Also regenerate fibers if `self.info` is found.
|
|
134
|
+
If `self.info` is not found, it currently raises a
|
|
135
|
+
NotImplementedError, however fiber could be interpolated
|
|
136
|
+
from the old mesh to the new mesh but this will result in a
|
|
137
|
+
loss of information about the fiber orientation.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
n : int, optional
|
|
142
|
+
Number of times to refine the mesh, by default 1
|
|
143
|
+
outdir : Path | None, optional
|
|
144
|
+
Output directory to save the refined mesh and meshtags,
|
|
145
|
+
by default None in which case the mesh is not saved.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
Geometry
|
|
150
|
+
A new Geometry object with the refined mesh and updated meshtags.
|
|
151
|
+
|
|
152
|
+
Raises
|
|
153
|
+
------
|
|
154
|
+
NotImplementedError
|
|
155
|
+
If `self.info` is not found, indicating that fiber
|
|
156
|
+
interpolation after refinement is not implemented yet.
|
|
157
|
+
"""
|
|
158
|
+
mesh = self.mesh
|
|
159
|
+
cfun = self.cfun
|
|
160
|
+
ffun = self.ffun
|
|
161
|
+
|
|
162
|
+
for _ in range(n):
|
|
163
|
+
new_mesh, parent_cell, parent_facet = dolfinx.mesh.refine(
|
|
164
|
+
mesh,
|
|
165
|
+
partitioner=None,
|
|
166
|
+
option=dolfinx.mesh.RefinementOption.parent_cell_and_facet,
|
|
167
|
+
)
|
|
168
|
+
new_mesh.name = mesh.name
|
|
169
|
+
mesh = new_mesh
|
|
170
|
+
new_mesh.topology.create_entities(1)
|
|
171
|
+
new_mesh.topology.create_connectivity(2, 3)
|
|
172
|
+
if cfun is not None:
|
|
173
|
+
new_cfun = dolfinx.mesh.transfer_meshtag(cfun, new_mesh, parent_cell, parent_facet)
|
|
174
|
+
new_cfun.name = cfun.name
|
|
175
|
+
cfun = new_cfun
|
|
176
|
+
else:
|
|
177
|
+
new_cfun = None
|
|
178
|
+
if ffun is not None:
|
|
179
|
+
new_ffun = dolfinx.mesh.transfer_meshtag(ffun, new_mesh, parent_cell, parent_facet)
|
|
180
|
+
new_ffun.name = ffun.name
|
|
181
|
+
ffun = new_ffun
|
|
182
|
+
else:
|
|
183
|
+
new_ffun = None
|
|
184
|
+
|
|
185
|
+
if outdir is not None:
|
|
186
|
+
outdir = Path(outdir)
|
|
187
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
188
|
+
with dolfinx.io.XDMFFile(new_mesh.comm, outdir / "mesh.xdmf", "w") as xdmf:
|
|
189
|
+
xdmf.write_mesh(new_mesh)
|
|
190
|
+
xdmf.write_meshtags(
|
|
191
|
+
cfun,
|
|
192
|
+
new_mesh.geometry,
|
|
193
|
+
geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{new_mesh.name}']/Geometry",
|
|
194
|
+
)
|
|
195
|
+
mesh.topology.create_connectivity(2, 3)
|
|
196
|
+
xdmf.write_meshtags(
|
|
197
|
+
ffun,
|
|
198
|
+
new_mesh.geometry,
|
|
199
|
+
geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{new_mesh.name}']/Geometry",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if self.info is not None:
|
|
203
|
+
info = self.info.copy()
|
|
204
|
+
info["refinement"] = n
|
|
205
|
+
info.pop("outdir", None)
|
|
206
|
+
if outdir is not None:
|
|
207
|
+
info["outdir"] = str(outdir)
|
|
208
|
+
from .fibers import generate_fibers
|
|
209
|
+
|
|
210
|
+
f0, s0, n0 = generate_fibers(mesh=new_mesh, ffun=new_ffun, markers=self.markers, **info)
|
|
211
|
+
else:
|
|
212
|
+
info = None
|
|
213
|
+
# Interpolate fibers
|
|
214
|
+
raise NotImplementedError(
|
|
215
|
+
"Interpolating fibers after refinement is not implemented yet."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return Geometry(
|
|
219
|
+
mesh=new_mesh,
|
|
220
|
+
markers=self.markers,
|
|
221
|
+
cfun=new_cfun,
|
|
222
|
+
ffun=new_ffun,
|
|
223
|
+
f0=f0,
|
|
224
|
+
s0=s0,
|
|
225
|
+
n0=n0,
|
|
226
|
+
)
|
|
227
|
+
|
|
100
228
|
@classmethod
|
|
101
229
|
def from_file(
|
|
102
230
|
cls,
|
|
@@ -104,6 +232,24 @@ class Geometry:
|
|
|
104
232
|
path: str | Path,
|
|
105
233
|
function_space_data: dict[str, np.ndarray] | None = None,
|
|
106
234
|
) -> "Geometry":
|
|
235
|
+
"""Read geometry from a file using adios4dolfinx.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
comm : MPI.Intracomm
|
|
240
|
+
The MPI communicator to use for reading the mesh.
|
|
241
|
+
path : str | Path
|
|
242
|
+
The path to the file containing the geometry data.
|
|
243
|
+
function_space_data : dict[str, np.ndarray] | None, optional
|
|
244
|
+
A dictionary containing function space data for the functions to be read.
|
|
245
|
+
If None, it will be read from the file.
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
Geometry
|
|
250
|
+
An instance of the Geometry class containing the mesh, markers, and functions.
|
|
251
|
+
"""
|
|
252
|
+
|
|
107
253
|
path = Path(path)
|
|
108
254
|
|
|
109
255
|
mesh = adios4dolfinx.read_mesh(comm=comm, filename=path)
|
|
@@ -148,6 +294,31 @@ class Geometry:
|
|
|
148
294
|
|
|
149
295
|
@classmethod
|
|
150
296
|
def from_folder(cls, comm: MPI.Intracomm, folder: str | Path) -> "Geometry":
|
|
297
|
+
"""Read geometry from a folder containing mesh and markers files.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
comm : MPI.Intracomm
|
|
302
|
+
The MPI communicator to use for reading the mesh and markers.
|
|
303
|
+
folder : str | Path
|
|
304
|
+
The path to the folder containing the geometry data.
|
|
305
|
+
The folder should contain the following files:
|
|
306
|
+
- mesh.xdmf: The mesh file in XDMF format.
|
|
307
|
+
- markers.json: A JSON file containing markers.
|
|
308
|
+
- microstructure.json: A JSON file containing microstructure data (optional).
|
|
309
|
+
- microstructure.bp: A BP file containing microstructure functions (optional).
|
|
310
|
+
- info.json: A JSON file containing additional information (optional).
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
Geometry
|
|
315
|
+
An instance of the Geometry class containing the mesh, markers, and functions.
|
|
316
|
+
|
|
317
|
+
Raises
|
|
318
|
+
------
|
|
319
|
+
ValueError
|
|
320
|
+
If the required mesh file is not found in the specified folder.
|
|
321
|
+
"""
|
|
151
322
|
folder = Path(folder)
|
|
152
323
|
logger.info(f"Reading geometry from {folder}")
|
|
153
324
|
# Read mesh
|
|
@@ -196,9 +367,20 @@ class Geometry:
|
|
|
196
367
|
else:
|
|
197
368
|
functions[name] = f
|
|
198
369
|
|
|
370
|
+
if (folder / "info.json").exists():
|
|
371
|
+
logger.debug("Reading info")
|
|
372
|
+
if comm.rank == 0:
|
|
373
|
+
info = json.loads((folder / "info.json").read_text())
|
|
374
|
+
else:
|
|
375
|
+
info = {}
|
|
376
|
+
info = comm.bcast(info, root=0)
|
|
377
|
+
else:
|
|
378
|
+
info = {}
|
|
379
|
+
|
|
199
380
|
return cls(
|
|
200
381
|
mesh=mesh,
|
|
201
382
|
markers=markers,
|
|
383
|
+
info=info,
|
|
202
384
|
**functions,
|
|
203
385
|
**tags,
|
|
204
386
|
)
|
|
@@ -10,6 +10,7 @@ from mpi4py import MPI
|
|
|
10
10
|
|
|
11
11
|
import cardiac_geometries_core as cgc
|
|
12
12
|
import dolfinx
|
|
13
|
+
import numpy as np
|
|
13
14
|
from structlog import get_logger
|
|
14
15
|
|
|
15
16
|
from . import utils
|
|
@@ -22,19 +23,80 @@ __version__ = meta["Version"]
|
|
|
22
23
|
logger = get_logger()
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
def transform_markers(
|
|
27
|
+
markers: dict[str, tuple[int, int]], clipped: bool = False
|
|
28
|
+
) -> dict[str, list[int]]:
|
|
29
|
+
if clipped:
|
|
30
|
+
return {
|
|
31
|
+
"lv": [markers["LV"][0]],
|
|
32
|
+
"rv": [markers["RV"][0]],
|
|
33
|
+
"epi": [markers["EPI"][0]],
|
|
34
|
+
"base": [markers["BASE"][0]],
|
|
35
|
+
}
|
|
36
|
+
else:
|
|
37
|
+
return {
|
|
38
|
+
"lv": [markers["LV"][0]],
|
|
39
|
+
"rv": [markers["RV"][0]],
|
|
40
|
+
"epi": [markers["EPI"][0]],
|
|
41
|
+
"base": [
|
|
42
|
+
markers["PV"][0],
|
|
43
|
+
markers["TV"][0],
|
|
44
|
+
markers["AV"][0],
|
|
45
|
+
markers["MV"][0],
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
25
50
|
def ukb(
|
|
26
51
|
outdir: str | Path,
|
|
27
52
|
mode: int = -1,
|
|
28
53
|
std: float = 1.5,
|
|
29
54
|
case: str = "ED",
|
|
30
|
-
char_length_max: float =
|
|
31
|
-
char_length_min: float =
|
|
55
|
+
char_length_max: float = 5.0,
|
|
56
|
+
char_length_min: float = 5.0,
|
|
32
57
|
fiber_angle_endo: float = 60,
|
|
33
58
|
fiber_angle_epi: float = -60,
|
|
59
|
+
create_fibers: bool = True,
|
|
34
60
|
fiber_space: str = "P_1",
|
|
35
61
|
clipped: bool = False,
|
|
36
62
|
comm: MPI.Comm = MPI.COMM_WORLD,
|
|
37
63
|
) -> Geometry:
|
|
64
|
+
"""Create a mesh from the UK-Biobank atlas using
|
|
65
|
+
the ukb-atlas package.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
outdir : str | Path
|
|
70
|
+
Directory where to save the results.
|
|
71
|
+
mode : int, optional
|
|
72
|
+
Mode for the UKB mesh, by default -1
|
|
73
|
+
std : float, optional
|
|
74
|
+
Standard deviation for the UKB mesh, by default 1.5
|
|
75
|
+
case : str, optional
|
|
76
|
+
Case for the UKB mesh (either "ED" or "ES"), by default "ED"
|
|
77
|
+
char_length_max : float, optional
|
|
78
|
+
Maximum characteristic length of the mesh, by default 2.0
|
|
79
|
+
char_length_min : float, optional
|
|
80
|
+
Minimum characteristic length of the mesh, by default 2.0
|
|
81
|
+
fiber_angle_endo : float, optional
|
|
82
|
+
Fiber angle for the endocardium, by default 60
|
|
83
|
+
fiber_angle_epi : float, optional
|
|
84
|
+
Fiber angle for the epicardium, by default -60
|
|
85
|
+
create_fibers : bool, optional
|
|
86
|
+
If True create rule-based fibers, by default True
|
|
87
|
+
fiber_space : str, optional
|
|
88
|
+
Function space for fibers of the form family_degree, by default "P_1"
|
|
89
|
+
clipped : bool, optional
|
|
90
|
+
If True create a clipped mesh, by default False
|
|
91
|
+
comm : MPI.Comm, optional
|
|
92
|
+
MPI communicator, by default MPI.COMM_WORLD
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
cardiac_geometries.geometry.Geometry
|
|
97
|
+
A Geometry with the mesh, markers, markers functions and fibers.
|
|
98
|
+
|
|
99
|
+
"""
|
|
38
100
|
try:
|
|
39
101
|
import ukb.cli
|
|
40
102
|
except ImportError as e:
|
|
@@ -87,67 +149,51 @@ def ukb(
|
|
|
87
149
|
"fiber_angle_epi": fiber_angle_epi,
|
|
88
150
|
"fiber_space": fiber_space,
|
|
89
151
|
"cardiac_geometry_version": __version__,
|
|
90
|
-
"
|
|
152
|
+
"mesh_type": "ukb",
|
|
153
|
+
"clipped": clipped,
|
|
91
154
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
92
155
|
}
|
|
93
156
|
)
|
|
94
157
|
)
|
|
95
158
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
if create_fibers:
|
|
160
|
+
try:
|
|
161
|
+
import ldrb
|
|
162
|
+
except ImportError as ex:
|
|
163
|
+
msg = (
|
|
164
|
+
"To create fibers you need to install the ldrb package "
|
|
165
|
+
"which you can install with pip install fenicsx-ldrb"
|
|
166
|
+
)
|
|
167
|
+
raise ImportError(msg) from ex
|
|
104
168
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"epi": [geometry.markers["EPI"][0]],
|
|
117
|
-
"base": [
|
|
118
|
-
geometry.markers["PV"][0],
|
|
119
|
-
geometry.markers["TV"][0],
|
|
120
|
-
geometry.markers["AV"][0],
|
|
121
|
-
geometry.markers["MV"][0],
|
|
122
|
-
],
|
|
123
|
-
}
|
|
124
|
-
system = ldrb.dolfinx_ldrb(
|
|
125
|
-
mesh=geometry.mesh,
|
|
126
|
-
ffun=geometry.ffun,
|
|
127
|
-
markers=markers,
|
|
128
|
-
alpha_endo_lv=fiber_angle_endo,
|
|
129
|
-
alpha_epi_lv=fiber_angle_epi,
|
|
130
|
-
beta_endo_lv=0,
|
|
131
|
-
beta_epi_lv=0,
|
|
132
|
-
fiber_space=fiber_space,
|
|
133
|
-
)
|
|
169
|
+
markers = transform_markers(geometry.markers, clipped=clipped)
|
|
170
|
+
system = ldrb.dolfinx_ldrb(
|
|
171
|
+
mesh=geometry.mesh,
|
|
172
|
+
ffun=geometry.ffun,
|
|
173
|
+
markers=markers,
|
|
174
|
+
alpha_endo_lv=fiber_angle_endo,
|
|
175
|
+
alpha_epi_lv=fiber_angle_epi,
|
|
176
|
+
beta_endo_lv=0,
|
|
177
|
+
beta_epi_lv=0,
|
|
178
|
+
fiber_space=fiber_space,
|
|
179
|
+
)
|
|
134
180
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
181
|
+
save_microstructure(
|
|
182
|
+
mesh=geometry.mesh,
|
|
183
|
+
functions=(system.f0, system.s0, system.n0),
|
|
184
|
+
outdir=outdir,
|
|
185
|
+
)
|
|
140
186
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
187
|
+
for k, v in system._asdict().items():
|
|
188
|
+
if v is None:
|
|
189
|
+
continue
|
|
190
|
+
if fiber_space.startswith("Q"):
|
|
191
|
+
# Cannot visualize Quadrature spaces yet
|
|
192
|
+
continue
|
|
147
193
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
194
|
+
logger.debug(f"Write {k}: {v}")
|
|
195
|
+
with dolfinx.io.VTXWriter(comm, outdir / f"{k}-viz.bp", [v], engine="BP4") as vtx:
|
|
196
|
+
vtx.write(0.0)
|
|
151
197
|
|
|
152
198
|
geo = Geometry.from_folder(comm=comm, folder=outdir)
|
|
153
199
|
return geo
|
|
@@ -272,10 +318,10 @@ def biv_ellipsoid(
|
|
|
272
318
|
"b_epi_rv": b_epi_rv,
|
|
273
319
|
"c_epi_rv": c_epi_rv,
|
|
274
320
|
"create_fibers": create_fibers,
|
|
275
|
-
"
|
|
276
|
-
"
|
|
321
|
+
"fiber_angle_endo": fiber_angle_endo,
|
|
322
|
+
"fiber_angle_epi": fiber_angle_epi,
|
|
277
323
|
"fiber_space": fiber_space,
|
|
278
|
-
|
|
324
|
+
"mesh_type": "biv_ellipsoid",
|
|
279
325
|
"cardiac_geometry_version": __version__,
|
|
280
326
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
281
327
|
},
|
|
@@ -486,10 +532,10 @@ def biv_ellipsoid_torso(
|
|
|
486
532
|
"b_epi_rv": b_epi_rv,
|
|
487
533
|
"c_epi_rv": c_epi_rv,
|
|
488
534
|
"create_fibers": create_fibers,
|
|
489
|
-
"
|
|
490
|
-
"
|
|
535
|
+
"fiber_angle_endo": fiber_angle_endo,
|
|
536
|
+
"fiber_angle_epi": fiber_angle_epi,
|
|
491
537
|
"fiber_space": fiber_space,
|
|
492
|
-
|
|
538
|
+
"mesh_type": "biv",
|
|
493
539
|
"cardiac_geometry_version": __version__,
|
|
494
540
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
495
541
|
},
|
|
@@ -640,11 +686,11 @@ def lv_ellipsoid(
|
|
|
640
686
|
"mu_apex_epi": mu_apex_epi,
|
|
641
687
|
"mu_base_epi": mu_base_epi,
|
|
642
688
|
"create_fibers": create_fibers,
|
|
643
|
-
"
|
|
644
|
-
"
|
|
689
|
+
"fiber_angle_endo": fiber_angle_endo,
|
|
690
|
+
"fiber_angle_epi": fiber_angle_epi,
|
|
645
691
|
"fiber_space": fiber_space,
|
|
646
692
|
"aha": aha,
|
|
647
|
-
|
|
693
|
+
"mesh_type": "lv_ellipsoid",
|
|
648
694
|
"cardiac_geometry_version": __version__,
|
|
649
695
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
650
696
|
},
|
|
@@ -717,6 +763,70 @@ def lv_ellipsoid(
|
|
|
717
763
|
return geo
|
|
718
764
|
|
|
719
765
|
|
|
766
|
+
def slab_dolfinx(
|
|
767
|
+
comm, outdir: Path, lx: float = 20.0, ly: float = 7.0, lz: float = 3.0, dx: float = 1.0
|
|
768
|
+
) -> utils.GMshGeometry:
|
|
769
|
+
mesh = dolfinx.mesh.create_box(
|
|
770
|
+
comm,
|
|
771
|
+
[[0.0, 0.0, 0.0], [lx, ly, lz]],
|
|
772
|
+
[int(lx / dx), int(ly / dx), int(lz / dx)],
|
|
773
|
+
dolfinx.mesh.CellType.tetrahedron,
|
|
774
|
+
ghost_mode=dolfinx.mesh.GhostMode.none,
|
|
775
|
+
)
|
|
776
|
+
mesh.name = "Mesh"
|
|
777
|
+
fdim = mesh.topology.dim - 1
|
|
778
|
+
x0_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[0], 0))
|
|
779
|
+
x1_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[0], lx))
|
|
780
|
+
y0_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[1], 0))
|
|
781
|
+
y1_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[1], ly))
|
|
782
|
+
z0_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[2], 0))
|
|
783
|
+
z1_facets = dolfinx.mesh.locate_entities_boundary(mesh, fdim, lambda x: np.isclose(x[2], lz))
|
|
784
|
+
|
|
785
|
+
# Concatenate and sort the arrays based on facet indices.
|
|
786
|
+
# Left facets marked with 1, right facets with two
|
|
787
|
+
marked_facets = np.hstack([x0_facets, x1_facets, y0_facets, y1_facets, z0_facets, z1_facets])
|
|
788
|
+
|
|
789
|
+
marked_values = np.hstack(
|
|
790
|
+
[
|
|
791
|
+
np.full_like(x0_facets, 1),
|
|
792
|
+
np.full_like(x1_facets, 2),
|
|
793
|
+
np.full_like(y0_facets, 3),
|
|
794
|
+
np.full_like(y1_facets, 4),
|
|
795
|
+
np.full_like(z0_facets, 5),
|
|
796
|
+
np.full_like(z1_facets, 6),
|
|
797
|
+
],
|
|
798
|
+
)
|
|
799
|
+
sorted_facets = np.argsort(marked_facets)
|
|
800
|
+
ft = dolfinx.mesh.meshtags(
|
|
801
|
+
mesh,
|
|
802
|
+
fdim,
|
|
803
|
+
marked_facets[sorted_facets],
|
|
804
|
+
marked_values[sorted_facets],
|
|
805
|
+
)
|
|
806
|
+
ft.name = "Facet tags"
|
|
807
|
+
markers = {
|
|
808
|
+
"X0": (1, 2),
|
|
809
|
+
"X1": (2, 2),
|
|
810
|
+
"Y0": (3, 2),
|
|
811
|
+
"Y1": (4, 2),
|
|
812
|
+
"Z0": (5, 2),
|
|
813
|
+
"Z1": (6, 2),
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
with dolfinx.io.XDMFFile(comm, outdir / "mesh.xdmf", "w") as xdmf:
|
|
817
|
+
xdmf.write_mesh(mesh)
|
|
818
|
+
xdmf.write_meshtags(ft, mesh.geometry)
|
|
819
|
+
|
|
820
|
+
return utils.GMshGeometry(
|
|
821
|
+
mesh=mesh,
|
|
822
|
+
markers=markers,
|
|
823
|
+
cfun=None,
|
|
824
|
+
ffun=ft.indices,
|
|
825
|
+
efun=None,
|
|
826
|
+
vfun=None,
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
|
|
720
830
|
def slab(
|
|
721
831
|
outdir: Path | str,
|
|
722
832
|
lx: float = 20.0,
|
|
@@ -728,6 +838,7 @@ def slab(
|
|
|
728
838
|
fiber_angle_epi: float = -60,
|
|
729
839
|
fiber_space: str = "P_1",
|
|
730
840
|
verbose: bool = False,
|
|
841
|
+
use_dolfinx: bool = False,
|
|
731
842
|
comm: MPI.Comm = MPI.COMM_WORLD,
|
|
732
843
|
) -> Geometry:
|
|
733
844
|
"""Create slab geometry
|
|
@@ -754,6 +865,8 @@ def slab(
|
|
|
754
865
|
Function space for fibers of the form family_degree, by default "P_1"
|
|
755
866
|
verbose : bool, optional
|
|
756
867
|
If True print information from gmsh, by default False
|
|
868
|
+
use_dolfinx : bool, optional
|
|
869
|
+
If True use dolfinx to create the mesh, by default False (gmsh)
|
|
757
870
|
comm : MPI.Comm, optional
|
|
758
871
|
MPI communicator, by default MPI.COMM_WORLD
|
|
759
872
|
|
|
@@ -771,15 +884,15 @@ def slab(
|
|
|
771
884
|
with open(outdir / "info.json", "w") as f:
|
|
772
885
|
json.dump(
|
|
773
886
|
{
|
|
774
|
-
"
|
|
775
|
-
"
|
|
776
|
-
"
|
|
887
|
+
"Lx": lx,
|
|
888
|
+
"Ly": ly,
|
|
889
|
+
"Lz": lz,
|
|
777
890
|
"dx": dx,
|
|
778
891
|
"create_fibers": create_fibers,
|
|
779
|
-
"
|
|
780
|
-
"
|
|
892
|
+
"fiber_angle_endo": fiber_angle_endo,
|
|
893
|
+
"fiber_angle_epi": fiber_angle_epi,
|
|
781
894
|
"fiber_space": fiber_space,
|
|
782
|
-
|
|
895
|
+
"mesh_type": "slab",
|
|
783
896
|
"cardiac_geometry_version": __version__,
|
|
784
897
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
785
898
|
},
|
|
@@ -788,17 +901,29 @@ def slab(
|
|
|
788
901
|
default=utils.json_serial,
|
|
789
902
|
)
|
|
790
903
|
|
|
791
|
-
|
|
792
|
-
|
|
904
|
+
if not use_dolfinx:
|
|
905
|
+
cgc.slab(
|
|
906
|
+
mesh_name=mesh_name.as_posix(),
|
|
907
|
+
lx=lx,
|
|
908
|
+
ly=ly,
|
|
909
|
+
lz=lz,
|
|
910
|
+
dx=dx,
|
|
911
|
+
verbose=verbose,
|
|
912
|
+
)
|
|
913
|
+
comm.barrier()
|
|
914
|
+
|
|
915
|
+
if use_dolfinx:
|
|
916
|
+
geometry = slab_dolfinx(
|
|
917
|
+
comm=comm,
|
|
918
|
+
outdir=outdir,
|
|
793
919
|
lx=lx,
|
|
794
920
|
ly=ly,
|
|
795
921
|
lz=lz,
|
|
796
922
|
dx=dx,
|
|
797
|
-
verbose=verbose,
|
|
798
923
|
)
|
|
799
|
-
comm.barrier()
|
|
800
924
|
|
|
801
|
-
|
|
925
|
+
else:
|
|
926
|
+
geometry = utils.gmsh2dolfin(comm=comm, msh_file=mesh_name)
|
|
802
927
|
|
|
803
928
|
if comm.rank == 0:
|
|
804
929
|
with open(outdir / "markers.json", "w") as f:
|
|
@@ -880,7 +1005,7 @@ def slab_in_bath(
|
|
|
880
1005
|
"by": by,
|
|
881
1006
|
"bz": bz,
|
|
882
1007
|
"dx": dx,
|
|
883
|
-
|
|
1008
|
+
"mesh_type": "slab-bath",
|
|
884
1009
|
"cardiac_geometry_version": __version__,
|
|
885
1010
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
886
1011
|
},
|
|
@@ -975,11 +1100,11 @@ def cylinder(
|
|
|
975
1100
|
"height": height,
|
|
976
1101
|
"char_length": char_length,
|
|
977
1102
|
"create_fibers": create_fibers,
|
|
978
|
-
"
|
|
979
|
-
"
|
|
1103
|
+
"fiber_angle_endo": fiber_angle_endo,
|
|
1104
|
+
"fiber_angle_epi": fiber_angle_epi,
|
|
980
1105
|
"fiber_space": fiber_space,
|
|
981
1106
|
"aha": aha,
|
|
982
|
-
|
|
1107
|
+
"mesh_type": "cylinder",
|
|
983
1108
|
"cardiac_geometry_version": __version__,
|
|
984
1109
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
985
1110
|
},
|
|
@@ -403,7 +403,7 @@ def read_mesh(
|
|
|
403
403
|
) -> tuple[dolfinx.mesh.Mesh, dict[str, dolfinx.mesh.MeshTags]]:
|
|
404
404
|
tags = {}
|
|
405
405
|
with dolfinx.io.XDMFFile(comm, filename, "r") as xdmf:
|
|
406
|
-
mesh = xdmf.read_mesh(name="Mesh")
|
|
406
|
+
mesh = xdmf.read_mesh(name="Mesh", ghost_mode=dolfinx.mesh.GhostMode.none)
|
|
407
407
|
for var, name, dim in [
|
|
408
408
|
("cfun", "Cell tags", mesh.topology.dim),
|
|
409
409
|
("ffun", "Facet tags", mesh.topology.dim - 1),
|
{cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2/src/cardiac_geometriesx.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardiac-geometriesx
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A python library for cardiac geometries
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -77,7 +77,11 @@ To install the package you can use `pip`
|
|
|
77
77
|
```
|
|
78
78
|
python3 -m pip install cardiac-geometriesx
|
|
79
79
|
```
|
|
80
|
-
however, this assumes that you already have `dolfinx` pre-installed. You can also use
|
|
80
|
+
however, this assumes that you already have `dolfinx` pre-installed. You can also use `conda`
|
|
81
|
+
```
|
|
82
|
+
conda install -c conda-forge cardiac-geometriesx
|
|
83
|
+
```
|
|
84
|
+
or the provided docker image
|
|
81
85
|
```
|
|
82
86
|
docker pull ghcr.io/computationalphysiology/cardiac-geometriesx:latest
|
|
83
87
|
```
|
|
@@ -21,6 +21,8 @@ try:
|
|
|
21
21
|
except ImportError:
|
|
22
22
|
HAS_UKB = False
|
|
23
23
|
|
|
24
|
+
MPI_SIZE = MPI.COMM_WORLD.size
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
@pytest.mark.parametrize(
|
|
26
28
|
"script",
|
|
@@ -60,7 +62,8 @@ def test_biv_fibers(tmp_path: Path):
|
|
|
60
62
|
path = comm.bcast(tmp_path, root=0)
|
|
61
63
|
|
|
62
64
|
res = runner.invoke(
|
|
63
|
-
cli.biv_ellipsoid,
|
|
65
|
+
cli.biv_ellipsoid,
|
|
66
|
+
[path.as_posix(), "--create-fibers", "--fiber-space", "P_1"],
|
|
64
67
|
)
|
|
65
68
|
assert res.exit_code == 0
|
|
66
69
|
assert path.is_dir()
|
|
@@ -95,6 +98,7 @@ def test_script_no_fibers(script, tmp_path: Path):
|
|
|
95
98
|
@pytest.mark.parametrize("clipped", [True, False])
|
|
96
99
|
@pytest.mark.parametrize("case", ["ED", "ES"])
|
|
97
100
|
@pytest.mark.skipif(not HAS_UKB, reason="UKB atlas is not installed")
|
|
101
|
+
@pytest.mark.skipif(MPI.COMM_WORLD.size > 1, reason="Pyvista operations is not parallelized yet")
|
|
98
102
|
def test_ukb(tmp_path: Path, case: str, clipped: bool):
|
|
99
103
|
runner = CliRunner()
|
|
100
104
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mpi4py import MPI
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import cardiac_geometries as cg
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import ldrb # noqa: F401
|
|
11
|
+
|
|
12
|
+
HAS_LDRB = True
|
|
13
|
+
except ImportError:
|
|
14
|
+
HAS_LDRB = False
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import ukb.cli # noqa: F401
|
|
18
|
+
|
|
19
|
+
HAS_UKB = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HAS_UKB = False
|
|
22
|
+
|
|
23
|
+
MPI_SIZE = MPI.COMM_WORLD.size
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"script",
|
|
28
|
+
[
|
|
29
|
+
cg.mesh.slab,
|
|
30
|
+
cg.mesh.lv_ellipsoid,
|
|
31
|
+
cg.mesh.cylinder,
|
|
32
|
+
],
|
|
33
|
+
ids=["slab", "lv_ellipsoid", "cylinder"],
|
|
34
|
+
)
|
|
35
|
+
def test_refine_analytic_fibers(script, tmp_path: Path):
|
|
36
|
+
comm = MPI.COMM_WORLD
|
|
37
|
+
path = comm.bcast(tmp_path, root=0)
|
|
38
|
+
outdir = path / "mesh"
|
|
39
|
+
script(outdir=outdir, create_fibers=True, comm=comm)
|
|
40
|
+
geo = cg.Geometry.from_folder(comm=comm, folder=outdir)
|
|
41
|
+
assert (outdir / "mesh.xdmf").is_file()
|
|
42
|
+
assert geo.f0 is not None
|
|
43
|
+
# assert geo.mesh.geometry.dim == 3
|
|
44
|
+
refined_outdir = path / "refined"
|
|
45
|
+
refined = geo.refine(outdir=refined_outdir, n=1)
|
|
46
|
+
assert refined.f0 is not None
|
|
47
|
+
assert (refined_outdir / "mesh.xdmf").is_file()
|
|
48
|
+
assert (
|
|
49
|
+
refined.f0.function_space.dofmap.index_map.size_global
|
|
50
|
+
> geo.f0.function_space.dofmap.index_map.size_global
|
|
51
|
+
)
|
|
52
|
+
assert refined.mesh.geometry.index_map().size_global > geo.mesh.geometry.index_map().size_global
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.skipif(not HAS_LDRB, reason="LDRB atlas is not installed")
|
|
56
|
+
def test_refine_biv(tmp_path: Path):
|
|
57
|
+
comm = MPI.COMM_WORLD
|
|
58
|
+
path = comm.bcast(tmp_path, root=0)
|
|
59
|
+
outdir = path / "mesh"
|
|
60
|
+
cg.mesh.biv_ellipsoid(outdir=outdir, create_fibers=True, comm=comm)
|
|
61
|
+
geo = cg.Geometry.from_folder(comm=comm, folder=outdir)
|
|
62
|
+
assert geo.f0 is not None
|
|
63
|
+
assert (outdir / "mesh.xdmf").is_file()
|
|
64
|
+
# assert geo.mesh.geometry.dim == 3
|
|
65
|
+
refined_outdir = path / "refined"
|
|
66
|
+
refined = geo.refine(outdir=refined_outdir, n=1)
|
|
67
|
+
assert refined.f0 is not None
|
|
68
|
+
assert (refined_outdir / "mesh.xdmf").is_file()
|
|
69
|
+
assert (
|
|
70
|
+
refined.f0.function_space.dofmap.index_map.size_global
|
|
71
|
+
> geo.f0.function_space.dofmap.index_map.size_global
|
|
72
|
+
)
|
|
73
|
+
assert refined.mesh.geometry.index_map().size_global > geo.mesh.geometry.index_map().size_global
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.skipif(not HAS_LDRB, reason="LDRB atlas is not installed")
|
|
77
|
+
@pytest.mark.skipif(not HAS_UKB, reason="UKB atlas is not installed")
|
|
78
|
+
@pytest.mark.skipif(MPI.COMM_WORLD.size > 1, reason="Pyvista operations is not parallelized yet")
|
|
79
|
+
def test_refine_ukb(tmp_path: Path):
|
|
80
|
+
comm = MPI.COMM_WORLD
|
|
81
|
+
path = comm.bcast(tmp_path, root=0)
|
|
82
|
+
outdir = path / "mesh"
|
|
83
|
+
cg.mesh.ukb(outdir=outdir, create_fibers=True, comm=comm)
|
|
84
|
+
geo = cg.Geometry.from_folder(comm=comm, folder=outdir)
|
|
85
|
+
assert geo.f0 is not None
|
|
86
|
+
assert (outdir / "mesh.xdmf").is_file()
|
|
87
|
+
# assert geo.mesh.geometry.dim == 3
|
|
88
|
+
refined_outdir = path / "refined"
|
|
89
|
+
refined = geo.refine(outdir=refined_outdir, n=1)
|
|
90
|
+
assert refined.f0 is not None
|
|
91
|
+
assert (refined_outdir / "mesh.xdmf").is_file()
|
|
92
|
+
assert (
|
|
93
|
+
refined.f0.function_space.dofmap.index_map.size_global
|
|
94
|
+
> geo.f0.function_space.dofmap.index_map.size_global
|
|
95
|
+
)
|
|
96
|
+
assert refined.mesh.geometry.index_map().size_global > geo.mesh.geometry.index_map().size_global
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cardiac_geometriesx-0.5.1 → cardiac_geometriesx-0.5.2}/src/cardiac_geometries/fibers/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|