Skip to content

Commit 6e43848

Browse files
authored
Merge pull request #3 from scientificcomputing/submesh
Change logic for converting mesh from wildmeshing to dolfinx
2 parents b4f0e1e + c12f04a commit 6e43848

4 files changed

Lines changed: 152 additions & 64 deletions

File tree

examples/cli.ipynb

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@
283283
"id": "27",
284284
"metadata": {},
285285
"source": [
286-
"Let us have a look at the mesh"
286+
"Now lets convert the mesh to dolfinx"
287287
]
288288
},
289289
{
@@ -292,6 +292,34 @@
292292
"id": "28",
293293
"metadata": {},
294294
"outputs": [],
295+
"source": [
296+
"!mri2mesh mesh convert idealized-brain"
297+
]
298+
},
299+
{
300+
"cell_type": "code",
301+
"execution_count": null,
302+
"id": "29",
303+
"metadata": {},
304+
"outputs": [],
305+
"source": [
306+
"!ls idealized-brain/"
307+
]
308+
},
309+
{
310+
"cell_type": "markdown",
311+
"id": "30",
312+
"metadata": {},
313+
"source": [
314+
"Let us have a look at the mesh"
315+
]
316+
},
317+
{
318+
"cell_type": "code",
319+
"execution_count": null,
320+
"id": "31",
321+
"metadata": {},
322+
"outputs": [],
295323
"source": [
296324
"from mpi4py import MPI\n",
297325
"import dolfinx\n",
@@ -304,7 +332,7 @@
304332
{
305333
"cell_type": "code",
306334
"execution_count": null,
307-
"id": "29",
335+
"id": "32",
308336
"metadata": {},
309337
"outputs": [],
310338
"source": [
@@ -326,7 +354,7 @@
326354
{
327355
"cell_type": "code",
328356
"execution_count": null,
329-
"id": "30",
357+
"id": "33",
330358
"metadata": {},
331359
"outputs": [],
332360
"source": []
@@ -348,7 +376,7 @@
348376
"name": "python",
349377
"nbconvert_exporter": "python",
350378
"pygments_lexer": "ipython3",
351-
"version": "3.12.9"
379+
"version": "3.12.11"
352380
}
353381
},
354382
"nbformat": 4,

src/mri2mesh/mesh/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def add_mesh_parser(parser: argparse.ArgumentParser) -> None:
2727

2828
convert_parser = subparsers.add_parser("convert", help="Convert mesh to dolfinx")
2929
convert_parser.add_argument("mesh_dir", type=Path, help="Directory containing mesh files")
30+
convert_parser.add_argument(
31+
"--extract-facet-tags", action="store_true", help="Extract facet tags"
32+
)
33+
convert_parser.add_argument("--extract-submesh", action="store_true", help="Extract submesh")
3034

3135
idealized_parser = subparsers.add_parser(
3236
"idealized",
@@ -41,12 +45,7 @@ def dispatch(command, args: dict[str, typing.Any]) -> int:
4145
basic.generate_sameple_config(**args)
4246

4347
elif command == "create":
44-
mesh_dir = basic.create_mesh_from_config(**args)
45-
try:
46-
basic.convert_mesh_dolfinx(mesh_dir=mesh_dir)
47-
except ImportError:
48-
logger.debug("dolfinx not installed, skipping conversion to dolfinx")
49-
48+
basic.create_mesh_from_config(**args)
5049
elif command == "convert":
5150
basic.convert_mesh_dolfinx(**args)
5251

src/mri2mesh/mesh/basic.py

Lines changed: 111 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -156,83 +156,149 @@ def create_mesh(
156156
manifold_surface=manifold_surface,
157157
correct_surface_orientation=True,
158158
)
159-
coords, connections = tetra.get_tracked_surfaces()
160159

161160
tetra_mesh = meshio.Mesh(
162161
point_array, [("tetra", cell_array)], cell_data={"cell_tags": [marker.ravel()]}
163162
)
164-
tetra_mesh_pv = pv.from_meshio(tetra_mesh).clean()
163+
164+
tetra_mesh_pv = pv.from_meshio(tetra_mesh)
165165
pv.save_meshio(outdir / "tetra_mesh.xdmf", tetra_mesh_pv)
166166

167-
for i, coord in enumerate(coords):
168-
np.save(outdir / f"coords_{i}.npy", coord)
167+
np.save(outdir / "point_array.npy", point_array)
168+
np.save(outdir / "cell_array.npy", cell_array)
169+
np.save(outdir / "marker.npy", marker)
170+
171+
# coords, connections = tetra.get_tracked_surfaces()
172+
# for i, coord in enumerate(coords):
173+
# np.save(outdir / f"coords_{i}.npy", coord)
169174

170-
for i, conn in enumerate(connections):
171-
np.save(outdir / f"connections_{i}.npy", conn)
175+
# for i, conn in enumerate(connections):
176+
# np.save(outdir / f"connections_{i}.npy", conn)
172177

178+
# cell_tags.find
179+
# Compute incident with facets
180+
# Intersection exterior facets
173181

174-
def convert_mesh_dolfinx(mesh_dir: Path):
182+
183+
def convert_mesh_dolfinx(
184+
mesh_dir: Path, extract_facet_tags: bool = False, extract_submesh: bool = False
185+
):
175186
logger.info("Converting mesh to dolfinx in %s", mesh_dir)
176187
from mpi4py import MPI
177-
from scipy.spatial.distance import cdist
178188
import dolfinx
189+
import basix
190+
import ufl
179191

180-
threshold = 1.0
181-
fdim = 2
192+
point_array = np.load(mesh_dir / "point_array.npy")
193+
cell_array = np.load(mesh_dir / "cell_array.npy")
194+
marker = np.load(mesh_dir / "marker.npy")
182195

183-
coords = []
184-
for path in sorted(mesh_dir.glob("coords_*.npy"), key=lambda x: int(x.stem.split("_")[-1])):
185-
coords.append(np.load(path))
186-
logger.debug(f"Found {len(coords)} coordinates")
187-
188-
connections = []
189-
for path in sorted(
190-
mesh_dir.glob("connections_*.npy"), key=lambda x: int(x.stem.split("_")[-1])
191-
):
192-
connections.append(np.load(path))
193-
logger.debug(f"Found {len(connections)} connections")
196+
comm = MPI.COMM_WORLD
197+
mesh = dolfinx.mesh.create_mesh(
198+
comm,
199+
cell_array.astype(np.int64),
200+
point_array,
201+
ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(3,))),
202+
)
203+
tdim = mesh.topology.dim
204+
fdim = tdim - 1
205+
local_entities, local_values = dolfinx.io.gmshio.distribute_entity_data(
206+
mesh,
207+
tdim,
208+
cell_array.astype(np.int64),
209+
marker.flatten().astype(np.int32),
210+
)
211+
adj = dolfinx.graph.adjacencylist(local_entities)
212+
cell_tags = dolfinx.mesh.meshtags_from_entities(
213+
mesh,
214+
tdim,
215+
adj,
216+
local_values.astype(np.int32, copy=False),
217+
)
218+
cell_tags.name = "cell_tags"
219+
if not extract_facet_tags:
220+
logger.debug("Save files")
221+
with dolfinx.io.XDMFFile(comm, mesh_dir / "mesh.xdmf", "w") as xdmf:
222+
xdmf.write_mesh(mesh)
223+
xdmf.write_meshtags(cell_tags, mesh.geometry)
194224

195-
assert len(connections) == len(coords)
225+
return
196226

197-
logger.debug("Loading mesh")
198-
comm = MPI.COMM_WORLD
199-
with dolfinx.io.XDMFFile(comm, mesh_dir / "tetra_mesh.xdmf", "r") as xdmf:
200-
mesh = xdmf.read_mesh(name="Grid")
201-
cell_tags = xdmf.read_meshtags(mesh, name="Grid")
227+
mesh.topology.create_connectivity(tdim - 1, tdim)
202228

203-
logger.debug("Mesh loaded")
229+
# FIXME: Here we just add hard coded values for now. This should be fixed in the future.
204230

205-
facets = []
231+
entities = []
206232
values = []
207-
for i, coord in enumerate(coords, start=1):
208-
logger.debug(f"Processing coord {i}")
233+
# 1 = Parenchyma
234+
PARENCHYMA = 1
235+
236+
cells = cell_tags.find(PARENCHYMA)
237+
exterior_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
238+
incident_facets = dolfinx.mesh.compute_incident_entities(mesh.topology, cells, tdim, tdim - 1)
239+
exterior_facets_marker = np.intersect1d(incident_facets, exterior_facets)
240+
values.append(np.full(exterior_facets_marker.shape[0], PARENCHYMA, dtype=np.int32))
241+
entities.append(exterior_facets_marker)
209242

210-
def locator(x):
211-
# Find the distance to all coordinates
212-
distances = cdist(x.T, coord)
213-
# And return True is they are close
214-
return np.any(distances < threshold, axis=1)
243+
VENTRICLES = 3
244+
all_cell_tags = np.unique(cell_tags.values)
245+
cell_not_ventricles = np.setdiff1d(all_cell_tags, [VENTRICLES])
246+
import scifem
215247

216-
f = dolfinx.mesh.locate_entities_boundary(mesh, dim=fdim, marker=locator)
217-
v = np.full(f.shape[0], i, dtype=np.int32)
218-
facets.append(f)
219-
values.append(v)
248+
interface_entities = scifem.mesh.find_interface(cell_tags, [VENTRICLES], cell_not_ventricles)
249+
entities.append(interface_entities)
250+
values.append(np.full(interface_entities.shape[0], VENTRICLES, dtype=np.int32))
220251

221-
logger.debug("Create meshtags")
222252
facet_tags = dolfinx.mesh.meshtags(
223253
mesh,
224254
fdim,
225-
np.hstack(facets),
255+
np.hstack(entities),
226256
np.hstack(values),
227257
)
228258
facet_tags.name = "facet_tags"
229-
cell_tags.name = "cell_tags"
259+
230260
mesh.name = "mesh"
231261

232262
logger.debug("Save files")
233-
with dolfinx.io.XDMFFile(comm, mesh_dir / "mesh.xdmf", "w") as xdmf:
263+
meshname = "mesh_full.xdmf" if extract_submesh else "mesh.xdmf"
264+
with dolfinx.io.XDMFFile(comm, mesh_dir / meshname, "w") as xdmf:
234265
xdmf.write_mesh(mesh)
235266
xdmf.write_meshtags(facet_tags, mesh.geometry)
236267
xdmf.write_meshtags(cell_tags, mesh.geometry)
237268

238-
logger.info("Mesh saved to %s", mesh_dir / "mesh.xdmf")
269+
logger.info("Mesh saved to %s", mesh_dir / meshname)
270+
271+
if not extract_submesh:
272+
return
273+
submesh_data = scifem.mesh.extract_submesh(mesh, cell_tags, cell_not_ventricles)
274+
275+
# Transfer the facet tags to the submesh
276+
submesh_data.domain.topology.create_connectivity(2, 3)
277+
facet_tags_submesh, sub_to_parent_entity_map = scifem.mesh.transfer_meshtags_to_submesh(
278+
facet_tags,
279+
# geo.facet_tags, # If available
280+
submesh_data.domain,
281+
vertex_to_parent=submesh_data.vertex_map,
282+
cell_to_parent=submesh_data.cell_map,
283+
)
284+
285+
np.save(mesh_dir / "sub_to_parent_entity_map.npy", sub_to_parent_entity_map)
286+
np.save(mesh_dir / "vertex_map.npy", submesh_data.vertex_map)
287+
np.save(mesh_dir / "cell_map.npy", submesh_data.cell_map)
288+
289+
# Remove overflow values
290+
keep_indices = facet_tags_submesh.values > 0
291+
facet_tags_new = dolfinx.mesh.meshtags(
292+
submesh_data.domain,
293+
fdim,
294+
facet_tags_submesh.indices[keep_indices],
295+
facet_tags_submesh.values[keep_indices],
296+
)
297+
298+
facet_tags_new.name = "facet_tags"
299+
submesh_data.cell_tag.name = "cell_tags"
300+
301+
with dolfinx.io.XDMFFile(comm, mesh_dir / "mesh.xdmf", "w") as xdmf:
302+
xdmf.write_mesh(submesh_data.domain)
303+
xdmf.write_meshtags(submesh_data.cell_tag, submesh_data.domain.geometry)
304+
xdmf.write_meshtags(facet_tags_new, submesh_data.domain.geometry)

tests/test_cli.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,9 @@ def test_labels_cli(name, capsys):
1414
def test_mesh_idealized_cli(tmp_path):
1515
cli.main(["mesh", "idealized", "-o", str(tmp_path)])
1616
for name in [
17-
"connections_3.npy",
18-
"coords_0.npy",
19-
"coords_1.npy",
20-
"connections_2.npy",
21-
"connections_0.npy",
22-
"coords_3.npy",
23-
"coords_2.npy",
24-
"connections_1.npy",
17+
"point_array.npy",
18+
"cell_array.npy",
19+
"marker.npy",
2520
"mesh.h5",
2621
"ventricles.ply",
2722
"skull.ply",
@@ -34,4 +29,4 @@ def test_mesh_idealized_cli(tmp_path):
3429
"V34.ply",
3530
"mesh_params.json",
3631
]:
37-
assert (tmp_path / name).exists()
32+
assert (tmp_path / name).exists(), name

0 commit comments

Comments
 (0)