Switch to side-by-side view

--- a
+++ b/Cobiveco/@cobiveco/computeApicobasal.m
@@ -0,0 +1,210 @@
+function computeApicobasal(o)
+
+if ~o.available.rotational
+    o.computeRotational;
+end
+o.printStatus('Computing apicobasal coordinate...');
+t = toc;
+
+% parameters
+numTmVal = 10; % number of contour lines in transmural direction
+numRtVal = 48; % number of contour lines in rotational direction, ideally a multiple of 6
+numClPoints = 100; % number of points per contour line after resampling using cubic splines
+
+% compute contour surfaces of tm (cs)
+tmp = vtkDeleteDataArrays(o.m1.vol);
+tmp.pointData.tm = o.m1.tm;
+tmVal = linspace(0.5/numTmVal, 1-0.5/numTmVal, numTmVal);
+cs = vtkContourFilter(tmp, 'points', 'tm', tmVal);
+
+% assign regions to cs
+cs = vtkConnectivityFilter(cs);
+cs.pointData.csRegion = cs.pointData.RegionId;
+cs = vtkDeleteDataArrays(cs, {'pointData.csRegion'});
+
+% interpolate abLaplace from o.m2.vol to cs
+Mcs = baryInterpMat(o.m2.vol.points, o.m2.vol.cells, cs.points);
+cs.pointData.abLaplace = Mcs * o.m2.abLaplace;
+
+if o.cfg.exportLevel > 2
+    vtkWrite(cs, sprintf('%sabContourSurfaces.vtp', o.cfg.outPrefix));
+end
+
+% find apex point for each csRegion
+csRegions = unique(cs.pointData.csRegion);
+apexPoints = NaN(numel(csRegions),3);
+for i = 1:numel(csRegions)
+    pointIds = find(cs.pointData.csRegion==csRegions(i));
+    [~,minId] = min(cs.pointData.abLaplace(pointIds));
+    apexPoints(i,:) = cs.points(pointIds(minId),:);
+end
+
+% compute contour lines of rtSin (rtSinCl)
+tmp = cs;
+tmp.pointData.rtSin = Mcs * o.m2.rtSin;
+rtSinVal = sin(pi*linspace(-0.25, 0.25, round(numRtVal/2)+1));
+rtSinCl = vtkContourFilter(tmp, 'points', 'rtSin', rtSinVal);
+rtSinCl.pointData = rmfield(rtSinCl.pointData, 'rtSin');
+
+% compute contour lines of rtCos (rtCosCl)
+tmp = cs;
+tmp.pointData.rtCos = Mcs * o.m2.rtCos;
+rtCosVal = rtSinVal(2:end-1);
+rtCosCl = vtkContourFilter(tmp, 'points', 'rtCos', rtCosVal);
+rtCosCl.pointData = rmfield(rtCosCl.pointData, 'rtCos');
+
+% combine rtSin and rtCos contour lines (cl)
+cl = vtkAppendPolyData({rtSinCl, rtCosCl});
+
+% exclude points close to the apex to split the contour lines into two parts
+% apexRadius needs to be large enough for each contour line to be splitted
+apexRadius = 3*o.m1.meanEdgLen;
+cl.pointData.clRegion = zeros(size(cl.points,1),1);
+for i = 1:numel(csRegions)
+    pointIds = find(cl.pointData.csRegion==csRegions(i));
+    ids = rangesearch(cl.points(pointIds,:), apexPoints(i,:), apexRadius);
+    cl.pointData.clRegion(pointIds(ids{1})) = -1;
+end
+cl = vtkThreshold(cl, 'points', 'clRegion', [0 inf]);
+
+% identify separate contour lines --> clRegion
+cl = vtkConnectivityFilter(cl);
+cl.pointData.clRegion = cl.pointData.RegionId;
+cl.pointData = rmfield(cl.pointData, 'RegionId');
+cl = rmfield(cl, 'cellData');
+
+% exclude too short contour lines based on values of abLaplace
+clRegions = unique(cl.pointData.clRegion);
+abLaplaceMin = NaN(numel(clRegions),1);
+abLaplaceMax = NaN(numel(clRegions),1);
+pointIds = cell(numel(clRegions),1);
+for i = 1:numel(clRegions)
+    pointIds{i} = find(cl.pointData.clRegion==clRegions(i));
+    abLaplaceVal = cl.pointData.abLaplace(pointIds{i});
+    abLaplaceMin(i) = min(abLaplaceVal);
+    abLaplaceMax(i) = max(abLaplaceVal);
+end
+abLaplaceMinThresh = min(abLaplaceMin) + 2*(median(abLaplaceMin)-min(abLaplaceMin));
+abLaplaceMaxThresh = 0.99;
+for i = 1:numel(clRegions)
+    if abLaplaceMin(i) > abLaplaceMinThresh || abLaplaceMax(i) < abLaplaceMaxThresh
+        cl.pointData.clRegion(pointIds{i}) = -1;
+    end
+end
+cl = vtkThreshold(cl, 'points', 'clRegion', [0 inf]);
+
+% reassign clRegion
+cl = vtkConnectivityFilter(cl);
+cl.pointData.clRegion = cl.pointData.RegionId;
+cl.pointData = rmfield(cl.pointData, 'RegionId');
+cl = rmfield(cl, 'cellData');
+
+if o.cfg.exportLevel > 2
+    vtkWrite(cl, sprintf('%sabContourLines.vtp', o.cfg.outPrefix));
+end
+
+% use cubic smoothing spline to smooth and resample the contour lines
+% and to compute the normalized distance along the contour lines (abSmoothCl)
+clRegions = unique(cl.pointData.clRegion);
+splineMisfit = 5e-4 * norm(mean(double(o.m1.sur.points(o.m1.sur.pointData.class==1,:)),1)-mean(apexPoints,1));
+smoothClPoints = NaN(numel(clRegions)*numClPoints, 3);
+abSmoothCl = zeros(numel(clRegions)*numClPoints, 1);
+abLength = abSmoothCl;
+for i = 1:numel(clRegions)
+    pointIds = find(cl.pointData.clRegion==clRegions(i));
+    [~,sortInd] = sort(cl.pointData.abLaplace(pointIds));
+    apexPoint = apexPoints(csRegions==cl.pointData.csRegion(pointIds(sortInd(1))),:);
+    P = [apexPoint; double(cl.points(pointIds(sortInd),:))];
+    d = sqrt(sum(diff(P,1,1).^2,2));
+    ind = d < 1e-4*o.m1.meanEdgLen;
+    P(ind,:) = [];
+    d(ind) = [];
+    d = [0; cumsum(d)];
+    w = [100; ones(size(P,1)-1,1)];
+    tol = numel(d)*splineMisfit^2;
+    sp = spaps(d, P', tol, w);
+    P = fnval(sp, linspace(min(d), max(d), numClPoints))';
+    d = [0; cumsum(sqrt(sum(diff(P,1,1).^2,2)))];
+    ind = (i-1)*numClPoints+1:i*numClPoints;
+    smoothClPoints(ind,:) = P;
+    abSmoothCl(ind) = d/max(d);
+    abLength(ind) = max(d);
+end
+
+if o.cfg.exportLevel > 2
+    clSmooth.points = single(smoothClPoints);
+    clSmooth.pointData.ab = single(abSmoothCl);
+    clSmooth.cells = int32(repmat(reshape(repmat(0:numClPoints:numel(clRegions)*numClPoints-1,numClPoints-1,1),[],1),1,2) + repmat([(1:numClPoints-1)' (2:numClPoints)'],numel(clRegions),1));
+    clSmooth.cellTypes = repmat(uint8(3), size(clSmooth.cells,1), 1);
+    vtkWrite(clSmooth, sprintf('%sabContourLinesSmooth.vtp', o.cfg.outPrefix));
+end
+
+% BEGIN: Laplacian extrapolation of abSmoothCl to o.m1.vol
+
+% ||M ab - abSmoothCl|| forces extrapolated values to fit to contour values
+M = baryInterpMat(o.m1.vol.points, o.m1.vol.cells, smoothClPoints);
+
+% ||E ab - 1|| forces extrapolated values to 1 at the base
+baseIds = double(o.m1.surToVol(o.m1.sur.pointData.class==1));
+baseVal = ones(numel(baseIds),1);
+E = sparse(1:numel(baseIds), baseIds, ones(size(baseIds)), numel(baseIds), size(o.m1.vol.points,1));
+eta = (numel(abSmoothCl)/(numel(baseIds)))^2;
+
+% ||L ab|| forces extrapolated values to be smooth
+L = o.m1.massMat \ o.m1.L;
+
+% Initial guess for ab using nearest-neighbor interpolation
+ab = abSmoothCl(knnsearch(smoothClPoints, o.m1.vol.points, 'NSMethod','kdtree'));
+
+% Initial guess for lambda based on the relation between lambda and the 
+% half-width at half-maximum of the point spread function of the operator
+% inv(speye(size(L))+lambda*(L'*L))
+hwhm = mean(abLength)/100;
+lambda = 1.58*hwhm^3.57;
+lambda = numel(abSmoothCl)/numel(ab)*lambda;
+fprintf('\n0\t%.3e\n', lambda);
+
+b = M'*abSmoothCl + eta*E'*baseVal;
+MM = M'*M + eta*(E'*E);
+LL = L'*L;
+extrapMisfit = o.cfg.abExtrapSmooth/100;
+
+try
+    [~,flag,iter] = secant(@objFun, 1e-1*lambda, lambda, 1e-2*extrapMisfit);
+    if flag
+        warning('Secant stopped at iteration %i without converging.', iter);
+    end
+catch err
+    disp(getReport(err, 'extended', 'hyperlinks', 'off'));
+    warning('Optimization of lambda failed. Using default value instead.');
+    objFun(lambda);
+end
+
+function objVal = objFun(lambda)
+    A = MM + lambda*LL;
+    icMat = ichol_autocomp(A, struct('michol','on'));
+    [ab, flag, relres, iter] = pcg(A, b, o.cfg.tol, o.cfg.maxit, icMat, icMat', ab);
+    if flag
+        error('pcg failed at iteration %i with flag %i and relative residual %.1e.', iter, flag, relres);
+    end
+    objVal = rms(M*ab-abSmoothCl)-extrapMisfit;
+end
+
+% END: Laplacian extrapolation
+
+o.m1.ab = min(max(ab,0),1);
+o.m0.ab = min(max(o.m1.M*o.m1.ab,0),1);
+o.result.pointData.ab = single(o.m0.ab);
+
+if o.cfg.exportLevel > 1
+    o.m1.debug.pointData.ab = single(o.m1.ab);
+    vtkWrite(o.m1.debug, sprintf('%sdebug1.vtu', o.cfg.outPrefix));
+    
+    o.m0.debug.pointData.ab = single(o.m0.ab);
+    vtkWrite(o.m0.debug, sprintf('%sdebug0.vtu', o.cfg.outPrefix));
+end
+
+o.printStatus(sprintf('%.1f seconds\n', toc-t), true);
+o.available.apicobasal = true;
+
+end