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

Getting Started

Installation

Install Julia

Sentinel needs Julia 1.10+. The easiest way to install and manage Julia is juliaup:

sh
# macOS / Linux
curl -fsSL https://install.julialang.org | sh

Restart your shell, then verify (and keep Julia current):

sh
juliaup status      # installed channels
julia --version     # should be ≥ 1.10
juliaup update      # update to the latest stable release

On macOS you can alternatively brew install juliaup.

Install Sentinel

Sentinel.jl is not yet registered in the Julia General registry. Two ways to install:

As a package — use it from your own environment:

julia
using Pkg
Pkg.add(url="https://github.com/mechneurolab/sentinel")

As a clone — recommended for development (editing source, running the test suite, using the bin/sentinel CLI):

sh
git clone https://github.com/mechneurolab/sentinel.git
cd sentinel
julia
using Pkg
Pkg.activate(".")     # use Sentinel's own project environment
Pkg.instantiate()     # install the pinned dependencies

Or develop the clone into another environment so your edits are picked up live:

julia
using Pkg
Pkg.develop(path="/path/to/sentinel")

Requirements: Julia 1.10+, Ferrite.jl 1.3.0 (installed automatically).

Optional Dependencies

julia
# MUMPS solver (recommended for large problems)
Pkg.add("MUMPS")

# GPU acceleration (Apple Silicon)
Pkg.add("Metal")

# GPU acceleration (NVIDIA)
Pkg.add("CUDA")
Pkg.add("CUDSS")

# AppleAccelerate (macOS — loaded automatically, ~13% faster sparse solves)
Pkg.add("AppleAccelerate")

AppleAccelerate

On macOS, Sentinel automatically loads AppleAccelerate if available, switching the BLAS/LAPACK backend to Apple's Accelerate framework. This gives ~13% faster sparse LU solves on Apple Silicon.

Development setup (VS Code)

The Julia extension for VS Code gives an integrated REPL, debugger, and plot pane — a convenient way to run Sentinel.

  1. Install the extension. In VS Code → Extensions, install "Julia" (julialang.language-julia). It auto-detects the juliaup-managed Julia.

  2. Open the cloned repo (File → Open Folder → your sentinel/ clone).

  3. Use Sentinel's environment. Start a Julia REPL (Command Palette → "Julia: Start REPL"). The status bar should show the sentinel project; if not, click it and choose the repo folder, or run ] activate . in the REPL.

  4. Set the thread count. The inversion parallelizes across Julia threads, but VS Code starts the REPL with one thread by default. In Settings, set julia.NumThreads to "auto" (all cores) or a number, then restart the REPL. Verify with Threads.nthreads().

Why threads matter

The inversion auto-selects a multicore strategy from the material model and the thread count (see Parallel Execution and Command-Line Interface). With a single thread it runs serially — so set julia.NumThreads before a real run.

Run a basic inversion from a .mat file

With the REPL on the sentinel environment and threads set, reconstruct directly from an MRE .mat data file (the mesh is generated automatically):

julia
using Sentinel

prob = InversionProblem(data="scan.mat", frequency=60.0,
                        model=:isotropic, output="inv/scan")
result = solve(prob)            # → InversionResult

result.state.converged          # did it converge?
result.material                 # reconstructed properties

Sentinel.invert("scan.mat"; opts=(...)) is the one-shot variant returning the shear modulus on the image voxel grid. The three .mat entry points are covered in Tutorial 5 below; to get results out to MATLAB/ParaView see Visualization & Data Export.

Tutorial 1: Forward Problem

This tutorial solves a forward problem on a simple hex27 mesh with isotropic incompressible material (Model 1).

julia
using Sentinel, Ferrite

# 1. Create a hex27 mesh (4x4x4 elements)
grid = generate_grid(Hexahedron, (4, 4, 4))
dh = setup_hex27_dofhandler(grid)

# 2. Set up cell values for integration
ip_scalar = Lagrange{RefHexahedron, 2}()
ip_press  = Lagrange{RefHexahedron, 1}()
qr = QuadratureRule{RefHexahedron}(3)
cv_disp  = CellValues(qr, ip_scalar)
cv_press = CellValues(qr, ip_press)

# 3. Define material properties
model = IsotropicIncompressible()
omega = 2pi * 60.0    # 60 Hz excitation frequency
rho   = 1000.0         # density [kg/m^3]

# Create Gauss-point material with uniform properties
ne = getncells(grid)
ngp = 27  # 3x3x3 Gauss points
mu_val    = complex(3000.0, -300.0)   # shear modulus [Pa]
kappa_val = complex(2.0e9, 0.0)       # bulk modulus [Pa]

props = GaussPointMaterial(
    mu    = fill(mu_val, ne, ngp),
    kappa = fill(kappa_val, ne, ngp),
    rho   = fill(rho, ne, ngp),
    ρ     = fill(rho, ne, ngp)
)

# 4. Allocate and assemble
K = allocate_stiffness(dh, model)
assemble_stiffness!(K, dh, cv_disp, cv_press, model, props, omega)

# 5. Apply boundary conditions and solve
f = zeros(ComplexF64, size(K, 1))
# ... set up BCs and RHS, then:
# forward_solve!(disp, K, f, model, dispset; solver=DirectSolver())

Tutorial 2: Runfile Workflow

The most common workflow uses Fortran-compatible runfiles (.dat format):

julia
using Sentinel

