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

Postprocessing

Visualization, convergence analysis, and property interpolation tools. For practical export workflows see Visualization & Data Export.

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