cardiac-geometriesx 0.4.5__tar.gz → 0.4.6__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.

Files changed (24) hide show
  1. {cardiac_geometriesx-0.4.5/src/cardiac_geometriesx.egg-info → cardiac_geometriesx-0.4.6}/PKG-INFO +4 -2
  2. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/pyproject.toml +4 -2
  3. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/cli.py +100 -1
  4. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/geometry.py +8 -1
  5. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/mesh.py +133 -2
  6. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/utils.py +14 -4
  7. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6/src/cardiac_geometriesx.egg-info}/PKG-INFO +4 -2
  8. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/requires.txt +2 -0
  9. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/tests/test_cli.py +28 -0
  10. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/LICENSE +0 -0
  11. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/README.md +0 -0
  12. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/setup.cfg +0 -0
  13. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/__init__.py +0 -0
  14. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/fibers/__init__.py +0 -0
  15. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/fibers/lv_ellipsoid.py +0 -0
  16. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/fibers/slab.py +0 -0
  17. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/fibers/utils.py +0 -0
  18. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometries/gui.py +0 -0
  19. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/SOURCES.txt +0 -0
  20. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/dependency_links.txt +0 -0
  21. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/entry_points.txt +0 -0
  22. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/not-zip-safe +0 -0
  23. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/src/cardiac_geometriesx.egg-info/top_level.txt +0 -0
  24. {cardiac_geometriesx-0.4.5 → cardiac_geometriesx-0.4.6}/tests/test_save_load.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: cardiac-geometriesx
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: A python library for cardiac geometries
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License: MIT
@@ -38,6 +38,8 @@ Provides-Extra: test
38
38
  Requires-Dist: pre-commit; extra == "test"
39
39
  Requires-Dist: pytest; extra == "test"
40
40
  Requires-Dist: pytest-cov; extra == "test"
41
+ Requires-Dist: ukb-atlas; extra == "test"
42
+ Requires-Dist: fenicsx-ldrb; extra == "test"
41
43
  Provides-Extra: gui
42
44
  Requires-Dist: streamlit; extra == "gui"
43
45
  Requires-Dist: stpyvista; extra == "gui"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cardiac-geometriesx"
7
- version = "0.4.5"
7
+ version = "0.4.6"
8
8
  description = "A python library for cardiac geometries"
9
9
  authors = [{name = "Henrik Finsberg", email = "henriknf@simula.no"}]
10
10
  license = {text = "MIT"}
@@ -52,6 +52,8 @@ test = [
52
52
  "pre-commit",
53
53
  "pytest",
54
54
  "pytest-cov",
55
+ "ukb-atlas",
56
+ "fenicsx-ldrb",
55
57
  ]
