[390c2f]: / Cobiveco / @cobiveco / computeHeartAxesAndApex.m

Download this file

148 lines (125 with data), 6.4 kB

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