# Medical Image-Based Finite Element Analysis of Spine

This example shows how to estimate bone stress and strain in a vertebra bone under axial compression using finite element (FE) analysis.

Bones experience mechanical loading due to gravity, motion, and muscle contraction. Estimating the stresses and strains within bone tissue can help predict bone strength and fracture risk. This example uses image-based FE analysis to predict bone stress and strain within a vertebra under axial compression. Image-based FE uses medical images, such as computed tomography (CT) scans, to generate the geometry and material properties of the FE model.

In this example, you create and analyze a 3-D model of a single vertebra under axial loading using this workflow:

### Download Data

This example uses a CT scan saved as a directory of DICOM files. The scan is part of a data set that contains three CT volumes. Run this code to download the data set from the MathWorks website as a zip file and unzip the file. The total size of the data set is approximately 81 MB.

zipFile = matlab.internal.examples.downloadSupportFile("medical","MedicalVolumeDICOMData.zip"); filepath = fileparts(zipFile); unzip(zipFile,filepath)

After you download and unzip the data set, the scan used in this example is available in the `dataFolder`

directory.

dataFolder = fullfile(filepath,"MedicalVolumeDICOMData","LungCT01");

### Segment Vertebra from CT Scan

This example uses a binary segmentation mask of one vertebra in the spine. The mask was created by segmenting the spine from a chest CT scan using the **Medical Image Labeler** app. For an example of how to segment medical image volumes in the app, see Label 3-D Medical Image Using Medical Image Labeler.

The segmentation mask is attached to this example as a supporting file. Load the mask as a `medicalVolume`

object.

`labelVol = medicalVolume("LungCT01.nii.gz");`

The `VolumeGeometry`

property of a medical volume object contains a `medicalref3d`

object that defines the patient coordinate system for the scan. Extract the `medicalref3d`

object for the mask into a new variable, `R`

.

R = labelVol.VolumeGeometry;

### Extract Vertebra Geometry and Generate FE Mesh

#### Extract Isosurface of Vertebra Mask

Calculate the isosurface faces and vertices for the vertebra mask by using the `extractIsosurface`

function. The `vertices`

coordinates are in the intrinsic coordinate system defined by rows, columns, and slices.

isovalue = 1; [faces,vertices] = extractIsosurface(labelVol.Voxels,isovalue);

Transform `vertices`

to the patient coordinate system defined by `R`

by using the `intrinsicToWorld`

object function. The `X`

, `Y`

, and `Z`

outputs define the *xyz*-coordinates of the surface points, in millimeters.

I = vertices(:,1); J = vertices(:,2); K = vertices(:,3); [X,Y,Z] = intrinsicToWorld(R,I,J,K); verticesPatient = [X Y Z];

Convert the vertex coordinates to meters.

verticesPatientMeters = verticesPatient.*10^-3;

Display the vertebral isosurface as a triangulated `Surface`

object.

triangul = triangulation(double(faces),double(verticesPatientMeters)); viewer = viewer3d; surface = images.ui.graphics.Surface(viewer,data=triangul,Color=[1 0 0],Alpha=1);

#### Create PDE Model Geometry

Create a general `PDEModel`

model container for a system of three equations. A general model allows you to assign spatially varying material properties based on bone density.

model = createpde(3);

Specify the geometry for the model by using the `geometryFromMesh`

(Partial Differential Equation Toolbox) object function, with the isosurface in patient coordinates as input.

```
geometryFromMesh(model,verticesPatientMeters',faces');
pdegplot(model,FaceLabels="on")
```

#### Generate Mesh from Model Geometry

Specify the maximum edge length for the FE mesh elements. This example uses a max edge length of 1.6 mm, which was determined using a mesh convergence analysis.

hMax = 0.0016;

Generate the mesh from the model geometry. Specify the maximum edge length, and specify that the minimum edge length is half of the maximum edge length. The elements are quadratic tetrahedra.

msh = generateMesh(model,Hmax=hMax,Hmin=hMax/2); pdemesh(model);

### Assign Material Properties

Bone material properties vary based on spatial location and direction:

Spatial location — Local regions with greater bone density are stiffer than low density regions.

Direction — Bone stiffness depends on the loading direction. This example models bone using

