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

Getting Started

Installation

Sentinel.jl is not yet registered in the Julia General registry. Install directly from the repository:

julia
using Pkg
Pkg.add(url="https://github.com/acerjanic/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.

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: 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)