56
58
  gui = [
57
59
  "streamlit",
@@ -175,7 +177,7 @@ tag = true
175
177
  sign_tags = false
176
178
  tag_name = "v{new_version}"
177
179
  tag_message = "Bump version: {current_version} → {new_version}"
178
- current_version = "0.4.5"
180
+ current_version = "0.4.6"
179
181
 
180
182
 
181
183
  [[tool.bumpversion.files]]
@@ -24,6 +24,102 @@ def app():
24
24
  pass
25
25
 
26
26
 
27
+ @click.command(help="Create UK Biobank geometry")
28
+ @click.argument(
29
+ "outdir",
30
+ required=True,
31
+ type=click.Path(
32
+ file_okay=False,
33
+ dir_okay=True,
34
+ writable=True,
35
+ readable=True,
36
+ resolve_path=True,
37
+ ),
38
+ )
39
+ @click.option(
40
+ "--mode",
41
+ default=-1,
42
+ type=int,
43
+ help="Mode of the geometry",
44
+ show_default=True,
45
+ )
46
+ @click.option(
47
+ "--std",
48
+ default=1.5,
49
+ type=float,
50
+ help="Standard deviation of the geometry",
51
+ show_default=True,
52
+ )
53
+ @click.option(
54
+ "--case",
55
+ default="ED",
56
+ type=str,
57
+ help="Case of the geometry",
58
+ show_default=True,
59
+ )
60
+ @click.option(
61
+ "--char-length-max",
62
+ default=2.0,
63
+ type=float,
64
+ help="Maximum characteristic length of the mesh",
65
+ show_default=True,
66
+ )
67
+ @click.option(
68
+ "--char-length-min",
69
+ default=2.0,
70
+ type=float,
71
+ help="Minimum characteristic length of the mesh",
72
+ show_default=True,
73
+ )
74
+ @click.option(
75
+ "--fiber-angle-endo",
76
+ default=60,
77
+ type=float,
78
+ help="Angle for the endocardium",
79
+ show_default=True,
80
+ )
81
+ @click.option(
82
+ "--fiber-angle-epi",
83
+ default=-60,
84
+ type=float,
85
+ help="Angle for the epicardium",
86
+ show_default=True,
87
+ )
88
+ @click.option(
89
+ "--fiber-space",
90
+ default="P_1",
91
+ type=str,
92
+ help="Function space for fibers of the form family_degree",
93
+ show_default=True,
94
+ )
95
+ def ukb(
96
+ outdir: Path | str,
97
+ mode: int = -1,
98
+ std: float = 1.5,
99
+ case: str = "ED",
100
+ char_length_max: float = 2.0,
101
+ char_length_min: float = 2.0,
102
+ fiber_angle_endo: float = 60,
103
+ fiber_angle_epi: float = -60,
104
+ fiber_space: str = "P_1",
105
+ ):
106
+ outdir = Path(outdir)
107
+ outdir.mkdir(exist_ok=True)
108
+
109
+ geo = mesh.ukb(
110
+ outdir=outdir,
111
+ mode=mode,
112
+ std=std,
113
+ case=case,
114
+ char_length_max=char_length_max,
115
+ char_length_min=char_length_min,
116
+ fiber_angle_endo=fiber_angle_endo,
117
+ fiber_angle_epi=fiber_angle_epi,
118
+ fiber_space=fiber_space,
119
+ )
120
+ geo.save(outdir / "ukb.bp")
121
+
122
+
27
123
  @click.command(help="Create LV ellipsoidal geometry")
28
124
  @click.argument(
29
125
  "outdir",
@@ -382,6 +478,7 @@ def biv_ellipsoid(
382
478
  geo = mesh.biv_ellipsoid(
383
479
  outdir=outdir,
384
480
  char_length=char_length,
481
+ center_lv_x=center_lv_x,
385
482
  center_lv_y=center_lv_y,
386
483
  center_lv_z=center_lv_z,
387
484
  a_endo_lv=a_endo_lv,
@@ -390,6 +487,7 @@ def biv_ellipsoid(
390
487
  a_epi_lv=a_epi_lv,
391
488
  b_epi_lv=b_epi_lv,
392
489
  c_epi_lv=c_epi_lv,
490
+ center_rv_x=center_rv_x,
393
491
  center_rv_y=center_rv_y,
394
492
  center_rv_z=center_rv_z,
395
493
  a_endo_rv=a_endo_rv,
@@ -457,7 +555,7 @@ def biv_ellipsoid(
457
555
  default=math.pi / 6,
458
556
  type=float,
459
557
  help=(
460
- "Angle to rotate the torso in order to object realistic" " position of the heart in a torso"
558
+ "Angle to rotate the torso in order to object realistic position of the heart in a torso"
461
559
  ),
462
560
  show_default=True,
463
561
  )
@@ -886,3 +984,4 @@ app.add_command(biv_ellipsoid_torso)
886
984
  app.add_command(slab)
887
985
  app.add_command(slab_in_bath)
888
986
  app.add_command(gui)
987
+ app.add_command(ukb)
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import logging
2
3
  import shutil
3
4
  from dataclasses import dataclass, field
4
5
  from pathlib import Path
@@ -13,6 +14,8 @@ from packaging.version import Version
13
14
 
14
15
  from . import utils
15
16
 
17
+ logger = logging.getLogger(__name__)
18
+
16
19
 
17
20
  @dataclass # (frozen=True, slots=True)
18
21
  class Geometry:
@@ -139,15 +142,17 @@ class Geometry:
139
142
  @classmethod
140
143
  def from_folder(cls, comm: MPI.Intracomm, folder: str | Path) -> "Geometry":
141
144
  folder = Path(folder)
142
-
145
+ logger.info(f"Reading geometry from {folder}")
143
146
  # Read mesh
144
147
  if (folder / "mesh.xdmf").exists():
148
+ logger.debug("Reading mesh")
145
149
  mesh, tags = utils.read_mesh(comm=comm, filename=folder / "mesh.xdmf")
146
150
  else:
147
151
  raise ValueError("No mesh file found")
148
152
 
149
153
  # Read markers
150
154
  if (folder / "markers.json").exists():
155
+ logger.debug("Reading markers")
151
156
  if comm.rank == 0:
152
157
  markers = json.loads((folder / "markers.json").read_text())
153
158
  else:
@@ -168,10 +173,12 @@ class Geometry:
168
173
  functions = {}
169
174
  microstructure_path = folder / "microstructure.bp"
170
175
  if microstructure_path.exists():
176
+ logger.debug("Reading microstructure")
171
177
  # function_space = adios4dolfinx.read_attributes(
172
178
  # comm=MPI.COMM_WORLD, filename=microstructure_path, name="function_space"
173
179
  # )
174
180
  for name, el in microstructure.items():
181
+ logger.debug(f"Reading {name}")
175
182
  element = utils.array2element(el)
176
183
  V = dolfinx.fem.functionspace(mesh, element)
177
184
  f = dolfinx.fem.Function(V, name=name)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import json
3
5
  import math
@@ -7,9 +9,11 @@ from pathlib import Path
7
9
  from mpi4py import MPI
8
10
 
9
11
  import cardiac_geometries_core as cgc
12
+ import dolfinx
10
13
  from structlog import get_logger
11
14
 
12
15
  from . import utils
16
+ from .fibers.utils import save_microstructure
13
17
  from .geometry import Geometry
14
18
 
15
19
  meta = metadata("cardiac-geometriesx")
@@ -18,6 +22,132 @@ __version__ = meta["Version"]
18
22
  logger = get_logger()
19
23
 
20
24
 
25
+ def ukb(
26
+ outdir: str | Path,
27
+ mode: int = -1,
28
+ std: float = 1.5,
29
+ case: str = "ED",
30
+ char_length_max: float = 2.0,
31
+ char_length_min: float = 2.0,
32
+ fiber_angle_endo: float = 60,
33
+ fiber_angle_epi: float = -60,
34
+ fiber_space: str = "P_1",
35
+ comm: MPI.Comm = MPI.COMM_WORLD,
36
+ ) -> Geometry:
37
+ try:
38
+ import ukb.cli
39
+ except ImportError as e:
40
+ msg = (
41
+ "To create the UKB mesh you need to install the ukb package "
42
+ "which you can install with pip install ukb-atlas"
43
+ )
44
+ raise ImportError(msg) from e
45
+
46
+ if comm.rank == 0:
47
+ print(comm.rank)
48
+ ukb.cli.main(
49
+ [
50
+ str(outdir),
51
+ "--mode",
52
+ str(mode),
53
+ "--std",
54
+ str(std),
55
+ "--case",
56
+ case,
57
+ "--mesh",
58
+ "--char_length_max",
59
+ str(char_length_max),
60
+ "--char_length_min",
61
+ str(char_length_min),
62
+ ]
63
+ )
64
+ comm.barrier()
65
+ outdir = Path(outdir)
66
+ mesh_name = outdir / f"{case}.msh"
67
+
68
+ geometry = utils.gmsh2dolfin(comm=comm, msh_file=mesh_name)
69
+
70
+ if comm.rank == 0:
71
+ (outdir / "markers.json").write_text(
72
+ json.dumps(geometry.markers, default=utils.json_serial)
73
+ )
74
+ (outdir / "info.json").write_text(
75
+ json.dumps(
76
+ {
77
+ "mode": mode,
78
+ "std": std,
79
+ "case": case,
80
+ "char_length_max": char_length_max,
81
+ "char_length_min": char_length_min,
82
+ "fiber_angle_endo": fiber_angle_endo,
83
+ "fiber_angle_epi": fiber_angle_epi,
84
+ "fiber_space": fiber_space,
85
+ "cardiac_geometry_version": __version__,
86
+ "type": "ukb",
87
+ "timestamp": datetime.datetime.now().isoformat(),
88
+ }
89
+ )
90
+ )
91
+
92
+ try:
93
+ import ldrb
94
+ except ImportError:
95
+ msg = (
96
+ "To create fibers you need to install the ldrb package "
97
+ "which you can install with pip install fenicsx-ldrb"
98
+ )
99
+ raise ImportError(msg)
100
+
101
+ # base_marker = 3
102
+ # indices = []
103
+ # for k in ["PV", "TV", "AV", "MV"]:
104
+ # indices.append(geometry.ffun.find(geometry.markers[k][0]))
105
+ # indices = np.hstack(comm.allreduce(indices, op=MPI.SUM))
106
+ # values = np.full(len(indices), base_marker, dtype=np.int32)
107
+
108
+ markers = {
109
+ "lv": [geometry.markers["LV"][0]],
110
+ "rv": [geometry.markers["RV"][0]],
111
+ "epi": [geometry.markers["EPI"][0]],
112
+ "base": [
113
+ geometry.markers["PV"][0],
114
+ geometry.markers["TV"][0],
115
+ geometry.markers["AV"][0],
116
+ geometry.markers["MV"][0],
117
+ ],
118
+ }
119
+ system = ldrb.dolfinx_ldrb(
120
+ mesh=geometry.mesh,
121
+ ffun=geometry.ffun,
122
+ markers=markers,
123
+ alpha_endo_lv=fiber_angle_endo,
124
+ alpha_epi_lv=fiber_angle_epi,
125
+ beta_endo_lv=0,
126
+ beta_epi_lv=0,
127
+ fiber_space=fiber_space,
128
+ )
129
+
130
+ save_microstructure(
131
+ mesh=geometry.mesh,
132
+ functions=(system.f0, system.s0, system.n0),
133
+ outdir=outdir,
134
+ )
135
+
136
+ for k, v in system._asdict().items():
137
+ if v is None:
138
+ continue
139
+ if fiber_space.startswith("Q"):
140
+ # Cannot visualize Quadrature spaces yet
141
+ continue
142
+
143
+ logger.debug(f"Write {k}: {v}")
144
+ with dolfinx.io.VTXWriter(comm, outdir / f"{k}-viz.bp", [v], engine="BP4") as vtx:
145
+ vtx.write(0.0)
146
+
147
+ geo = Geometry.from_folder(comm=comm, folder=outdir)
148
+ return geo
149
+
150
+
21
151
  def biv_ellipsoid(
22
152
  outdir: str | Path,
23
153
  char_length: float = 0.5,
@@ -199,10 +329,11 @@ def biv_ellipsoid(
199
329
  beta_epi_lv=0,
200
330
  fiber_space=fiber_space,
201
331
  )
202
- from .fibers.utils import save_microstructure
203
332
 
204
333
  save_microstructure(
205
- mesh=geometry.mesh, functions=(system.f0, system.s0, system.n0), outdir=outdir
334
+ mesh=geometry.mesh,
335
+ functions=(system.f0, system.s0, system.n0),
336
+ outdir=outdir,
206
337
  )
207
338
 
208
339
  geo = Geometry.from_folder(comm=comm, folder=outdir)
@@ -422,6 +422,7 @@ def read_mesh(
422
422
  def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
423
423
  logger.debug(f"Convert file {msh_file} to dolfin")
424
424
  outdir = Path(msh_file).parent
425
+ outdir.mkdir(parents=True, exist_ok=True)
425
426
 
426
427
  if Version(dolfinx.__version__) >= Version("0.10.0"):
427
428
  mesh_data = dolfinx.io.gmshio.read_from_msh(comm=comm, filename=msh_file)
@@ -454,6 +455,7 @@ def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
454
455
 
455
456
  else:
456
457
  import gmsh
458
+
457
459
  # We could make this work in parallel in the future
458
460
 
459
461
  if comm.rank == rank:
@@ -482,19 +484,27 @@ def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
482
484
  with dolfinx.io.XDMFFile(comm, outdir / "mesh.xdmf", "w") as xdmf:
483
485
  xdmf.write_mesh(mesh)
484
486
  xdmf.write_meshtags(
485
- ct, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
487
+ ct,
488
+ mesh.geometry,
489
+ geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry",
486
490
  )
487
491
  mesh.topology.create_connectivity(2, 3)
488
492
  xdmf.write_meshtags(
489
- ft, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
493
+ ft,
494
+ mesh.geometry,
495
+ geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry",
490
496
  )
491
497
  mesh.topology.create_connectivity(1, 3)
492
498
  xdmf.write_meshtags(
493
- et, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
499
+ et,
500
+ mesh.geometry,
501
+ geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry",
494
502
  )
495
503
  mesh.topology.create_connectivity(0, 3)
496
504
  xdmf.write_meshtags(
497
- vt, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
505
+ vt,
506
+ mesh.geometry,
507
+ geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry",
498
508
  )
499
509
 
500
510
  return GMshGeometry(mesh, ct, ft, et, vt, markers)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: cardiac-geometriesx
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: A python library for cardiac geometries
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License: MIT
@@ -38,6 +38,8 @@ Provides-Extra: test
38
38
  Requires-Dist: pre-commit; extra == "test"
39
39
  Requires-Dist: pytest; extra == "test"
40
40
  Requires-Dist: pytest-cov; extra == "test"
41
+ Requires-Dist: ukb-atlas; extra == "test"
42
+ Requires-Dist: fenicsx-ldrb; extra == "test"
41
43
  Provides-Extra: gui
42
44
  Requires-Dist: streamlit; extra == "gui"
43
45
  Requires-Dist: stpyvista; extra == "gui"
@@ -35,3 +35,5 @@ fenicsx-ldrb
35
35
  pre-commit
36
36
  pytest
37
37
  pytest-cov
38
+ ukb-atlas
39
+ fenicsx-ldrb
@@ -58,3 +58,31 @@ def test_script_no_fibers(script, tmp_path: Path):
58
58
 
59
59
  geo = Geometry.from_folder(comm=comm, folder=path)
60
60
  assert geo.mesh.geometry.dim == 3
61
+
62
+
63
+ @pytest.mark.parametrize("case", ["ED", "ES"])
64
+ def test_ukb(tmp_path: Path, case: str):
65
+ runner = CliRunner()
66
+
67
+ comm = MPI.COMM_WORLD
68
+ path = comm.bcast(tmp_path, root=0)
69
+
70
+ res = runner.invoke(
71
+ cli.ukb,
72
+ [
73
+ path.as_posix(),
74
+ "--case",
75
+ case,
76
+ "--char-length-max",
77
+ "10.0",
78
+ "--char-length-min",
79
+ "10.0",
80
+ ],
81
+ )
82
+ assert res.exit_code == 0
83
+ assert path.is_dir()
84
+
85
+ assert (path / "mesh.xdmf").exists()
86
+ assert (path / f"{case}.msh").exists()
87
+ geo = Geometry.from_folder(comm=comm, folder=path)
88
+ assert geo.mesh.geometry.dim == 3