*transverse isotropy*. A transversely isotropic material is stiffer in the axial direction versus in the transverse plane, and is uniform within the transverse plane.

In this example, you assign spatially varying, transversely isotropic linear elastic material properties.

#### Extract HU Values Within Vertebra Mask

To assign spatially varying material properties, you need to map CT intensity values to FE mesh coordinates.

Load the original, unsegmented CT scan using a `medicalVolume`

object. The `medicalVolume`

object automatically converts the intensity to Hounsfield units (HU). The HU value is a standard for measuring radiodensity.

DCMData = medicalVolume(dataFolder);

Extract the indices and intensities of CT voxels inside the vertebra mask.

trueIdx = find(labelVol.Voxels==1); HUVertebra = double(DCMData.Voxels(trueIdx));

Convert the linear indices in `trueIdx`

into subscript indices, in units of voxels.

[row,col,slice] = ind2sub(size(labelVol.Voxels),trueIdx);

Transform the subscript indices into patient coordinates and convert the coordinates from millimeters to meters.

[X2,Y2,Z2] = intrinsicToWorld(R,col,row,slice); HUVertebraMeters = [X2 Y2 Z2].*10^-3;

#### Map HU Values from CT Voxels to Mesh Nodes

The FE node coordinates are scattered and not aligned with the CT voxel grid. Create a `scatteredInterpolant`

object to define the 3-D interpolation between the voxel grid and FE nodes.

F = scatteredInterpolant(HUVertebraMeters,HUVertebra);

#### Specify Coefficients

Specify the PDE coefficients for the model. In this structural analysis, the *c* coefficient defines the material stiffness of the model, which is inversely related to compliance. To define a spatially varying *c* coefficient in Partial Differential Equation Toolbox™, represent `c`

in function form. In this example, the `HU2TransverseIsotropy`

helper function defines transversely isotropic material properties based on bone density. Bone density is calculated for a given location using the scattered interpolant `F`

. The helper function is wrapped in an anonymous function, `ccoeffunc`

, which passes the `location`

and `state`

structures to `HU2TransverseIsotropy`

. The FE solver automatically computes `location`

and `state`

structure arrays and passes them to your function during the simulation.

ccoeffunc = @(location,state) HU2TransverseIsotropy(location,state,F); specifyCoefficients(model,'m',0, ... 'd',0, ... 'c',ccoeffunc, ... 'a',0, ... 'f',[0;0;0]);

### Apply Loading and Solve Model

To simulate axial loading of the vertebra, fix the bottom surface and apply a downward load to the top surface. To simulate distributed loading, the load is applied as a pressure.

Identify the faces in the model geometry to apply the boundary conditions. In this example, the faces were identified using visual inspection of the plot created above using `pdegplot`

.

bottomSurfaceFace = 1; topSurfaceFace = 250;

Specify the total force to apply, in newtons.

forceInput = -3000;

Estimate the area of the top surface.

```
nf2=findNodes(model.Mesh,"region",Face=topSurfaceFace);
positions = model.Mesh.Nodes(:,nf2)';
surfaceShape = alphaShape(positions(:,1:2));
faceArea = area(surfaceShape);
```

Calculate the pressure magnitude as a function of force and area, in pascals.

inputPressure_Pa = forceInput/faceArea;

Apply the boundary conditions.

applyBoundaryCondition(model,"dirichlet",Face=bottomSurfaceFace,u=[0,0,0]); applyBoundaryCondition(model,"neumann",Face=topSurfaceFace,g=[0;0;inputPressure_Pa]);

Solve the model. The output is a `StationaryResults`

(Partial Differential Equation Toolbox) object that contains nodal displacements and their gradients.

Rs = solvepde(model);

### Analyze Results

View the results of the simulation by plotting the axial displacement, stress, and strain within the model.

#### Displacement

Plot axial displacement, in millimeters, for the full model by using the `pdeplot3D`

function.

Uz = Rs.NodalSolution(:,3)*10^3; pdeplot3D(model,"ColorMapData",Uz) clim([-0.15 0]) title("Axial Displacement (mm)")

Plot displacement in a transverse slice at the midpoint of vertebral height.

Create a rectangular grid that covers the geometry of the transverse (*xy*) slice with spacing of 1 mm in each direction. The `x`

and `y`

vectors define the spatial limits and spacing in the transverse plane, and `z`

