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

Visualization & Data Export

After solve (or Sentinel.invert) you have an in-memory reconstruction. This page is the deep-dive on reading that result and getting it out — to MATLAB (.mat), to ParaView (.vtu), or as the standard ReconProps.mat — plus a runnable example.

See also

The User Guide has a brief Postprocessing section and Getting Started Tutorial 6 shows quick VTK export; this page is the authoritative reference for all export paths.

Understanding inversion outputs

What you get back depends on which entry point you used:

You want…UseYou get
Full programmatic access (mesh, state, export handles)solvean InversionResult
Shear modulus on the image voxel grid (FORGE one-shot)Sentinel.inverta NamedTuple (see below)

solveInversionResult. Key fields: material (the reconstructed properties), displacement (final calculated field, metres), state (converged, global_obj, disp_error), and setup — the InverseProblemSetup you pass to the export helpers below. See InversionResult for the full field list.

Sentinel.invertNamedTuple. The FORGE-facing one-shot returns (; real_shear, imag_shear, voxel_size, dims, stats):

  • real_shear, imag_shear — storage (μ′) and loss (μ″) shear modulus in Pa, as 3-D arrays already sampled on the original image voxel grid.

  • voxel_size — image voxel size in mm; dimssize(real_shear).

  • stats — a QA NamedTuple (converged, iterations, max_iterations, final_objective, final_disp_error, mesh_elements, mesh_nodes).

Voxels with no reconstruction (outside the mask / beyond the material mesh) are NaN — an honest "no value" sentinel, not a fill value. Don't assume the arrays are finite.

Physical units: the scalar-factor rule

Stored property values are normalized. The physical value is material.prop[i].scalar[1] * rvalue (real) and scalar[2] * ivalue (imag) — Pa for shear/bulk, kg/m³ for density. The export helpers below (interpolate_to_grid, interpolate_properties, export_reconprops, and the Material method of export_vtk) apply this scaling for you. Only raw material.prop[i].rvalue/.ivalue need it applied by hand.

Exporting to MATLAB (.mat)

Sample the reconstructed properties onto a regular grid with interpolate_to_grid, then write a .mat with MAT.jl. The grid arrays are already in Pa — write them directly, do not re-scale.

julia
using Sentinel, MAT

result = solve(prob)
mat    = result.material
# material mesh carrying property 1 (shear for Model 1):
mesh   = result.setup.meshes[result.setup.config.meshind[1, 1]]

# Sample onto a regular grid — here the mesh's own extent/resolution (metres).
# Each property may live on a DIFFERENT material mesh, so interpolate one
# property at a time, passing the mesh that carries it via prop_indices = [i].
origin = collect(mesh.origin)
res    = collect(mesh.res)
dims   = mesh.dims                         # (nx, ny, nz)
grid   = interpolate_to_grid(mat, mesh, origin, res, dims; prop_indices = [1])
# grid.data["prop1_real"] / ["prop1_imag"] are (nx,ny,nz) Float64 in Pa (NaN outside)

# coordinate axes (metres) for MATLAB
x = origin[1] .+ (0:dims[1]-1) .* res[1]
y = origin[2] .+ (0:dims[2]-1) .* res[2]
z = origin[3] .+ (0:dims[3]-1) .* res[3]

matwrite("recon_props.mat", Dict(
    "mu_storage" => grid.data["prop1_real"],                       # μ′ (Pa)
    "mu_loss"    => grid.data["prop1_imag"],                       # μ″ (Pa)
    "mu_mag"     => hypot.(grid.data["prop1_real"], grid.data["prop1_imag"]),
    "x_m" => collect(x), "y_m" => collect(y), "z_m" => collect(z),
))

Julia and MATLAB are both column-major and MAT.jl writes MATLAB-native order, so the [nx, ny, nz] arrays load with the same indexing — no transpose needed. For values at arbitrary points rather than a grid, use interpolate_properties.

Load and visualize in MATLAB:

matlab
S  = load('recon_props.mat');
mu = S.mu_storage;                         % Pa, [nx,ny,nz]

k = round(size(mu,3)/2);                   % axial slice
figure; imagesc(S.x_m, S.y_m, mu(:,:,k).'); axis image; colorbar
title('\mu'' storage modulus (Pa)');

figure; volshow(mu);                       % volume render (R2018b+)
figure; p = patch(isosurface(mu, 3000));   % 3 kPa isosurface
isonormals(mu, p); p.FaceColor = 'red'; p.EdgeColor = 'none';
camlight; lighting gouraud; axis image

NaN voxels render as transparent in imagesc/isosurface.

The standard ReconProps.mat

export_reconprops writes the canonical ReconProps.mat consumed by the MATLAB MRE-analysis pipeline. Unlike the ad-hoc grid above, it samples onto the MR voxel grid defined by sidecar files and applies the brain mask, so it requires those sidecars in basedir:

  • <stem>.InterpLocations.matxout, yout, zout, maskint

  • <stem>.InterpData.matMagIm_int

  • the dataset's .meshind file

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

Output keys (3-D arrays on the MR voxel grid, physical units, masked voxels = NaN): RealShear/ImagShear/ShearMag (Pa), RealDens/ImagDens (kg/m³), RealBulk/ImagBulk (or RealLambda/ImagLambda), DR (damping ratio 0.5*(μ″/μ′ − ρ″/ρ′)), RC, MagIm, plus meshind, meshres, recondir.

Use this for the standard pipeline product; use interpolate_to_grid + MAT.jl for grids you define yourself (no sidecars needed).

Visualizing in ParaView (VTK)

export_vtk writes a .vtu that opens directly in ParaView. Three methods cover the common cases:

julia
# 1. A generated mesh (preprocessing) — mesh + displacement/anatomy/region
export_vtk("mesh", hex_mesh_result; write_disp=true)

# 2. A Ferrite grid + a solved displacement field (disp in metres; dispset 1-indexed)
export_vtk("forward", prob.grid, prob.dh, disp)

# 3. Reconstructed material on its mesh (values scalar-scaled to Pa).
#    Scope to property 1 (shear) — its mesh; other properties may be on others.
export_vtk("recon", result.material,
           result.setup.meshes[result.setup.config.meshind[1, 1]]; prop_indices=[1])

The displacement methods write disp_real/disp_imag vector fields (metres). In ParaView: use Threshold to hide NaN/empty cells, Slice/Clip for an ROI, and Glyph to draw displacement vectors. (Generated-mesh VTK is also covered in Getting Started Tutorial 6.)

Convergence output

Convergence history is summarized and exported via print_convergence_table, convergence_statistics, and export_convergence_csv (objective is unitless; disp_error is in metres).

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

Runnable example

examples/export_outputs.jl runs a short inversion on the bundled fixture and demonstrates the MATLAB (.mat) and ParaView (.vtu) exports end-to-end:

bash
julia --project=. examples/export_outputs.jl