cardiac-geometriesx 0.4.7__tar.gz → 0.5.0__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 (26) hide show
  1. {cardiac_geometriesx-0.4.7/src/cardiac_geometriesx.egg-info → cardiac_geometriesx-0.5.0}/PKG-INFO +1 -1
  2. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/pyproject.toml +2 -2
  3. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/cli.py +97 -4
  4. cardiac_geometriesx-0.5.0/src/cardiac_geometries/fibers/__init__.py +4 -0
  5. cardiac_geometriesx-0.5.0/src/cardiac_geometries/fibers/cylinder.py +124 -0
  6. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/fibers/utils.py +10 -4
  7. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/geometry.py +12 -5
  8. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/mesh.py +110 -0
  9. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/utils.py +9 -2
  10. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0/src/cardiac_geometriesx.egg-info}/PKG-INFO +1 -1
  11. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/SOURCES.txt +1 -0
  12. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/tests/test_cli.py +2 -1
  13. cardiac_geometriesx-0.4.7/src/cardiac_geometries/fibers/__init__.py +0 -4
  14. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/LICENSE +0 -0
  15. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/README.md +0 -0
  16. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/setup.cfg +0 -0
  17. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/__init__.py +0 -0
  18. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/fibers/lv_ellipsoid.py +0 -0
  19. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/fibers/slab.py +0 -0
  20. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometries/gui.py +0 -0
  21. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/dependency_links.txt +0 -0
  22. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/entry_points.txt +0 -0
  23. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/not-zip-safe +0 -0
  24. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/requires.txt +0 -0
  25. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/src/cardiac_geometriesx.egg-info/top_level.txt +0 -0
  26. {cardiac_geometriesx-0.4.7 → cardiac_geometriesx-0.5.0}/tests/test_save_load.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardiac-geometriesx
3
- Version: 0.4.7
3
+ Version: 0.5.0
4
4
  Summary: A python library for cardiac geometries
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cardiac-geometriesx"
7
- version = "0.4.7"
7
+ version = "0.5.0"
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.4.7"
180
+ current_version = "0.5.0"
181
181
 
182
182
 
183
183
  [[tool.bumpversion.files]]
@@ -209,7 +209,6 @@ def ukb(
209
209
  "--create-fibers",
210
210
  default=False,
211
211
  is_flag=True,
212
- type=bool,
213
212
  help="If True create analytic fibers",
214
213
  show_default=True,
215
214
  )
@@ -431,7 +430,6 @@ def lv_ellipsoid(
431
430
  "--create-fibers",
432
431
  default=False,
433
432
  is_flag=True,
434
- type=bool,
435
433
  help="If True create analytic fibers",
436
434
  show_default=True,
437
435
  )
@@ -699,7 +697,6 @@ def biv_ellipsoid(
699
697
  "--create-fibers",
700
698
  default=False,
701
699
  is_flag=True,
702
- type=bool,
703
700
  help="If True create analytic fibers",
704
701
  show_default=True,
705
702
  )
@@ -837,7 +834,6 @@ def biv_ellipsoid_torso(
837
834
  "--create-fibers",
838
835
  default=False,
839
836
  is_flag=True,
840
- type=bool,
841
837
  help="If True create analytic fibers",
842
838
  show_default=True,
843
839
  )
