% Consistent Biventricular Coordinates
%
% Usage:
% c = cobiveco(config);
% c.computeAll;
% result = c.result;
%
% Input:
% - config: configuration struct with the following fields:
% config.inPrefix: prefix for input files, e.g. 'inputDir/namePrefix';
% a tetrahedral mesh has to be provided as VTU (inPrefix + '.vtu');
% boundary surfaces can either be providing as individual PLY files:
% inPrefix + '_base.ply', '_epi.ply', '_endo_lv.ply', '_endo_rv.ply'
% or as one VTP file (inPrefix + '.vtp') containing point data 'class' with the following values:
% 1: base, 2: epi, 3: endo_lv, 4: endo_rv
% config.outPrefix: prefix for output files, e.g. 'outputDir/' (trailing slash for directories!)
% config.exportLevel: integer defining what should be exported as files:
% 0: nothing
% 1: only the final result and the rotation matrix (default)
% 2: + intermediate PDE-solutions, all volume meshes and mmg command line outputs
% 3: + septal curves, heart axes, apicobasal streamlines and an axes-aligned version of the final result
% config.verbose: whether to print status messages (optional, default: true)
% config.mappingTol: tolerance for mapping of boundary surfaces, unit: mean edge length (optional, default: 0.5)
% config.tol: tolerance for iterative solvers (optional, default: 1e-8)
% config.maxit: maximum number of iterations for iterative solvers (optional, default: 3000)
% config.truncSeptSur: percentages of triangles to truncate from the septal surface on the anterior and posterior side, respectively;
% affects the left-right axis and the position of the apex point (optional, default: [20 10])
% config.abExtrapSmooth: smoothness for extrapolation of apicobasal coord from contour lines to mesh nodes
% (RMS deviation from the contour line values in percent), useful range: 0.1...1 (optional, default: 0.25)
% config.mmgSizingParam: a vector [hausd hmin hmax] defining sizing parameters for mmg (optional), unit: mean edge length
% hausd: maximal Hausdorff distance for approximation of boundaries (default: 0.1)
% hmin: minimal edge size (default: 0.9)
% hmax: maximal edge size (default: 1.1)
%
% Outputs:
% - c.result: VTK struct with the following point data fields:
% c.result.pointData.tv: transventricular coordinate
% c.result.pointData.tm: transmural coordinate
% c.result.pointData.ab: apicobasal coordinate
% c.result.pointData.rt: rotational coordinate
% c.result.pointData.rtSin: rotational sine coordinate
% c.result.pointData.rtCos: rotational cosine coordinate
% - c.R: rotation matrix for alignment of heart axes with x,y,z
% - c.cfg: complete configuration struct with all parameters
%
% Dependencies:
% - MMG3D by INP/Inria/U Bordeaux
% https://github.com/MmgTools/mmg
% Tested version: Release 5.4.3, 26 Feb 2020
% - gptoolbox by Alec Jacobson
% https://github.com/alecjacobson/gptoolbox
% Tested version: Commit bee2edb, 20 Aug 2020
% - vtkToolbox by Steffen Schuler
% https://github.com/KIT-IBT/vtkToolbox
% Run dependencies/install.sh to install them from source
%
% Written in 2020 by Steffen Schuler
% Institute of Biomedical Engineering, KIT
% www.ibt.kit.edu
classdef cobiveco < handle
properties (SetAccess = private)
% Configuration parameters
% (default values)
cfg = struct( ...
'inPrefix', '', ...
'outPrefix', '', ...
'exportLevel', 1, ...
'verbose', true, ...
'mappingTol', 0.5, ...
'tol', 1e-8, ...
'maxit', 3000, ...
'truncSeptSur', [20 10], ...
'abExtrapSmooth', 0.25, ...
'mmgSizingParam', [0.1 0.9 1.1], ...
'rvepi', false ...
);
% Mesh 0 (original mesh)
m0 = struct( ...
'vol', [], ...
'sur', [], ...
'surToVol', [], ...
'meanEdgLen', [], ...
'L', [], ...
'tv', [], ...
'tm', [], ...
'tmRVEpi', [], ...
'rtSin', [], ...
'rtCos', [], ...
'rt', [], ...
'ab', [], ...
'debug', [] ...
);
% Mesh 1 (isovalue discretization at boundary between LV and RV)
m1 = struct( ...
'vol', [], ...
'sur', [], ...
'surToVol', [], ...
'meanEdgLen', [], ...
'L', [], ...
'G', [], ...
'massMat', [], ...
'M', [], ...
'tm', [], ...
'tmRVEpi', [], ...
'ridgeLaplace', [], ...
'debug', [] ...
);
% Mesh 2 (isovalue discretization at ridge)
m2 = struct( ...
'vol', [], ...
'sur', [], ...
'surToVol', [], ...
'L', [], ...
'G', [], ...
'M', [], ...
'abLaplace', [], ...
'rtSin', [], ...
'rtCos', [], ...
'rt', [], ...
'debug', [] ...
);
% VTK struct with final coordinates as pointData
result
% Rotation matrix aligning heart axes with x,y,z
R
end
properties (Access = private)
septCurveAnt
septCurvePost
% used to keep track of what has already been computed
available = struct( ...
'mesh0', false, ...
'transventricular', false, ...
'mesh1', false, ...
'transmural', false, ...
'heartAxesAndApex', false, ...
'mesh2', false, ...
'rotational', false, ...
'apicobasal', false ...
);
end
methods
function o = cobiveco(config)
% c = cobiveco(config)
% overwrite default parameters, if provided by the user
fns = fieldnames(config);
for i = 1:numel(fns)
fn = fns{i};
if ~isfield(o.cfg,fn)
error('Unknown parameter ''%s'' found in config.', fn);
end
o.cfg.(fn) = config.(fn);
end
% check mandatory parameters
if ~isfield(config,'inPrefix')
error('Mandatory parameter ''inPrefix'' missing in config.');
end
if o.cfg.exportLevel > 0
if isempty(o.cfg.outPrefix)
error('Mandatory parameter ''outPrefix'' missing in config or empty.');
end
outPath = fileparts(o.cfg.outPrefix);
if ~isempty(outPath) && ~exist(outPath, 'dir')
mkdir(outPath);
end
testfile = sprintf('%stest', o.cfg.outPrefix);
[fid,errmsg] = fopen(testfile, 'w');
if fid==-1
error('Invalid parameter outPrefix. Could not create ''%s'' (%s).', testfile, errmsg);
else
fclose(fid);
delete(testfile);
end
end
% start timer
tic;
% set up paths
mpath = fileparts(mfilename('fullpath'));
addpath(['../functions']);
% if ~exist([mpath '/../dependencies/mmg/build/bin/mmg3d_O3'], 'file')
% error('Dependency ''mmg'' not found. Run dependencies/install_cobiveco.sh to install.');
% end
if ~exist('cotmatrix.m', 'file')
if ~exist(['../dependencies/gptoolbox'], 'dir')
error('Dependency ''gptoolbox'' not found. Run dependencies/install_cobiveco.sh to install.');
end
addpath(['../dependencies/gptoolbox/matrix']);
addpath(['../dependencies/gptoolbox/mesh']);
end
if ~exist('vtkRead.m', 'file')
if ~exist(['../dependencies/vtkToolbox'], 'dir')
error('Dependency ''vtkToolbox'' not found. Run dependencies/install_cobiveco.sh to install.');
end
addpath(['../dependencies/vtkToolbox/MATLAB']);
end
end
function computeAll(o)
% Compute all coordinates
t = toc;
o.prepareMesh0;
o.computeTransventricular;
o.prepareMesh1;
if o.cfg.rvepi
o.computeTransmuralRVEpi;
else
o.computeTransmural;
end
o.computeHeartAxesAndApex;
o.prepareMesh2;
o.computeRotational;
o.computeApicobasal;
o.exportResult;
o.printStatus('Total elapsed time:');
o.printStatus(sprintf('%.1f seconds\n', toc-t), true);
end
% declare methods defined in separate files
prepareMesh0(o)
computeTransventricular(o)
prepareMesh1(o)
computeTransmural(o)
computeTransmrualRVEpi(o)
computeHeartAxesAndApex(o)
prepareMesh2(o)
computeRotational(o)
computeApicobasal(o)
exportResult(o)
end
methods (Access = private)
function printStatus(o, msg, noPadding)
if o.cfg.verbose
if nargin > 2 && noPadding
fprintf('%s', msg);
else
pad = repmat(' ', 1, 41-numel(msg));
fprintf('%s%s', msg, pad);
end
end
end
end
end