-
Notifications
You must be signed in to change notification settings - Fork 47
adding fiber generation codes #480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b010f79
c36a6b8
c401a9a
f15a136
6fd7d15
6941248
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
| * 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. | ||
javijv4 marked this conversation as resolved.
Show resolved
Hide resolved
javijv4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * 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`). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also justify this too?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before, should I add it to the documentation?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| 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 | ||
| ''' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
| 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 | ||
| ''' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.mdshowing these checks?There was a problem hiding this comment.
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!