@@ -977,6 +973,102 @@ def slab_in_bath(
977
973
  geo.save(outdir / "slab_in_bath.bp")
978
974
 
979
975
 
976
+ @click.command(help="Create BiV ellipsoidal geometry")
977
+ @click.argument(
978
+ "outdir",
979
+ required=True,
980
+ type=click.Path(
981
+ file_okay=False,
982
+ dir_okay=True,
983
+ writable=True,
984
+ readable=True,
985
+ resolve_path=True,
986
+ ),
987
+ )
988
+ @click.option(
989
+ "--char-length",
990
+ default=10.0,
991
+ type=float,
992
+ help="Characteristic length of mesh",
993
+ show_default=True,
994
+ )
995
+ @click.option(
996
+ "--r-inner",
997
+ default=10.0,
998
+ type=float,
999
+ help="Inner radius of the cylinder",
1000
+ show_default=True,
1001
+ )
1002
+ @click.option(
1003
+ "--r-outer",
1004
+ default=20.0,
1005
+ type=float,
1006
+ help="Outer radius of the cylinder",
1007
+ show_default=True,
1008
+ )
1009
+ @click.option(
1010
+ "--height",
1011
+ default=40.0,
1012
+ type=float,
1013
+ help="Height of the cylinder",
1014
+ show_default=True,
1015
+ )
1016
+ @click.option(
1017
+ "--create-fibers",
1018
+ default=False,
1019
+ is_flag=True,
1020
+ help="If True create analytic fibers",
1021
+ show_default=True,
1022
+ )
1023
+ @click.option(
1024
+ "--fiber-angle-endo",
1025
+ default=-60,
1026
+ type=float,
1027
+ help="Angle for the endocardium",
1028
+ show_default=True,
1029
+ )
1030
+ @click.option(
1031
+ "--fiber-angle-epi",
1032
+ default=+60,
1033
+ type=float,
1034
+ help="Angle for the epicardium",
1035
+ show_default=True,
1036
+ )
1037
+ @click.option(
1038
+ "--fiber-space",
1039
+ default="P_1",
1040
+ type=str,
1041
+ help="Function space for fibers of the form family_degree",
1042
+ show_default=True,
1043
+ )
1044
+ def cylinder(
1045
+ outdir: Path,
1046
+ char_length: float = 10.0,
1047
+ r_inner: float = 10.0,
1048
+ r_outer: float = 20.0,
1049
+ height: float = 40.0,
1050
+ create_fibers: bool = False,
1051
+ fiber_angle_endo: float = -60,
1052
+ fiber_angle_epi: float = +60,
1053
+ fiber_space: str = "P_1",
1054
+ ):
1055
+ outdir = Path(outdir)
1056
+ outdir.mkdir(exist_ok=True)
1057
+
1058
+ geo = mesh.cylinder(
1059
+ outdir=outdir,
1060
+ r_inner=r_inner,
1061
+ r_outer=r_outer,
1062
+ height=height,
1063
+ char_length=char_length,
1064
+ create_fibers=create_fibers,
1065
+ fiber_angle_endo=fiber_angle_endo,
1066
+ fiber_angle_epi=fiber_angle_epi,
1067
+ fiber_space=fiber_space,
1068
+ )
1069
+ geo.save(outdir / "cylinder.bp")
1070
+
1071
+
980
1072
  @click.command("gui")
981
1073
  def gui():
982
1074
  # Make sure we can import the required packages
@@ -995,3 +1087,4 @@ app.add_command(slab)
995
1087
  app.add_command(slab_in_bath)
996
1088
  app.add_command(gui)
997
1089
  app.add_command(ukb)
1090
+ app.add_command(cylinder)
@@ -0,0 +1,4 @@
1
+ from . import cylinder, lv_ellipsoid, slab, utils
2
+ from .utils import Microstructure
3
+
4
+ __all__ = ["lv_ellipsoid", "slab", "cylinder", "utils", "Microstructure"]
@@ -0,0 +1,124 @@
1
+ from pathlib import Path
2
+
3
+ import dolfinx
4
+ import numpy as np
5
+
6
+ from ..utils import space_from_string
7
+ from . import utils
8
+
9
+
10
+ def compute_system(
11
+ mesh: dolfinx.mesh.Mesh,
12
+ alpha_endo: float = -60,
13
+ alpha_epi: float = 60,
14
+ r_inner: float = 10.0,
15
+ r_outer: float = 20.0,
16
+ function_space: str = "P_1",
17
+ **kwargs,
18
+ ) -> utils.Microstructure:
19
+ """Compute ldrb system for cylinder, assuming linear
20
+ angle between endo and epi
21
+
22
+ Parameters
23
+ ----------
24
+ mesh : dolfinx.mesh.Mesh
25
+ A cylinder mesh
26
+ alpha_endo : float, optional
27
+ Angle on endocardium, by default -60
28
+ alpha_epi : float, optional
29
+ Angle on epicardium, by default 60
30
+ r_inner : float, optional
31
+ Inner radius, by default 10.0
32
+ r_outer : float, optional
33
+ Outer radius, by default 20.0
34
+ function_space : str, optional
35
+ Function space to interpolate the fibers, by default "P_1"
36
+ Returns
37
+ -------
38
+ Microstructure
39
+ Tuple with fiber, sheet and sheet normal
40
+ """
41
+
42
+ Vv = space_from_string(function_space, mesh, dim=3)
43
+
44
+ x, y, z = Vv.tabulate_dof_coordinates().T
45
+ r = np.sqrt(x**2 + y**2)
46
+
47
+ # Circumferential direction
48
+ e_r = np.array([x / r, y / r, np.zeros_like(r)])
49
+ e_theta = np.array([-y / r, x / r, np.zeros_like(r)])
50
+ e_z = np.array([np.zeros_like(r), np.zeros_like(r), np.ones_like(r)])
51
+
52
+ n0 = e_r
53
+ alpha = (alpha_endo + (alpha_epi - alpha_endo) * (r - r_inner) / (r_outer - r_inner)) * (
54
+ np.pi / 180
55
+ )
56
+
57
+ f0 = e_theta * np.cos(alpha) - e_z * np.sin(alpha)
58
+ s0 = e_theta * np.sin(alpha) + e_z * np.cos(alpha)
59
+
60
+ fiber = dolfinx.fem.Function(Vv)
61
+ norm_f = np.linalg.norm(f0, axis=0)
62
+ fiber.x.array[:] = (f0 / norm_f).T.reshape(-1)
63
+ fiber.name = "f0"
64
+
65
+ sheet = dolfinx.fem.Function(Vv)
66
+ sheet.x.array[:] = s0.T.reshape(-1)
67
+ sheet.name = "s0"
68
+
69
+ sheet_normal = dolfinx.fem.Function(Vv)
70
+ sheet_normal.x.array[:] = n0.T.reshape(-1)
71
+ sheet_normal.name = "n0"
72
+
73
+ return utils.Microstructure(f0=fiber, s0=sheet, n0=sheet_normal)
74
+
75
+
76
+ def create_microstructure(
77
+ mesh: dolfinx.mesh.Mesh,
78
+ alpha_endo: float,
79
+ alpha_epi: float,
80
+ r_inner: float,
81
+ r_outer: float,
82
+ function_space: str = "P_1",
83
+ outdir: str | Path | None = None,
84
+ ) -> utils.Microstructure:
85
+ """Generate microstructure for cylinder
86
+
87
+ Parameters
88
+ ----------
89
+ mesh : dolfinx.mesh.Mesh
90
+ A cylinder mesh
91
+ alpha_endo : float
92
+ Angle on the endocardium
93
+ alpha_epi : float
94
+ Angle on the epicardium
95
+ r_inner : float
96
+ Inner radius
97
+ r_outer : float
98
+ Outer radius
99
+ function_space : str
100
+ Function space to interpolate the fibers, by default P_1
101
+ outdir : Optional[Union[str, Path]], optional
102
+ Output directory to store the results, by default None.
103
+ If no output directory is specified the results will not be stored,
104
+ but only returned.
105
+
106
+ Returns
107
+ -------
108
+ Microstructure
109
+ Tuple with fiber, sheet and sheet normal
110
+ """
111
+
112
+ system = compute_system(
113
+ mesh=mesh,
114
+ function_space=function_space,
115
+ r_inner=r_inner,
116
+ r_outer=r_outer,
117
+ alpha_endo=alpha_endo,
118
+ alpha_epi=alpha_epi,
119
+ )
120
+
121
+ if outdir is not None:
122
+ utils.save_microstructure(mesh, system, outdir)
123
+
124
+ return system
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import shutil
2
3
  from pathlib import Path
3
4
  from typing import NamedTuple, Sequence
4
5
 
@@ -32,20 +33,25 @@ def save_microstructure(
32
33
  if functions[0].function_space.ufl_element().family_name == "quadrature":
33
34
  from scifem.xdmf import XDMFFile
34
35
 
35
- with XDMFFile(Path(outdir) / "microstructure-viz.xdmf", functions) as xdmf:
36
+ fname = Path(outdir) / "microstructure-viz.xdmf"
37
+ fname.unlink(missing_ok=True)
38
+ fname.with_suffix(".h5").unlink(missing_ok=True)
39
+ with XDMFFile(fname, functions) as xdmf:
36
40
  xdmf.write(0.0)
37
41
 
38
42
  else:
43
+ fname = Path(outdir) / "microstructure-viz.bp"
44
+ shutil.rmtree(fname, ignore_errors=True)
39
45
  try:
40
- with dolfinx.io.VTXWriter(
41
- mesh.comm, Path(outdir) / "microstructure-viz.bp", functions, engine="BP4"
42
- ) as file:
46
+ with dolfinx.io.VTXWriter(mesh.comm, fname, functions, engine="BP4") as file:
43
47
  file.write(0.0)
44
48
  except RuntimeError as ex:
45
49
  print(f"Failed to write microstructure: {ex}")
46
50
 
47
51
  # Save with proper function space
52
+
48
53
  filename = Path(outdir) / "microstructure.bp"
54
+ shutil.rmtree(filename, ignore_errors=True)
49
55
  for function in functions:
50
56
  adios4dolfinx.write_function(u=function, filename=filename)
51
57
 
@@ -98,7 +98,12 @@ class Geometry:
98
98
  self.mesh.comm.barrier()
99
99
 
100
100
  @classmethod
101
- def from_file(cls, comm: MPI.Intracomm, path: str | Path) -> "Geometry":
101
+ def from_file(
102
+ cls,
103
+ comm: MPI.Intracomm,
104
+ path: str | Path,
105
+ function_space_data: dict[str, np.ndarray] | None = None,
106
+ ) -> "Geometry":
102
107
  path = Path(path)
103
108
 
104
109
  mesh = adios4dolfinx.read_mesh(comm=comm, filename=path)
@@ -118,10 +123,12 @@ class Geometry:
118
123
  tags[name] = None
119
124
 
120
125
  functions = {}
121
- function_space = adios4dolfinx.read_attributes(
122
- comm=comm, filename=path, name="function_space"
123
- )
124
- for name, el in function_space.items():
126
+ if function_space_data is None:
127
+ function_space_data = adios4dolfinx.read_attributes(
128
+ comm=comm, filename=path, name="function_space"
129
+ )
130
+ assert isinstance(function_space_data, dict), "function_space_data must be a dictionary"
131
+ for name, el in function_space_data.items():
125
132
  element = utils.array2element(el)
126
133
  V = dolfinx.fem.functionspace(mesh, element)
127
134
  f = dolfinx.fem.Function(V, name=name)
@@ -910,3 +910,113 @@ def slab_in_bath(
910
910
  geo = Geometry.from_folder(comm=comm, folder=outdir)
911
911
 
912
912
  return geo
913
+
914
+
915
+ def cylinder(
916
+ outdir: Path | str,
917
+ r_inner: float = 10.0,
918
+ r_outer: float = 20.0,
919
+ height: float = 40.0,
920
+ char_length: float = 10.0,
921
+ create_fibers: bool = False,
922
+ fiber_angle_endo: float = 60,
923
+ fiber_angle_epi: float = -60,
924
+ fiber_space: str = "P_1",
925
+ aha: bool = True,
926
+ verbose: bool = False,
927
+ comm: MPI.Comm = MPI.COMM_WORLD,
928
+ ) -> Geometry:
929
+ """Create an LV ellipsoidal geometry
930
+
931
+ Parameters
932
+ ----------
933
+ outdir : Optional[Path], optional
934
+ Directory where to save the results.
935
+ r_inner : float, optional
936
+ Radius on the endocardium layer, by default 10.0
937
+ r_outer : float, optional
938
+ Radius on the epicardium layer, by default 20.0
939
+ height : float, optional
940
+ Longest radius on the endocardium layer, by default 10.0
941
+ char_length : float, optional
942
+ Characteristic length of mesh, by default 10.0
943
+ create_fibers : bool, optional
944
+ If True create analytic fibers, by default False
945
+ fiber_angle_endo : float, optional
946
+ Angle for the endocardium, by default 60
947
+ fiber_angle_epi : float, optional
948
+ Angle for the epicardium, by default -60
949
+ fiber_space : str, optional
950
+ Function space for fibers of the form family_degree, by default "P_1"
951
+ aha : bool, optional
952
+ If True create 17-segment AHA regions
953
+ verbose : bool, optional
954
+ If True print information from gmsh, by default False
955
+ comm : MPI.Comm, optional
956
+ MPI communicator, by default MPI.COMM_WORLD
957
+
958
+ Returns
959
+ -------
960
+ cardiac_geometries.geometry.Geometry
961
+ A Geometry with the mesh, markers, markers functions and fibers.
962
+
963
+ """
964
+
965
+ outdir = Path(outdir)
966
+ mesh_name = outdir / "cylinder.msh"
967
+ if comm.rank == 0:
968
+ outdir.mkdir(exist_ok=True, parents=True)
969
+
970
+ with open(outdir / "info.json", "w") as f:
971
+ json.dump(
972
+ {
973
+ "r_inner": r_inner,
974
+ "r_outer": r_outer,
975
+ "height": height,
976
+ "char_length": char_length,
977
+ "create_fibers": create_fibers,
978
+ "fibers_angle_endo": fiber_angle_endo,
979
+ "fibers_angle_epi": fiber_angle_epi,
980
+ "fiber_space": fiber_space,
981
+ "aha": aha,
982
+ # "mesh_type": MeshTypes.lv_ellipsoid.value,
983
+ "cardiac_geometry_version": __version__,
984
+ "timestamp": datetime.datetime.now().isoformat(),
985
+ },
986
+ f,
987
+ indent=2,
988
+ default=utils.json_serial,
989
+ )
990
+
991
+ cgc.cylinder(
992
+ inner_radius=r_inner,
993
+ outer_radius=r_outer,
994
+ height=height,
995
+ mesh_name=mesh_name.as_posix(),
996
+ char_length=char_length,
997
+ verbose=verbose,
998
+ )
999
+ comm.barrier()
1000
+
1001
+ geometry = utils.gmsh2dolfin(comm=comm, msh_file=mesh_name)
1002
+
1003
+ if comm.rank == 0:
1004
+ with open(outdir / "markers.json", "w") as f:
1005
+ json.dump(geometry.markers, f, default=utils.json_serial)
1006
+
1007
+ if create_fibers:
1008
+ from .fibers.cylinder import create_microstructure
1009
+
1010
+ create_microstructure(
1011
+ mesh=geometry.mesh,
1012
+ function_space=fiber_space,
1013
+ r_inner=r_inner,
1014
+ r_outer=r_outer,
1015
+ alpha_endo=fiber_angle_endo,
1016
+ alpha_epi=fiber_angle_epi,
1017
+ outdir=outdir,
1018
+ )
1019
+
1020
+ geo = Geometry.from_folder(comm=comm, folder=outdir)
1021
+
1022
+ return geo
@@ -440,12 +440,19 @@ def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
440
440
  ft = dolfinx.mesh.meshtags(
441
441
  mesh, tdim - 1, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
442
442
  )
443
- et = mesh_data.edge_tags
443
+
444
+ if hasattr(mesh_data, "edge_tags"):
445
+ et = mesh_data.edge_tags
446
+ else:
447
+ et = mesh_data.ridge_tags
444
448
  if et is None:
445
449
  et = dolfinx.mesh.meshtags(
446
450
  mesh, tdim - 2, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
447
451
  )
448
- vt = mesh_data.vertex_tags
452
+ if hasattr(mesh_data, "vertex_tags"):
453
+ vt = mesh_data.vertex_tags
454
+ else:
455
+ vt = mesh_data.peak_tags
449
456
  if vt is None:
450
457
  vt = dolfinx.mesh.meshtags(
451
458
  mesh, tdim - 3, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardiac-geometriesx
3
- Version: 0.4.7
3
+ Version: 0.5.0
4
4
  Summary: A python library for cardiac geometries
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License: MIT
@@ -8,6 +8,7 @@ src/cardiac_geometries/gui.py
8
8
  src/cardiac_geometries/mesh.py
9
9
  src/cardiac_geometries/utils.py
10
10
  src/cardiac_geometries/fibers/__init__.py
11
+ src/cardiac_geometries/fibers/cylinder.py
11
12
  src/cardiac_geometries/fibers/lv_ellipsoid.py
12
13
  src/cardiac_geometries/fibers/slab.py
13
14
  src/cardiac_geometries/fibers/utils.py
@@ -13,8 +13,9 @@ from cardiac_geometries import Geometry, cli
13
13
  [
14
14
  cli.slab,
15
15
  cli.lv_ellipsoid,
16
+ cli.cylinder,
16
17
  ],
17
- ids=["slab", "lv_ellipsoid"],
18
+ ids=["slab", "lv_ellipsoid", "cylinder"],
18
19
  )
19
20
  @pytest.mark.parametrize("fiber_space", [None, "P_1", "P_2", "Quadrature_2", "DG_1"])
20
21
  def test_script(fiber_space, script, tmp_path: Path):
@@ -1,4 +0,0 @@
1
- from . import lv_ellipsoid, slab, utils
2
- from .utils import Microstructure
3
-
4
- __all__ = ["lv_ellipsoid", "slab", "utils", "Microstructure"]