◆ FORGE Suite
GitHubMechanical Neuroimaging Lab · Univ. of Delaware
Skip to content

Postprocessing

Visualization, convergence analysis, and property interpolation tools. For practical export workflows see How to Export Results to MATLAB and ParaView.

VTK Export

Export meshes and results to VTK format for visualization in ParaView.

Sentinel.export_vtk Function
julia
export_vtk(filename::AbstractString, result::HexMeshResult;
           write_disp::Bool=true, write_anatomical::Bool=true,
           write_region::Bool=true)

Export a HexMeshResult to VTK (.vtu) format with hex27 cells.

Arguments

  • filename: output path (without .vtu extension)

  • result: hex mesh generation result

  • write_disp: include displacement point data (default: true)

  • write_anatomical: include anatomical magnitude point data (default: true)

  • write_region: include region assignment point data (default: true)

Notes

Writes a single .vtu (the .vtu suffix is added). Displacement is written as disp_real/disp_imag vector point data (metres); anatomical magnitude and region id as scalar point data. Open directly in ParaView.

Example

julia
export_vtk("mesh", hex_mesh_result; write_disp=true)
source
julia
export_vtk(filename::AbstractString, grid, dh, disp::Displacement;
           dispset::Int=1)

Export a Ferrite grid with displacement solution to VTK.

Arguments

  • filename: output path (without .vtu extension)

  • grid: Ferrite Grid

  • dh: DofHandler

  • disp: solved displacement

  • dispset: which displacement set to export (default: 1, 1-indexed)

Notes

Writes disp_real/disp_imag vector point data in metres for the chosen dispset (only the first min(nd, nn) nodes are populated). Cell type follows the grid (hex27/hex8/tet4). Use ParaView's Glyph filter to draw the vectors.

Example

julia
disp = solve(forward_problem)
export_vtk("forward", forward_problem.grid, forward_problem.dh, disp)
source
julia
export_vtk(filename::AbstractString, material::Material, mesh::MaterialMesh;
           prop_indices=1:material.numprop, val_idx::Int=1)

Export material properties on a MaterialMesh to VTK (.vtu).

Nodal-basis properties are written as point data; elemental-basis properties are written as cell data.

Arguments

  • filename: output path (without .vtu extension)

  • material: Material with property arrays

  • mesh: MaterialMesh (structured hex8)

  • prop_indices: which properties to export (default: all)

  • val_idx: which value-per-point column (default: 1)

Notes

Values are written in physical units — the property's scalar factors are applied (Pa for shear/bulk, kg/m³ for density). Fields are named prop{i}_real/prop{i}_imag; nodal-basis properties become point data, elemental-basis become cell data.

Example

julia
result = solve(prob)
mesh   = result.setup.meshes[result.setup.config.meshind[1, 1]]
export_vtk("recon", result.material, mesh)   # property 1 (shear, Pa) for Model 1
source
Sentinel.PropertyMapData Type
julia
PropertyMapData

Extracted material property coordinates and values (no file I/O).

Fields

  • coords::Matrix{Float64}: (n, 3) point coordinates (metres)

  • values::Dict{String, Vector{Float64}}: named property arrays in physical units (Pa for shear/bulk, kg/m³ for density — the scalar factors are applied), keyed "prop{i}_real"/"prop{i}_imag". Nodal-basis properties have one entry per node (nn); elemental-basis have one per element (ne).

  • mesh_type::Symbol: :hex8, :hex27, or :structured

source

Convergence Analysis

Sentinel.print_convergence_table Function
julia
print_convergence_table(history::ConvergenceHistory; io::IO=stdout, header::Bool=true)

Print a formatted convergence table showing per-iteration metrics.

Columns: Iteration | Objective | Disp Error | Mat Epsilon | Obj Ratio. Units: objective is unitless, disp_error is in metres, mat_epsilon is the unitless relative material change.

The objective ratio is objective[i] / objective[i-1] (shown as --- for iteration 1).

source
Sentinel.convergence_statistics Function
julia
convergence_statistics(history::ConvergenceHistory) -> NamedTuple

Compute summary statistics from a convergence history.

Returns

Named tuple with fields:

  • iterations: total iterations

  • converged: whether the optimizer converged

  • initial_objective: first iteration objective

  • final_objective: last iteration objective

  • objective_reduction: final / initial

  • initial_disp_error: first iteration displacement error

  • final_disp_error: last iteration displacement error

  • final_mat_epsilon: last iteration material change

  • min_objective: minimum objective across all iterations

  • min_objective_iter: iteration at which minimum occurred

  • avg_obj_reduction_rate: geometric mean of per-iteration ratios

source
Sentinel.export_convergence_csv Function
julia
export_convergence_csv(filename::AbstractString, history::ConvergenceHistory;
                       delimiter::Char=',')

Export convergence history to a CSV file.

Columns: iteration, objective (unitless), disp_error (metres), mat_epsilon (unitless relative material change).

Example

julia
export_convergence_csv("convergence.csv", history)
source

Property Interpolation

Interpolate reconstructed material properties from the material mesh to arbitrary points or structured grids.

Sentinel.PropertyGridResult Type
julia
PropertyGridResult

Result of interpolate_to_grid: material properties sampled on a regular structured grid.

Fields

  • data::Dict{String, Array{Float64, 3}}: property arrays keyed "prop{i}_real" / "prop{i}_imag" (e.g. "prop1_real"). Values are in physical units — the material.prop[i].scalar factors are already applied (Pa for shear/bulk, kg/m³ for density). Grid points with no reconstruction are NaN. To recover the raw normalized values, divide by the property's scalar.

  • origin::SVector{3, Float64}: grid origin (metres).

  • resolution::SVector{3, Float64}: voxel size (metres).

  • dims::NTuple{3, Int}: grid dimensions (nx, ny, nz).

