a b/Cobiveco/@cobiveco/computeHeartAxesAndApex.m
1
function computeHeartAxesAndApex(o)
2
3
if ~o.available.mesh1
4
    o.prepareMesh1;
5
end
6
o.printStatus('Computing heart axes and apex point...');
7
t = toc;
8
9
% point ids (wrt o.m1.vol) of septal surface and septal curve on epicaridum
10
idsEpi = o.m1.surToVol(o.m1.sur.pointData.class==2);
11
ucl = unique(o.m1.vol.cellData.class);
12
idsSeptSur = intersect(o.m1.vol.cells(o.m1.vol.cellData.class==ucl(1),:), o.m1.vol.cells(o.m1.vol.cellData.class==ucl(2),:));
13
idsSeptCurve = intersect(idsEpi, idsSeptSur);
14
15
% septCurve: line segments of septal curve, extracted from o.m1.sur
16
[~,idsSeptCurveSur] = ismember(idsSeptCurve, o.m1.surToVol);
17
pSeptCurve = double(o.m1.sur.points(idsSeptCurveSur,:));
18
cSeptCurve = o.m1.sur.cells';
19
ind = ismember(cSeptCurve, idsSeptCurveSur);
20
ind(:,sum(ind,1)~=2) = 0;
21
cSeptCurve = unique(reshape(cSeptCurve(ind),2,[])', 'rows');
22
cSeptCurve = changem(cSeptCurve, 1:numel(idsSeptCurveSur), idsSeptCurveSur);
23
septCurve = vtkCreateStruct(pSeptCurve, cSeptCurve);
24
25
% lvCenter: center of LV endocardium 
26
endoLv = vtkDataSetSurfaceFilter(vtkThreshold(o.m1.sur, 'points', 'class', [3 3]));
27
lvCenter = vtkCenterOfArea(endoLv);
28
29
% rvCenter: center of RV endocardium 
30
endoRv = vtkDataSetSurfaceFilter(vtkThreshold(o.m1.sur, 'points', 'class', [4 4]));
31
rvCenter = vtkCenterOfArea(endoRv);
32
33
% point coords of base
34
pBase = double(o.m1.sur.points(o.m1.sur.pointData.class==1,:));
35
36
% longAx: vector that is on average most orthogonal to the surface normals of the LV endocardium
37
longAx = computeLongAxis(endoLv, lvCenter-mean(pBase,1));
38
39
% compute triangle centroids and areas of septSur
40
pSeptSur = double(o.m1.vol.points(idsSeptSur,:));
41
cSeptSur = o.m1.vol.cells';
42
ind = ismember(cSeptSur, idsSeptSur');
43
ind(:,sum(ind,1)~=3) = 0;
44
cSeptSur = unique(reshape(cSeptSur(ind),3,[])', 'rows');
45
cSeptSur = changem(cSeptSur, 1:numel(idsSeptSur), idsSeptSur);
46
centroidsSeptSur = squeeze(mean(reshape(pSeptSur(cSeptSur,:),[],size(cSeptSur,2),size(pSeptSur,2)),2));
47
areasSeptSur = doublearea(pSeptSur, cSeptSur)/2;
48
49
% septVec: vector most orthogonal to the septal surface,
50
% determined as the last principle component of septal surface centroids
51
pc = pca(centroidsSeptSur, 'Weights', areasSeptSur./mean(areasSeptSur));
52
septVec = pc(:,end)';
53
% make sure septVec is directed from left to right
54
d = (rvCenter - lvCenter) * septVec';
55
septVec = sign(d) * septVec;
56
57
% antPostVec: vector pointing from anterior to posterior
58
antPostVec = cross(longAx, septVec);
59
d = (centroidsSeptSur - repmat(lvCenter, size(centroidsSeptSur,1), 1)) * antPostVec';
60
maskSeptSur = d > prctile(d,o.cfg.truncSeptSur(1)) & d < prctile(d,100-o.cfg.truncSeptSur(2));
61
62
% truncSeptCenter: center of truncated septal surface
63
centroidsTruncSept = centroidsSeptSur(maskSeptSur,:);
64
areasTruncSept = areasSeptSur(maskSeptSur);
65
truncSeptCenter = sum(repmat(areasTruncSept,1,3) .* centroidsTruncSept, 1) / sum(areasTruncSept);
66
67
% leftRightAx: vector pointing from left to right,
68
% determined as the last principle component of truncated septal surface centroids
69
pc = pca(centroidsTruncSept, 'Weights', areasTruncSept./mean(areasTruncSept));
70
leftRightAx = pc(:,end)';
71
% make sure leftRightAx is directed from left to right
72
d = (truncSeptCenter - lvCenter) * leftRightAx';
73
leftRightAx = sign(d) * leftRightAx;
74
% orthogonalize leftRightAx wrt longAx
75
leftRightAx = leftRightAx - leftRightAx*(longAx'*longAx);
76
77
% antPostAx: vector pointing from anterior to posterior,
78
antPostAx = cross(longAx, leftRightAx);
79
80
% center: global center point,
81
% determined by projecting lvCenter onto the truncated septal surface
82
d = (truncSeptCenter - lvCenter) * leftRightAx';
83
center = lvCenter + d .* leftRightAx;
84
85
% pApexCurve: points of septal curve below the center point
86
d = (pSeptCurve - repmat(center, size(pSeptCurve,1), 1)) * longAx';
87
ind1 = find(d>0);
88
pApexCurve = pSeptCurve(ind1,:);
89
90
% apex point: point in pApexCurve that is closest to the line parallel to
91
% longAx and passing through center
92
d = (repmat(center, size(pApexCurve,1), 1) - pApexCurve) * longAx';
93
pApexCurveProj = pApexCurve + d .* repmat(longAx, size(pApexCurve,1), 1);
94
dist = sqrt(sum((pApexCurveProj - center).^2, 2));
95
[~,ind2] = min(dist);
96
97
% split septCurve at apex point into anterior and posterior part
98
ind3 = ind1(ind2);
99
septCurveSplit = septCurve;
100
ind4 = any(ismember(septCurveSplit.cells, ind3),2);
101
septCurveSplit.cells(ind4,:) = [];
102
septCurveSplit.cellTypes(ind4,:) = [];
103
septCurveSplit = vtkConnectivityFilter(septCurveSplit);
104
isovals = double(unique(septCurveSplit.cellData.RegionId));
105
numCellsPerRegion = NaN(size(isovals));
106
for i = 1:numel(isovals)
107
    numCellsPerRegion(i) = numel(find(septCurveSplit.cellData.RegionId==isovals(i)));
108
end
109
[~,ind5] = sort(numCellsPerRegion, 'descend');
110
isovals = isovals(ind5(1:2));
111
o.septCurveAnt = vtkDeleteDataArrays(vtkThreshold(septCurveSplit, 'cells', 'RegionId', [isovals(1) isovals(1)]));
112
o.septCurvePost = vtkDeleteDataArrays(vtkThreshold(septCurveSplit, 'cells', 'RegionId', [isovals(2) isovals(2)]));
113
% make sure o.septCurveAnt and o.septCurvePost are not interchanged
114
if (mean(o.septCurvePost.points,1)-center)*antPostAx' < 0
115
    tmp = o.septCurveAnt;
116
    o.septCurveAnt = o.septCurvePost;
117
    o.septCurvePost = tmp;
118
end
119
120
% R: rotation matrix to align heart axes with x,y,z
121
xvec = -leftRightAx';
122
zvec = -longAx';
123
R1 = makehgtform('translate', -center);
124
R2 = makehgtform('axisrotate', cross([0;0;1], zvec), -acos([0;0;1]'*zvec));
125
R3 = makehgtform('zrotate', -acos([1;0;0]'*(R2(1:3,1:3)*xvec)));
126
o.R = R3*R2*R1;
127
128
if o.cfg.exportLevel > 2
129
    vtkWrite(o.septCurveAnt, sprintf('%sseptCurveAnt.vtp', o.cfg.outPrefix));
130
    vtkWrite(o.septCurvePost, sprintf('%sseptCurvePost.vtp', o.cfg.outPrefix));
131
    
132
    cSeptSurTrunc = cSeptSur(maskSeptSur,:);
133
    pSeptSurTrunc = pSeptSur(unique(cSeptSurTrunc),:);
134
    cSeptSurTrunc = changem(cSeptSurTrunc, 1:size(pSeptSurTrunc,1), unique(cSeptSurTrunc));
135
    vtkWrite(vtkCreateStruct(pSeptSurTrunc, cSeptSurTrunc), sprintf('%sseptSurfaceTrunc.vtp', o.cfg.outPrefix));
136
    
137
    d = norm(center-lvCenter);
138
    axes.points = [center; center-d*leftRightAx; center+2.5*d*longAx; center+2*d*antPostAx];
139
    axes.cells = int32([1 2; 1 3; 1 4]);
140
    axes.cellTypes = uint8([3; 3; 3]);
141
    axes.cellData.axis = uint8([1; 2; 3]);
142
    vtkWrite(axes, sprintf('%saxes.vtp', o.cfg.outPrefix));
143
end
144
145
o.printStatus(sprintf('%.1f seconds\n', toc-t), true);
146
o.available.heartAxesAndApex = true;
147
148
end