Switch to side-by-side view

--- a
+++ b/Cobiveco/@cobiveco/computeHeartAxesAndApex.m
@@ -0,0 +1,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
\ No newline at end of file