source
Sentinel.interpolate_properties Function
julia
interpolate_properties(material::Material, mesh::MaterialMesh,
                       target_coords::AbstractMatrix{<:Real};
                       prop_indices=1:material.numprop, val_idx::Int=1)
    -> Dict{String, Vector{Float64}}

Interpolate reconstructed material properties from a MaterialMesh to arbitrary target points (use interpolate_to_grid for a regular grid).

Arguments

  • material: reconstructed Material.

  • mesh: source MaterialMesh (structured hex8 grid).

  • target_coords: (n, 3) matrix of target (x, y, z) coordinates in metres.

  • prop_indices: which properties to interpolate (default: all).

  • val_idx: which value-per-point column to use (default: 1).

Nodal properties are interpolated with the trilinear hex8 basis; elemental properties take the containing element's constant value.

Returns

Dict{String, Vector{Float64}} with keys "prop{i}_real" / "prop{i}_imag" (each length n). Values are in physical units — the material.prop[i].scalar factors are applied (Pa for shear/bulk, kg/m³ for density). Points outside the mesh extent (or in empty elements) are NaN.

Example

julia
pts  = [0.0 0.0 0.0; 0.01 0.0 0.0]      # (n, 3), metres
vals = interpolate_properties(result.material, mesh, pts)
vals["prop1_real"]                       # μ′ (Pa) at each point
source
Sentinel.interpolate_to_grid Function
julia
interpolate_to_grid(material::Material, mesh::MaterialMesh,
                    origin, resolution, dims::NTuple{3, Int};
                    prop_indices=1:material.numprop, val_idx::Int=1)
    -> PropertyGridResult

Sample reconstructed material properties onto a regular structured grid (e.g. an image voxel grid) by trilinear interpolation over the source hex8 material mesh.

Arguments

  • material: reconstructed Material.

  • mesh: source MaterialMesh (the structured hex8 grid carrying the property).

  • origin: (3,) grid origin [x0, y0, z0] in metres.

  • resolution: (3,) voxel size [dx, dy, dz] in metres.

  • dims: (nx, ny, nz) output grid dimensions.

  • prop_indices: which properties to sample (default: all). Each property may live on a different material mesh (see meshind), so when properties differ pass the mesh that carries the selected property — e.g. meshes[meshind[1, i]] together with prop_indices=[i] — rather than sampling all properties against a single mesh.

  • val_idx: which value-per-point column to use (default: 1).

Returns

A PropertyGridResult. Its data["prop{i}_real"] / data["prop{i}_imag"] are (nx, ny, nz) Float64 arrays in physical units — the property's scale factors (material.prop[i].scalar) are already applied, so shear/bulk are in Pa and density in kg/m³. Grid points outside the mesh extent, or in empty elements, are left as NaN (an honest "no value" sentinel, not zero).

Example

julia
# material mesh carrying property 1 (shear), then sample it on its own grid:
mesh = result.setup.meshes[result.setup.config.meshind[1, 1]]
g    = interpolate_to_grid(result.material, mesh,
                           collect(mesh.origin), collect(mesh.res), mesh.dims;
                           prop_indices = [1])
mu_re = g.data["prop1_real"]    # μ′ (Pa), (nx,ny,nz), NaN outside the mesh

See also interpolate_properties (arbitrary points) and export_reconprops (standard ReconProps.mat).

source

MATLAB / ReconProps Export

Write the standard ReconProps.mat consumed by the MATLAB MRE-analysis pipeline.

Sentinel.export_reconprops Function
julia
export_reconprops(material::Material, setup::InverseProblemSetup,
                  basedir::String, outpath::String; iteration::Int=0) -> String

Write the standard ReconProps.mat consumed by the MATLAB MRE-analysis pipeline. Interpolates the reconstructed properties onto the MR voxel grid (via interpolate_to_grid), applies the brain mask, computes derived quantities, and writes the .mat file. Returns outpath.

For ad-hoc grids you define yourself (no sidecar files required), use interpolate_to_grid with MAT.matwrite instead.

Arguments

  • material: reconstructed Material (current property values).

  • setup: the InverseProblemSetup from the solve (e.g. result.setup) — provides the meshes, config, and model.

  • basedir: dataset directory containing the required sidecar files.

  • outpath: output .mat path.

  • iteration: iteration number, recorded as metadata (default 0).

Required sidecar files (in basedir)

  • <stem>.InterpLocations.matxout, yout, zout, maskint (MR voxel grid and brain mask).

  • <stem>.InterpData.matMagIm_int (magnitude image).

  • the dataset's .meshind file.

Output .mat keys

3-D arrays on the MR voxel grid, in physical units, with voxels outside the mask set to NaN:

  • RealShear, ImagShear — storage / loss shear modulus μ′, μ″ (Pa).

  • ShearMag — complex shear magnitude sqrt(RealShear^2 + ImagShear^2) (Pa).

  • RealDens, ImagDens — density ρ (kg/m³), when reconstructed.

  • RealBulk/ImagBulk (isotropic-incompressible) or RealLambda/ImagLambda (isotropic-compressible) — the third property, when present.

  • DR — damping ratio 0.5*(μ″/μ′ − ρ″/ρ′) (dimensionless).

  • RC — reconstruction confidence 0.5*(μ″/μ′)/DR (dimensionless).

  • MagIm — the magnitude image from InterpData.mat.

  • meshind (2 × numprop), meshres (nmesh × 3, metres), recondir.

Example

julia
result = solve(prob)
export_reconprops(result.material, result.setup, "/path/to/dataset", "ReconProps.mat")
source