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