is a scalar the provides the midpoint of vertebral height. The `Xg`

, `Yg`

, and `Zg`

variables define the grid coordinates.

x = min(msh.Nodes(1,:)):0.001:max(msh.Nodes(1,:)); y = min(msh.Nodes(2,:)):0.001:max(msh.Nodes(2,:)); z = min(msh.Nodes(3,:))+0.5*(max(msh.Nodes(3,:))-min(msh.Nodes(3,:))); [Xg,Yg] = meshgrid(x,y); Zg = z*ones(size(Xg));

Interpolate normal axial displacement onto the grid coordinates by using the `interpolateSolution`

(Partial Differential Equation Toolbox) object function. Convert displacement from meters to millimeters, and reshape the displacement vector to the grid size.

U = interpolateSolution(Rs,Xg,Yg,Zg,3); U = U*10^3; Ug = reshape(U,size(Xg));

Plot axial displacement in the transverse slice.

surf(Xg,Yg,Ug,LineStyle="none") axis equal grid off view(0,90) colormap("turbo") colorbar clim([-0.15 0]) title("Axial Displacement (mm)")

#### Stress

In a structural analysis, stress is the product of the *c* coefficient and the gradients of displacement. Calculate normal stresses at the transverse slice grid coordinates by using the `evaluateCGradient`

(Partial Differential Equation Toolbox) object function.

[cgradx,cgrady,cgradz] = evaluateCGradient(Rs,Xg,Yg,Zg,3);

Convert normal axial stress to megapascals, and reshape the stress vector to the grid size.

cgradz = cgradz*10^-6; cgradzg = reshape(cgradz,size(Xg));

Plot the normal axial stress in the transverse slice.

surf(Xg,Yg,cgradzg,LineStyle="none"); axis equal grid off view(0,90) colormap("turbo") colorbar clim([-10 1]) title("Axial Stress (MPa)")

#### Strain

In a structural analysis, strain is the gradient of the displacement result. Extract the normal strain at the transverse slice grid coordinates by using the `evaluateGradient`

(Partial Differential Equation Toolbox) object function.

[gradx,grady,gradz] = evaluateGradient(Rs,Xg,Yg,Zg,3);

Reshape the axial strain vector to the grid size.

gradzg = reshape(gradz,size(Xg));

Plot the axial strain in the transverse slice.

surf(Xg,Yg,gradzg,LineStyle="none"); axis equal grid off view(0,90) colormap("turbo") colorbar clim([-0.008 0]) title("Axial Strain")

### Helper Functions

The `HU2TransverseIsotropy`

helper function specifies transverse isotropic material properties using these steps:

Map voxel intensity to Hounsfield units (HU) at nodal locations using the

`scatteredInterpolant`

object,`F`

.Convert HU to CT density using a linear calibration equation from [1].

Convert CT density to elastic modulus in the axial direction,

`E3`

, and in the transverse plane,`E12`

, as well as the bulk modulus,`G`

, using density-elasticity relationships from [2]. The Poisson's ratio in the axial direction,`v3`

, and transverse plane,`v12`

, are also assigned based on [2].Define the 6-by-6 compliance matrix for transverse isotropy by using the

`elasticityTransOrthoC3D`

helper function.Convert the 6-by-6 compliance, or

*c*-matrix, to the vector form required by Partial Differential Equation Toolbox by using the`SixMat2NineMat`

and`SquareMat2CCoeffVec`

helper functions.

function ccoeff = HU2TransverseIsotropy(location,state,F) HU = F(location.x,location.y,location.z); rho = 5.2+0.8*HU; E3 = -34.7 + 3.230.*rho; E12 = 0.333*E3; v12 = 0.104; v3 = 0.381; G = 0.121*E3; ccoeff = zeros(45,length(location.x)); for i = 1:length(location.x) cMatrix = elasticityTransOrthoC3D(E12(i),E3(i),v12,v3,G(i)); nineMat = SixMat2NineMat(cMatrix); ccoeff(:,i) = SquareMat2CCoeffVec(nineMat); end end

The `elasticityTransOrthoC3D`

helper function defines the 6-by-6 compliance matrix for a transversely isotropic linear elastic material based on the elastic moduli, `E12`

and `E3`

, bulk modulus, `G`