# Parse a .dat runfile (same format as Fortran MRE-Zone)
config = parse_runfile("brain_mre.dat")

# Load all files and set up the complete problem
setup = setup_forward_problem(config, "/path/to/data/")

# Access the loaded components
grid     = setup.grid
dh       = setup.dh
model    = setup.model
material = setup.material
bcs      = setup.bcs
K        = setup.K

The setup_forward_problem function reads all referenced files (.nod, .elm, .dsp, .mtr, .bnd, .bcs) and constructs the complete problem including the DOF handler, material mesh interpolation, and pre-assembled stiffness matrix.

Tutorial 3: Inverse Problem

Set up and run an inverse reconstruction using conjugate gradient with Tikhonov regularization:

julia
using Sentinel

# ... (load data via runfile or manual setup) ...

# Set up regularization
tikhonov = TikhonovReg(1, 1e-4, 1e-4, material)  # prop 1, weights, reference from material

# Build the forward problem context
ctx = ForwardProblemContext(
    grid=grid, dh=dh, cv_disp=cv_disp, cv_press=cv_press,
    model=model, bcs=bcs, meas=meas, omega=omega, rho=rho,
    regularizations=[tikhonov], solver=DirectSolver(),
    numdispsets=1
)

# Initialize gradient structure
grad = MaterialGradient()
init_gradient!(grad, material)

# Run CG optimizer
result_material, history = conjugate_gradient!(material, ctx;
    max_iter=20, tol=1e-6)

# Check convergence
print_convergence_table(history)

Tutorial 4: MRI Data to Mesh

Generate a finite element mesh from MRI displacement data:

julia
using Sentinel

# Load MRI data (Siemens DICOM format)
mre_data = read_mre_siemens("dicom_dir/", 60.0, 1.0)

# Configure mesh generation
config = HexMeshConfig(
    mesh_strategy=2,           # wavelength-based resolution
    mu_estimate=3000.0,        # estimated shear modulus [Pa]
    rho_estimate=1000.0,       # density [kg/m^3]
    nodes_per_wavelength=8.0,  # FEM nodes per shear wavelength
    buffer_size=2              # boundary buffer elements
)

# Generate hex27 mesh
result = generate_hex_mesh(mre_data, config)

# Write mesh files in NLI format
write_hex_mesh_files(result, "output/brain")

# Export to VTK for visualization
export_vtk("output/brain_mesh", result)

Tutorial 5: Inversion from a .mat File

You can start an inversion directly from an MRE .mat data file — the mesh and displacement files are generated from it automatically, so no pre-built mesh is needed. There are three equivalent ways.

1. Sentinel.invert — one shot, shear modulus on the image voxel grid:

julia
using Sentinel

res = invert("scan.mat";
    opts = (material_model="isotropic", regularization="total_variation",
            reg_weight=1e-3, frequency_hz=60.0, density_kg_m3=1000.0,
            max_global_iters=20, mesh_resolution=1,
            compute_backend="auto"))   # gradient device: "auto" | "gpu" | "cpu"

res.real_shear   # storage modulus (Pa) on the original voxel grid (NaN outside the mask)
res.imag_shear   # loss modulus (Pa)
res.voxel_size   # (mm); res.dims; res.stats (convergence/QA summary)

2. InversionProblem(data=…) + solve — declarative, writes files + provenance:

julia
prob = InversionProblem(data="scan.mat", frequency=60.0, mesh_strategy=2,
                        model=:isotropic, output="inv/scan")
result = solve(prob)    # generates inv/generated_mesh/, writes inv/scan.config.toml

3. TOML + the sentinel CLI:

toml
# inversion.toml
[data]
mat = "scan.mat"
frequency_hz = 60.0
output = "recon"

[model]
type = "isotropic"
bash
bin/sentinel invert inversion.toml --threads 8    # Model 1 → multicore (auto)

Export the result to MATLAB (.mat). Sample the reconstructed shear modulus onto a grid and write it with MAT.jl. The grid is already in physical Pa (do not re-scale), and properties can live on different material meshes, so scope to property 1 (shear):

julia
using MAT

mesh = result.setup.meshes[result.setup.config.meshind[1, 1]]   # the shear mesh
grid = interpolate_to_grid(result.material, mesh,
                           collect(mesh.origin), collect(mesh.res), mesh.dims;
                           prop_indices = [1])
matwrite("inv/scan_shear.mat", Dict(
    "mu_storage" => grid.data["prop1_real"],   # μ′ (Pa)
    "mu_loss"    => grid.data["prop1_imag"],    # μ″ (Pa)
))

(With the Sentinel.invert one-shot, res.real_shear / res.imag_shear are already on the image voxel grid — matwrite them directly.) For coordinate axes, a MATLAB load/visualize snippet, ParaView, and the standard ReconProps.mat, see Visualization & Data Export.

A runnable, no-download version is in examples/run_mat_demo.sh, which uses the bundled test/fixtures/forge_mre_uiuc.mat. See the Command-Line Interface guide for the full CLI reference.

Tutorial 6: VTK Export

Export results to VTK format for visualization in ParaView:

julia
using Sentinel

# Export mesh with displacement data
export_vtk("result", hex_mesh_result;
    write_disp=true,
    write_anatomical=true,
    write_region=true
)

# Export convergence history to CSV
export_convergence_csv("convergence.csv", history)

For full export coverage — MATLAB (.mat), ParaView, and the standard ReconProps.mat — see Visualization & Data Export.