Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions utilities/fiber_generation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SV-fibergen
Python + svMultiPhysics codes for fiber generation. Two methods are implemented:
* Bayer et al. (2012). [link](https://doi.org/10.1007/s10439-012-0593-5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any validation we can do? I know you were looking into lifex, were you able to get that to work with your example data? It would be great to show that this code produces the same fiber field as lifex

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aabrown100-git lifex-fibers does not (as far as I could find) directly support biventricular fiber generation. My guess is that they use the code to get LV/RV fibers and then use some kind of interpolation (probably bislerp) to get the BV fibers.

The way I usually check the code is to, after generating the fibers, calculate the angle between the fiber direction and the circumferential vector. These should match the alpha/beta values. The other check is to check that the alpha/beta angle is changing linearly with the transmural coordinate. Will it be okay to add a VALIDATION.md showing these checks?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure a validation document sounds good!

* Doste et al. (2018). [link](https://doi.org/10.1002/cnm.3185)

### Examples
The `main_bayer.py` and `main_doste.py` are scripts to run both methods in the geometry described in the `example/truncated` and `example/ot` folders respectively.

#### Bayer results (fiber, sheet, sheet-normal)
<img src="example/truncated/bayer_fiber.png" alt="Fiber direction for truncated BiV (Bayer)" width="500" />
<img src="example/truncated/bayer_sheet.png" alt="Sheet direction for truncated BiV (Bayer)" width="500" />
<img src="example/truncated/bayer_sheetnormal.png" alt="Sheet-normal direction for truncated BiV (Bayer)" width="500" />

#### Doste results (fiber, sheet, sheet-normal)
<img src="example/ot/doste_fiber.png" alt="Fiber direction for BiV with OT (Doste)" width="500" />
<img src="example/ot/doste_sheet.png" alt="Sheet direction for BiV with OT (Doste)" width="500" />
<img src="example/ot/doste_sheetnormal.png" alt="Sheet-normal direction for BiV with OT (Doste)" width="500" />

Note that the Doste methods needs a geometry with outflow tracts to be run (each valve needs to be defined as a separated surface). Bayer can be run in any biventricular geometry.


### Updates to the old code
* All operations are vectorized now.
* The SVmultiphysics solver now solves a Laplace equation.
* In Bayer: For the bislerp interpolation, instead of using the correction described in Bayer et al. (that returns a discontinuity), the basis are flipped to maintain a coherent fiber direction (see function `generate_fibers_BiV_Bayer_cells` in `FibGen.py`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain these modifications a bit more? They're not published anywhere right? If so, I think it would be good to explain these in more detail (with math perhaps) and explain why you made this modifications.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. do you refer to explaining all three of these points?

Explaining the vectorization and the Bayer modification with math would probably require rewriting the methods of the Bayer paper again, which might be a bit overkill for a README? Maybe I can add those details to the documentation?

For the Bayer bislerp one, I've been trying to look for a math or code explanation that explains why the formula returns this discontinuity, but I haven't found it. If I rewrite the methods of the Bayer paper in the documentation, then I can indicate exactly where I performed the flip of the vectors. Would that work?

Same for the Laplace, I think including math would be better suited for the documentation.

Let me know what you think

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good with the documentation! I don't think you need to explain the vectorization or the Laplace equation part. Just explain how this implementation is different from what's published in the Bayer paper

* In Bayer: The beta angles were not being included correctly. The second rotation was being applied respect the first vector (circumferential) when it should be respect to the second vector (longitudinal) (see function `generate_fibers_BiV_Bayer_cells` in `FibGen.py`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also justify this too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before, should I add it to the documentation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes add to documentation


### Notes on SVmultiphysics solver

To solve a Laplace equation directly from the transient HEAT solver in SVmultiphysics, in `<GeneralSimulationParameters>` we need to set,
```
<Number_of_time_steps> 1 </Number_of_time_steps>
<Time_step_size> 1 </Time_step_size>
<Spectral_radius_of_infinite_time_step> 0. </Spectral_radius_of_infinite_time_step>
```
and in `<Add_equation type="heatS" >`,
```
<Conductivity> 1.0 </Conductivity>
<Source_term> 0.0 </Source_term>
<Density> 0.0 </Density>
```
This will allow us to solve the Laplace equation directly in 1 timestep and 1 iteration.
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/doste_fiber.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/doste_sheet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/doste_sheetnormal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-complete.mesh.vtu
Git LFS file not shown
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/av.vtp
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/epi.vtp
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/mv.vtp
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/pv.vtp
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/top.vtp
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/ot/mesh-surfaces/tv.vtp
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/truncated/VOLUME.vtu
Git LFS file not shown
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/truncated/bayer_fiber.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions utilities/fiber_generation/example/truncated/bayer_sheet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
64 changes: 64 additions & 0 deletions utilities/fiber_generation/main_bayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python
# -*-coding:utf-8 -*-
'''
Created on 2025/11/21 20:38:14

@author: Javiera Jilberto Vallejos
'''
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javijv4 There is no need have date and author. This will also need a license header.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ktbolt I removed the date and author. Where can I find the license header?


import os
import src.FibGen as fg
from time import time

###########################################################
############ USER INPUTS ################################
###########################################################

run_flag = True
svfsi_exec = "svmultiphysics "

mesh_path = "example/truncated/VOLUME.vtu"
surfaces_dir = f"example/truncated/mesh-surfaces"
outdir = "example/truncated/output_b"

surface_names = {'epi': 'EPI.vtp',
'epi_apex': 'EPI_APEX.vtp', # New surface
'base': 'BASE.vtp',
'endo_lv': 'LV.vtp',
'endo_rv': 'RV.vtp'}

# Parameters for the Bayer et al. method https://doi.org/10.1007/s10439-012-0593-5.
params = {
"ALFA_END": 60.0,
"ALFA_EPI": -60.0,
"BETA_END": 20.0,
"BETA_EPI": -20.0,
}


###########################################################
############ FIBER GENERATION ###########################
###########################################################

# Make sure the paths are full paths
mesh_path = os.path.abspath(mesh_path)
surfaces_dir = os.path.abspath(surfaces_dir)
outdir = os.path.abspath(outdir)

start = time()
fg.generate_epi_apex(mesh_path, surfaces_dir, surface_names)

# Run the Laplace solver
if run_flag:
template_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "src", "templates", "solver_bayer.xml")
laplace_results_file = fg.runLaplaceSolver(mesh_path, surfaces_dir, mesh_path, svfsi_exec, template_file, outdir, surface_names)
laplace_results_file = outdir + '/result_001.vtu'

# Generate the fiber directions
result_mesh = fg.generate_fibers_BiV_Bayer_cells(outdir, laplace_results_file, params, return_angles=True, return_intermediate=True)

print(f"generate fibers (Bayer method) elapsed time: {time() - start:.3f} s")

# Optional, save the result mesh with intermediate field and angles for checking
result_mesh_path = os.path.join(outdir, "results_bayer.vtu")
result_mesh.save(result_mesh_path)
85 changes: 85 additions & 0 deletions utilities/fiber_generation/main_doste.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*-coding:utf-8 -*-
'''
Created on 2025/11/21 20:38:14

@author: Javiera Jilberto Vallejos
'''
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javijv4 No need for date and author, also add license header.


import os
import src.FibGen as fg
from time import time

###########################################################
############ USER INPUTS ################################
###########################################################

run_flag = True
method = 'doste'
svfsi_exec = "svmultiphysics "

mesh_path = "example/ot/mesh-complete.mesh.vtu"
surfaces_dir = f"example/ot/mesh-surfaces"
outdir = "example/ot/output_d"

surface_names = {'epi': 'epi.vtp',
'epi_apex': 'epi_apex.vtp', # New surface
'av': 'av.vtp',
'mv': 'mv.vtp',
'tv': 'tv.vtp',
'pv': 'pv.vtp',
'base': 'top.vtp', # This is all the valves together, it is used to find the apex.
'endo_lv': 'endo_lv.vtp',
'endo_rv': 'endo_rv.vtp'}

# Parameters from the Doste paper https://doi.org/10.1002/cnm.3185
params = {
# A = alpha angle
'AENDORV' : 90,
'AEPIRV' : -25,
'AENDOLV' : 60,
'AEPILV' : -60,

'AOTENDOLV' : 90,
'AOTENDORV' : 90,
'AOTEPILV' : 0,
'AOTEPIRV' : 0,

# B = beta angle (this have an opposite sign to the Doste paper,
# but it's because the longitudinal direction is opposite)
'BENDORV' : 20,
'BEPIRV' : -20,
'BENDOLV' : 20,
'BEPILV' : -20,
}


###########################################################
############ FIBER GENERATION ###########################
###########################################################

# Make sure the paths are full paths
mesh_path = os.path.abspath(mesh_path)
surfaces_dir = os.path.abspath(surfaces_dir)
outdir = os.path.abspath(outdir)

# Generate the apex surface
start = time()

start = time()
fg.generate_epi_apex(mesh_path, surfaces_dir, surface_names)

# Run the Laplace solver
if run_flag:
template_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "src", "templates", "solver_doste.xml")
laplace_results_file = fg.runLaplaceSolver(mesh_path, surfaces_dir, mesh_path, svfsi_exec, template_file, outdir, surface_names)
laplace_results_file = outdir + '/result_001.vtu'

# Generate the fiber directions
result_mesh = fg.generate_fibers_BiV_Doste_cells(outdir, laplace_results_file, params, return_angles=True, return_intermediate=False)

print(f"generate fibers (Doste method) elapsed time: {time() - start:.3f} s")

# Optional, save the result mesh with intermediate field and angles for checking
result_mesh_path = os.path.join(outdir, "results_doste.vtu")
result_mesh.save(result_mesh_path)
Loading
Loading