, and Poisson's ratios, `v12`

and `v3`

. The helper function converts all modulus values from megapascals to pascals before computing the compliance matrix.

function C = elasticityTransOrthoC3D(E12,E3,v12,v3,G) E12 = E12*10^6; E3 = E3*10^6; G = G*10^6; v_zp = (E3*v3)/E12; C = zeros(6,6); C(1,1) = 1/E12; C(1,2) = -v12/E12; C(1,3)= -v_zp/E3; C(2,1) = C(1,2); C(2,2) = 1/E12; C(2,3) = -v_zp/E3; C(3,1) = -v3/E12; C(3,2) = C(2,3); C(3,3) = 1/E3; C(4,4) = 1/(2*G); C(5,5) = 1/(2*G); C(6,6) = (1+v12)/E12; C=inv(C); end

The `SixMat2NineMat`

helper function converts a 6-by-6 *c* coefficient matrix in Voigt notation to a 9-by-9 matrix corresponding to expanded form.

function nineMat = SixMat2NineMat(sixMat) for i = 1:6 nineVecs(i,:) = sixMat(i,[1 6 5 6 2 4 5 4 3]); end nineMat = [ nineVecs(1,:); ... nineVecs(6,:); ... nineVecs(5,:); ... nineVecs(6,:); ... nineVecs(2,:); ... nineVecs(4,:); ... nineVecs(5,:); ... nineVecs(4,:); ... nineVecs(3,:)]; end

The `SquareMat2CCoeffVec`

converts a 9-by-9 *c* coefficient matrix into vector form as required by Partial Differential Equation Toolbox. This helper function creates a 45-element vector, corresponding to the "3N(3N+1)/2-Element Column Vector c, 3-D Systems" case in c Coefficient for specifyCoefficients (Partial Differential Equation Toolbox).

function c45Vec = SquareMat2CCoeffVec(nineMat) C11 = [nineMat(1,1) nineMat(1,2) nineMat(2,2) nineMat(1,3) nineMat(2,3) nineMat(3,3)]; C12 = [nineMat(1,4) nineMat(2,4) nineMat(3,4) nineMat(1,5) nineMat(2,5) nineMat(3,5) ... nineMat(1,6) nineMat(2,6) nineMat(3,6)]; C13 = [nineMat(1,7) nineMat(2,7) nineMat(3,7) nineMat(1,8) nineMat(2,8) nineMat(3,8) ... nineMat(1,9) nineMat(2,9) nineMat(3,9)]; C22 = [nineMat(4,4) nineMat(4,5) nineMat(5,5) nineMat(4,6) nineMat(5,6) nineMat(6,6)]; C23 = [nineMat(4,7) nineMat(5,7) nineMat(6,7) nineMat(4,8) nineMat(5,8) nineMat(6,8) ... nineMat(4,9) nineMat(5,9) nineMat(6,9)]; C33 = [nineMat(7,7) nineMat(7,8) nineMat(8,8) nineMat(7,9) nineMat(8,9) nineMat(9,9)]; c45Vec = [C11 C12 C22 C13 C23 C33]'; end

### References

[1] Turbucz, Mate, Agoston Jakab Pokorni, György Szőke, Zoltan Hoffer, Rita Maria Kiss, Aron Lazary, and Peter Endre Eltes. “Development and Validation of Two Intact Lumbar Spine Finite Element Models for In Silico Investigations: Comparison of the Bone Modelling Approaches.” *Applied Sciences* 12, no. 20 (January 2022): 10256. https://doi.org/10.3390/app122010256.

[2] Unnikrishnan, Ginu U., and Elise F. Morgan. “A New Material Mapping Procedure for Quantitative Computed Tomography-Based, Continuum Finite Element Analyses of the Vertebra.” *Journal of Biomechanical Engineering* 133, no. 7 (July 1, 2011): 071001. https://doi.org/10.1115/1.4004190.

## See Also

Medical Image
Labeler | `medicalVolume`

| `intrinsicToWorld`

| `scatteredInterpolant`

| `createpde`

(Partial Differential Equation Toolbox) | `geometryFromMesh`

(Partial Differential Equation Toolbox) | `solvepde`

(Partial Differential Equation Toolbox) | `StationaryResults`

(Partial Differential Equation Toolbox)