Switch to unified view

a b/MATLAB/analysis/analyzeBones.m
1
%ANALYZEBONES checks & analyzes the surface models of the VSD bones.
2
%
3
% AUTHOR: Maximilian C. M. Fischer
4
% COPYRIGHT (C) 2023 Maximilian C. M. Fischer
5
% LICENSE: EUPL v1.2
6
%
7
8
clearvars; close all
9
10
addpath(genpath('..\src'))
11
VSD_addPathes('..\..\..\..\..\')
12
13
% Load subjects & meta data
14
subjectXLSX = '..\res\VSD_Subjects.xlsx';
15
Subjects = readtable(subjectXLSX);
16
NoS = size(Subjects, 1);
17
18
load(['..\..\Bones\' Subjects.ID{1} '.mat'], 'B')
19
NoB = length(B);
20
boneNames = {B.name};
21
clear B
22
23
%% Mesh sanity checks
24
NoCC = nan(NoS, NoB);
25
for s=1:NoS
26
    % Import the bones
27
    load(['..\..\Bones\' Subjects.ID{s} '.mat'], 'B')
28
    for b=1:length(B)
29
        if ~isempty(B(b).mesh)
30
            stats = statistics(B(b).mesh.vertices, B(b).mesh.faces, 'MinArea', 1e-8);
31
            sanityStats = rmfield(stats, {'num_faces', 'num_vertices', 'num_edges', ...
32
                'num_connected_components', 'num_handles', 'euler_characteristic'});
33
            if ~all(full(cell2mat(struct2cell(sanityStats)))==0)
34
                warning(['Failed mesh check for subject ' ...
35
                    Subjects.ID{s} ' (' num2str(s) '): ' B(b).name ' (' num2str(b) ')'])
36
            end
37
            NoCC(s,b) = stats.num_connected_components;
38
        end
39
    end
40
end
41
42
save('NumberOfConnComp.mat','NoCC')
43
44
% Check for intersections of adjacent bones
45
for s=1:NoS
46
    % Import the bones
47
    load(['..\..\Bones\' Subjects.ID{s} '.mat'], 'B')
48
    mesh = concatenateMeshes([B(1:end).mesh]);
49
    stats = statistics(mesh.vertices, mesh.faces);
50
    if stats.num_selfintersecting_pairs ~= 0
51
        error([Subjects.ID{s} ' has intersections between adjacent bones!'])
52
    end
53
end
54
55
%% Mesh volume
56
% Remove incomplete or inconsistent subjects
57
Subjects = Subjects(cellfun(@(x) isempty(strfind(lower(x),'cut off')), Subjects.Comment),:); %#ok<STREMP>
58
Subjects = Subjects(cellfun(@(x) isempty(strfind(lower(x),'total knee')), Subjects.Comment),:); %#ok<STREMP>
59
Subjects = Subjects(cellfun(@(x) isempty(strfind(lower(x),'gender')), Subjects.Comment),:); %#ok<STREMP>
60
Subjects = Subjects(cellfun(@(x) isempty(strfind(lower(x),'conflicting')), Subjects.Comment),:); %#ok<STREMP>
61
Subjects = Subjects(~isnan(Subjects.Weight),:);
62
% Calculate volume of the bone models
63
NoS = size(Subjects, 1);
64
volume = nan(NoS, NoB);
65
for s=1:NoS
66
    % Import the bones
67
    load(['..\..\Bones\' Subjects.ID{s} '.mat'], 'B')
68
    for b=1:length(B)
69
        volume(s,b) = VolumeIntegrate(B(b).mesh.vertices,B(b).mesh.faces);
70
        % volume(s,b) = sum(arrayfun(@(x) meshVolume(x), splitMesh(B(b).mesh)));
71
    end
72
end
73
74
cmm3toccm3 = 0.001;
75
volumeTable = cell2table(cell(NoB,13), 'VariableNames', {...
76
    'Bone name', 'Min.', 'Mean', 'Median', 'Max.',...
77
    'M_Min.', 'M_Mean', 'M_Median', 'M_Max.',...
78
    'F_Min.', 'F_Mean', 'F_Median', 'F_Max.'});
79
[minIdx, maxIdx] = deal(nan(NoS, 1));
80
mIdx = strcmp(Subjects.Sex, 'M');
81
assert(sum(mIdx) == 10)
82
fIdx = strcmp(Subjects.Sex, 'F');
83
assert(sum(fIdx) == 10)
84
assert(sum(fIdx & mIdx) == 0)
85
for b=1:NoB
86
    % All
87
    volumeTable(b,1) = boneNames(b);
88
    [minValue, minIdx(b)] = min(volume(:,b)*cmm3toccm3);
89
    volumeTable{b,2} = {minValue};
90
    volumeTable(b,3) = meanStats(volume(:,b)*cmm3toccm3,'% 1.0f','format','short');
91
    volumeTable(b,4) = medianStats(volume(:,b)*cmm3toccm3,'% 1.0f','format','short');
92
    [maxValue, maxIdx(b)] = max(volume(:,b)*cmm3toccm3);
93
    volumeTable{b,5} = {maxValue};
94
    % Male
95
    [minValue, minIdx(b)] = min(volume(mIdx,b)*cmm3toccm3);
96
    volumeTable{b,6} = {minValue};
97
    volumeTable(b,7) = meanStats(volume(mIdx,b)*cmm3toccm3,'% 1.0f','format','short');
98
    volumeTable(b,8) = medianStats(volume(mIdx,b)*cmm3toccm3,'% 1.0f','format','short');
99
    [maxValue, maxIdx(b)] = max(volume(mIdx,b)*cmm3toccm3);
100
    volumeTable{b,9} = {maxValue};
101
    % Female
102
    [minValue, minIdx(b)] = min(volume(fIdx,b)*cmm3toccm3);
103
    volumeTable{b,10} = {minValue};
104
    volumeTable(b,11) = meanStats(volume(fIdx,b)*cmm3toccm3,'% 1.0f','format','short');
105
    volumeTable(b,12) = medianStats(volume(fIdx,b)*cmm3toccm3,'% 1.0f','format','short');
106
    [maxValue, maxIdx(b)] = max(volume(fIdx,b)*cmm3toccm3);
107
    volumeTable{b,13} = {maxValue};
108
end
109
110
writetable(volumeTable, 'volumeResults.xlsx', 'Sheet','Volume', 'Range','B5',...
111
    'WriteVariableNames',0, 'WriteRowNames',0)
112
113
figure('color','w','numbertitle','off','name','Bone Volume')
114
bph = boxplot(volume*cmm3toccm3,boneNames,'LabelOrientation','inline');
115
set(findobj(get(bph(1), 'parent'), 'type', 'text'), 'interpreter','tex');
116
ylabel('[cm³]')
117
118
%% Age, weight, height
119
SubjectStats(1,1) = meanStats(Subjects.Age,'% 1.0f','format','short');
120
SubjectStats(1,2) = meanStats(Subjects.Weight,'% 1.0f','format','short');
121
SubjectStats(1,3) = meanStats(Subjects.Height,'% 1.1f','format','short');
122
SubjectStats(2,1) = medianStats(Subjects.Age,'% 1.0f','format','short');
123
SubjectStats(2,2) = medianStats(Subjects.Weight,'% 1.0f','format','short');
124
SubjectStats(2,3) = medianStats(Subjects.Height,'% 1.1f','format','short');
125
126
%% Number of vertices
127
NoV = nan(NoS, NoB);
128
for s=1:NoS
129
    % Import the bones
130
    load(['..\..\Bones\' Subjects.ID{s} '.mat'], 'B')
131
    for b=1:length(B)
132
        NoV(s,b) = size(B(b).mesh.vertices,1);
133
    end
134
end
135
136
figure('color','w','numbertitle','off','name','Number of mesh vertices')
137
bph = boxplot(NoV,boneNames,'LabelOrientation','inline');
138
set(findobj(get(bph(1), 'parent'), 'type', 'text'), 'interpreter','tex');
139
140
% [List.f, List.p] = matlab.codetools.requiredFilesAndProducts([mfilename '.m']);
141
% List.f = List.f'; List.p = List.p';