|
a |
|
b/Cobiveco/functions/computeLongAxis.m |
|
|
1 |
function longAx = computeLongAxis(sur, directionVec) |
|
|
2 |
% Computes the heart's long axis as the vector minimizing the dot product |
|
|
3 |
% with the surface normals of the LV endocardium, i.e. the vector that is |
|
|
4 |
% "most orthogonal" to the surface normals. |
|
|
5 |
% |
|
|
6 |
% longAx = computeLongAxis(sur, basePoints) |
|
|
7 |
% |
|
|
8 |
% Inputs: |
|
|
9 |
% sur: LV endocardial surface as VTK struct |
|
|
10 |
% directionVec: A vector coarsely directed from base towards apex |
|
|
11 |
% |
|
|
12 |
% Outputs: |
|
|
13 |
% longAx: Unit vector directed from base towards apex |
|
|
14 |
% |
|
|
15 |
% Written by Steffen Schuler, Institute of Biomedical Engineering, KIT |
|
|
16 |
|
|
|
17 |
TR = vtkToTriangulation(sur); |
|
|
18 |
normals = TR.faceNormal; |
|
|
19 |
areaWeights = doublearea(TR.Points, TR.ConnectivityList); |
|
|
20 |
areaWeights = areaWeights/mean(areaWeights); |
|
|
21 |
|
|
|
22 |
% p-norm is chosen to have average properties of 1-norm and 2-norm |
|
|
23 |
h = (1/sqrt(2)+1)/2; |
|
|
24 |
p = 1/(log2(sqrt(2)/h)); |
|
|
25 |
|
|
|
26 |
objFun = @(longAxis) sum(abs(areaWeights.*normals*longAxis').^p) + size(normals,1)*abs(norm(longAxis)-1)^p; |
|
|
27 |
options = optimset('MaxFunEvals', 1e4); |
|
|
28 |
longAx = NaN(3); |
|
|
29 |
objVal = NaN(3,1); |
|
|
30 |
[longAx(1,:),objVal(1)] = fminsearch(objFun, [1 0 0], options); |
|
|
31 |
[longAx(2,:),objVal(2)] = fminsearch(objFun, [0 1 0], options); |
|
|
32 |
[longAx(3,:),objVal(3)] = fminsearch(objFun, [0 0 1], options); |
|
|
33 |
[~,minInd] = min(objVal); |
|
|
34 |
longAx = longAx(minInd,:); |
|
|
35 |
longAx = longAx/norm(longAx); |
|
|
36 |
|
|
|
37 |
% make sure longAxis is directed from base towards apex |
|
|
38 |
d = directionVec(:)' * longAx'; |
|
|
39 |
longAx = sign(d) * longAx; |
|
|
40 |
|
|
|
41 |
end |