|
a |
|
b/combinedDeepLearningActiveContour/functions/compare_contours.m |
|
|
1 |
function compare_result = compare_contours(dicom_path,manual_contour_path,auto_contour_path,para) |
|
|
2 |
%COMPARE_CONTOURS Compare manual rawn contours with auto contours |
|
|
3 |
% COMPARE_CONTOURS(DICOM_PATH,MANUAL_CONTOUR_PATH,AUTO_CONTOUR_PATH) |
|
|
4 |
% |
|
|
5 |
% Copyright: Imaging Research, Sunnybrook Health Sciences Centre, Toronto, ON, Canada |
|
|
6 |
% Author: Perry Radau and Yingli Lu |
|
|
7 |
% Email: perry.radau@gmail.com |
|
|
8 |
% Version: 1.0 |
|
|
9 |
% Date: 2009/06/29 |
|
|
10 |
|
|
|
11 |
% OUTPUT: compare_result is a struct with fields of |
|
|
12 |
%----------------------------------------------------- |
|
|
13 |
% auto_number_i: total number of auto inside contours |
|
|
14 |
% auto_number_o: total number auto outside contours; |
|
|
15 |
% manual_number_i: total number manual inside contours |
|
|
16 |
% manual_number_o: total number manual outside contours |
|
|
17 |
% detect_percent_i: percent of detected number of auto inside contours compared to total number of manual inside contours |
|
|
18 |
% detect_percent_o: percent of detected number of auto outside contours compared to total number of manual outside contours |
|
|
19 |
% good_percent_i: percent of good auto inside contours (a good contour has an average distance smaller than para.dist_limit) |
|
|
20 |
% good_percent_o: percent of good auto outside contours |
|
|
21 |
% auto_ef_pic: auto contour's ejection fraction, '_pic' means Papillary Included in the LV Cavity |
|
|
22 |
% auto_ef_pim: auto contour's ejection fraction, '_pim' means Papillary Included in the Myocardium |
|
|
23 |
% auto_lvm_pic: auto contour's lv mass |
|
|
24 |
% auto_lvm_pim: |
|
|
25 |
% manual_ef_pic: auto contour's ejection fraction |
|
|
26 |
% manual_ef_pim: |
|
|
27 |
% manual_lvm_pic: manual contour's lv mass |
|
|
28 |
% manual_lvm_pim: |
|
|
29 |
% avg_dist_i: average distance of inside contours |
|
|
30 |
% avg_dist_o: average distance of outside contours |
|
|
31 |
% avg_dm_i: average dice metric of inside contours |
|
|
32 |
% avg_dm_o: average dice metric of outside contours |
|
|
33 |
|
|
|
34 |
% auto_missing_index_i: missing auto inside contours' index |
|
|
35 |
% auto_missing_index_o: missing auto outside contours' index |
|
|
36 |
% auto_bad_index_i: bad auto inside contours' index (a bad contour has an average distance larger than para.dist_limit) |
|
|
37 |
% auto_bad_index_o: bad auto outside contours' index |
|
|
38 |
%----------------------------------------------------- |
|
|
39 |
|
|
|
40 |
%-initialize |
|
|
41 |
compare_result = []; |
|
|
42 |
|
|
|
43 |
%-check path |
|
|
44 |
if ~exist(dicom_path,'dir') |
|
|
45 |
disp([dicom_path ' :NOT exist!']) |
|
|
46 |
return; |
|
|
47 |
end |
|
|
48 |
if ~exist(manual_contour_path,'dir') |
|
|
49 |
disp([manual_contour_path ' :NOT exist!']) |
|
|
50 |
return; |
|
|
51 |
end |
|
|
52 |
if ~exist(auto_contour_path,'dir') |
|
|
53 |
disp([auto_contour_path ' :NOT exist!']) |
|
|
54 |
return; |
|
|
55 |
end |
|
|
56 |
|
|
|
57 |
%-check files |
|
|
58 |
dicom_files = dir([dicom_path filesep '*.dcm']); |
|
|
59 |
if isempty(dicom_files) |
|
|
60 |
disp([dicom_path ' :NO dicom files!']) |
|
|
61 |
return; |
|
|
62 |
end |
|
|
63 |
manual_contour_files = dir([manual_contour_path filesep '*.txt']); |
|
|
64 |
if isempty(manual_contour_files) |
|
|
65 |
disp([manual_contour_path ' :NO manual contours!']) |
|
|
66 |
return; |
|
|
67 |
end |
|
|
68 |
auto_contour_files = dir([auto_contour_path filesep '*.txt']); |
|
|
69 |
if isempty(auto_contour_files) |
|
|
70 |
disp([auto_contour_path ' :NO auto contours!']) |
|
|
71 |
return; |
|
|
72 |
end |
|
|
73 |
|
|
|
74 |
%-dicominfo |
|
|
75 |
try |
|
|
76 |
dicom_filename = dicom_files(1).name; %use the first dicom file. |
|
|
77 |
full_dicom_filename = [dicom_path filesep dicom_filename]; |
|
|
78 |
dicom_meta= dicominfo(full_dicom_filename); |
|
|
79 |
|
|
|
80 |
para.width = dicom_meta.Width;%image width |
|
|
81 |
para.height = dicom_meta.Height;%image height |
|
|
82 |
para.pixel_spacing = dicom_meta.PixelSpacing; % mm |
|
|
83 |
para.thickness = dicom_meta.SliceThickness;% mm |
|
|
84 |
para.gap = dicom_meta.SpacingBetweenSlices - para.thickness; % mm |
|
|
85 |
para.phase_number = dicom_meta.CardiacNumberOfImages; %numer of phases |
|
|
86 |
para.image_number = length(dicom_files);%number of images |
|
|
87 |
catch |
|
|
88 |
s = lasterror; |
|
|
89 |
disp(s.message); |
|
|
90 |
return |
|
|
91 |
end |
|
|
92 |
|
|
|
93 |
%-calc percent of auto contours compared to manual contours |
|
|
94 |
manual_number_i = 0; %number of manual inside contours |
|
|
95 |
manual_number_o = 0; %number of manual outside contours |
|
|
96 |
manual_index_i = []; %index of manual inside contours |
|
|
97 |
manual_index_o = []; %index of manual outside contours |
|
|
98 |
|
|
|
99 |
%count manual contours |
|
|
100 |
for ix = 1:para.image_number |
|
|
101 |
sindex = add_zero_index(ix,para.digit_length); |
|
|
102 |
%inside contour |
|
|
103 |
full_icontour_filename = get_contour_filename(manual_contour_path, para.name_prefix, sindex,para.inside_contour_mode,para.manual_seg_mode); |
|
|
104 |
if exist(full_icontour_filename,'file') |
|
|
105 |
fileinfo_i = dir(full_icontour_filename); |
|
|
106 |
if fileinfo_i.bytes ~= 0 % not null |
|
|
107 |
manual_number_i = manual_number_i + 1; |
|
|
108 |
manual_index_i = [manual_index_i; ix]; %#ok<AGROW> |
|
|
109 |
end |
|
|
110 |
end |
|
|
111 |
%outside contour |
|
|
112 |
full_ocontour_filename = get_contour_filename(manual_contour_path,para.name_prefix, sindex, para.outside_contour_mode,para.manual_seg_mode); |
|
|
113 |
if exist(full_ocontour_filename,'file') |
|
|
114 |
fileinfo_o = dir(full_ocontour_filename); |
|
|
115 |
if fileinfo_o.bytes ~= 0 % not null |
|
|
116 |
manual_number_o = manual_number_o + 1; |
|
|
117 |
manual_index_o = [manual_index_o; ix]; %#ok<AGROW> |
|
|
118 |
end |
|
|
119 |
end |
|
|
120 |
end |
|
|
121 |
|
|
|
122 |
if (manual_number_i == 0) |
|
|
123 |
disp([manual_contour_path ' :NO inside contours!']) |
|
|
124 |
return; |
|
|
125 |
end |
|
|
126 |
if (manual_number_o == 0) |
|
|
127 |
disp([manual_contour_path ' :NO outside contours!']) |
|
|
128 |
return; |
|
|
129 |
end |
|
|
130 |
|
|
|
131 |
para.manual_index_i = manual_index_i; |
|
|
132 |
para.manual_index_o = manual_index_o; |
|
|
133 |
|
|
|
134 |
%count auto inside contours according to manual_index_i |
|
|
135 |
auto_number_i = 0; |
|
|
136 |
auto_missing_index_i = []; |
|
|
137 |
for ix = 1:length(manual_index_i) |
|
|
138 |
sindex = add_zero_index(manual_index_i(ix),para.digit_length); |
|
|
139 |
full_icontour_filename = get_contour_filename(auto_contour_path,para.name_prefix, sindex, para.inside_contour_mode, para.auto_seg_mode); |
|
|
140 |
if exist(full_icontour_filename,'file') |
|
|
141 |
fileinfo_i = dir(full_icontour_filename); |
|
|
142 |
if fileinfo_i.bytes ~= 0 % not null |
|
|
143 |
auto_number_i = auto_number_i + 1; |
|
|
144 |
else |
|
|
145 |
auto_missing_index_i = [auto_missing_index_i manual_index_i(ix)]; %#ok<AGROW> |
|
|
146 |
end |
|
|
147 |
else |
|
|
148 |
auto_missing_index_i = [auto_missing_index_i manual_index_i(ix)]; %#ok<AGROW> |
|
|
149 |
end |
|
|
150 |
end |
|
|
151 |
|
|
|
152 |
if (auto_number_i == 0) |
|
|
153 |
disp([auto_contour_path ' :NO inside contours!']) |
|
|
154 |
return; |
|
|
155 |
end |
|
|
156 |
|
|
|
157 |
%count auto outside contours according to manual_index_o |
|
|
158 |
auto_number_o = 0; |
|
|
159 |
auto_missing_index_o = []; |
|
|
160 |
for ix = 1:length(manual_index_o) |
|
|
161 |
sindex = add_zero_index(manual_index_o(ix),para.digit_length); |
|
|
162 |
full_ocontour_filename = get_contour_filename(auto_contour_path,para.name_prefix, sindex, para.outside_contour_mode,para.auto_seg_mode); |
|
|
163 |
if exist(full_ocontour_filename,'file') |
|
|
164 |
fileinfo_o = dir(full_ocontour_filename); |
|
|
165 |
if fileinfo_o.bytes ~= 0 % not null |
|
|
166 |
auto_number_o = auto_number_o + 1; |
|
|
167 |
else |
|
|
168 |
auto_missing_index_o = [auto_missing_index_o manual_index_o(ix)]; %#ok<AGROW> |
|
|
169 |
end |
|
|
170 |
else |
|
|
171 |
auto_missing_index_o = [auto_missing_index_o manual_index_o(ix)]; %#ok<AGROW> |
|
|
172 |
end |
|
|
173 |
end |
|
|
174 |
|
|
|
175 |
if (auto_number_o == 0) |
|
|
176 |
disp([auto_contour_path ' :NO outside contours!']) |
|
|
177 |
return; |
|
|
178 |
end |
|
|
179 |
|
|
|
180 |
%-calc perpendicular distance & Dice Metric |
|
|
181 |
%inside contours |
|
|
182 |
avg_dist_i=ones(length(manual_index_i),1)*para.init_value; |
|
|
183 |
dm_i = ones(length(manual_index_i),1)*para.init_value; |
|
|
184 |
auto_bad_index_i = []; |
|
|
185 |
|
|
|
186 |
for ix = 1:length(manual_index_i) |
|
|
187 |
sindex = add_zero_index(manual_index_i(ix),para.digit_length); |
|
|
188 |
% manual contour |
|
|
189 |
manual_icontour_filename = get_contour_filename(manual_contour_path,para.name_prefix,sindex, para.inside_contour_mode,para.manual_seg_mode); |
|
|
190 |
% auto contour |
|
|
191 |
auto_icontour_filename = get_contour_filename(auto_contour_path,para.name_prefix,sindex, para.inside_contour_mode,para.auto_seg_mode); |
|
|
192 |
% if both exist, compare |
|
|
193 |
if (exist(manual_icontour_filename,'file') && exist(auto_icontour_filename,'file')) |
|
|
194 |
manual_xy = load(manual_icontour_filename); |
|
|
195 |
auto_xy = load(auto_icontour_filename); |
|
|
196 |
|
|
|
197 |
max_auto_x = max(auto_xy(:,1)); |
|
|
198 |
max_auto_y = max(auto_xy(:,2)); |
|
|
199 |
min_auto_xy = min(auto_xy(:)); |
|
|
200 |
|
|
|
201 |
if (size(auto_xy,1) > 15 && min_auto_xy > 1 && max_auto_x < para.width && max_auto_y < para.height) %% if "point number of auto contour" < 1, may lead to difficulty to find a subset of reference that correspond to the current target |
|
|
202 |
try |
|
|
203 |
%perpendicular distance |
|
|
204 |
if (para.auto_based_noraml == 1) %calc normal based on auto contours, manual constour is the reference contour |
|
|
205 |
avg_dist_i(ix) =calc_dist(auto_xy,manual_xy,auto_icontour_filename,para); |
|
|
206 |
else |
|
|
207 |
avg_dist_i(ix) =calc_dist(manual_xy,auto_xy,auto_icontour_filename,para); |
|
|
208 |
end |
|
|
209 |
|
|
|
210 |
%dice metric |
|
|
211 |
dm_i(ix) = calc_dm(auto_xy,manual_xy,para); |
|
|
212 |
catch |
|
|
213 |
s = lasterror; |
|
|
214 |
disp(s.message); |
|
|
215 |
continue; |
|
|
216 |
end |
|
|
217 |
end |
|
|
218 |
end |
|
|
219 |
end |
|
|
220 |
|
|
|
221 |
if max(avg_dist_i) == para.init_value |
|
|
222 |
return; |
|
|
223 |
end |
|
|
224 |
auto_bad_ix_i =find(avg_dist_i >= para.dist_limit); |
|
|
225 |
if ~isempty(auto_bad_ix_i) |
|
|
226 |
auto_bad_index_i = manual_index_i(auto_bad_ix_i); |
|
|
227 |
end |
|
|
228 |
auto_good_ix_i =find(avg_dist_i < para.dist_limit & avg_dist_i ~= para.init_value); |
|
|
229 |
auto_good_index_i = manual_index_i(auto_good_ix_i); |
|
|
230 |
para.auto_good_index_i = auto_good_index_i ; |
|
|
231 |
|
|
|
232 |
%outside contours |
|
|
233 |
auto_bad_index_o = []; |
|
|
234 |
avg_dist_o=ones(length(manual_index_o),1)*para.init_value; |
|
|
235 |
dm_o = ones(length(manual_index_o),1)*para.init_value; |
|
|
236 |
|
|
|
237 |
for ix = 1:length(manual_index_o) |
|
|
238 |
sindex = add_zero_index(manual_index_o(ix),para.digit_length); |
|
|
239 |
% manual contour |
|
|
240 |
manual_ocontour_filename = get_contour_filename(manual_contour_path,para.name_prefix,sindex, para.outside_contour_mode,para.manual_seg_mode); |
|
|
241 |
% auto contour |
|
|
242 |
auto_ocontour_filename = get_contour_filename(auto_contour_path,para.name_prefix,sindex, para.outside_contour_mode,para.auto_seg_mode); |
|
|
243 |
% if both exist, compare |
|
|
244 |
if (exist(manual_ocontour_filename,'file') && exist(auto_ocontour_filename,'file')) |
|
|
245 |
manual_xy = load(manual_ocontour_filename); |
|
|
246 |
auto_xy = load(auto_ocontour_filename); |
|
|
247 |
|
|
|
248 |
max_auto_x = max(auto_xy(:,1)); |
|
|
249 |
max_auto_y = max(auto_xy(:,2)); |
|
|
250 |
min_auto_xy = min(auto_xy(:)); |
|
|
251 |
|
|
|
252 |
if (size(auto_xy,1) > 15 && min_auto_xy > 1 && max_auto_x < para.width && max_auto_y < para.height) % if "point number of auto contour" < 1, may lead to difficulty to find a subset of reference that correspond to the current target |
|
|
253 |
try |
|
|
254 |
%perpendicular distance |
|
|
255 |
if (para.auto_based_noraml == 1) %calc normal based on auto contours, manual constour is the reference contour |
|
|
256 |
avg_dist_o(ix) =calc_dist(auto_xy,manual_xy,auto_ocontour_filename,para); |
|
|
257 |
else |
|
|
258 |
avg_dist_o(ix) =calc_dist(manual_xy,auto_xy,auto_ocontour_filename,para); |
|
|
259 |
end |
|
|
260 |
|
|
|
261 |
%dice metric |
|
|
262 |
dm_o(ix) = calc_dm(auto_xy,manual_xy,para); |
|
|
263 |
catch |
|
|
264 |
s = lasterror; |
|
|
265 |
disp(s.message); |
|
|
266 |
continue; |
|
|
267 |
end |
|
|
268 |
end |
|
|
269 |
end |
|
|
270 |
end |
|
|
271 |
|
|
|
272 |
if max(avg_dist_o) == para.init_value |
|
|
273 |
return; |
|
|
274 |
end |
|
|
275 |
auto_bad_ix_o =find(avg_dist_o >= para.dist_limit); |
|
|
276 |
if ~isempty(auto_bad_ix_o) |
|
|
277 |
auto_bad_index_o = manual_index_o(auto_bad_ix_o); |
|
|
278 |
end |
|
|
279 |
% if auto_good_ix_o is empty, mean(dm_o(auto_good_ix_o)) can be a NAN (undefined) |
|
|
280 |
auto_good_ix_o = find(avg_dist_o < para.dist_limit & avg_dist_o ~= para.init_value); |
|
|
281 |
auto_good_index_o = manual_index_o(auto_good_ix_o); |
|
|
282 |
para.auto_good_index_o = auto_good_index_o; |
|
|
283 |
|
|
|
284 |
%-calc ejection fraction and lv mass |
|
|
285 |
para.auto = false; |
|
|
286 |
lv_manual = calc_clinical_para(dicom_path, manual_contour_path, para); |
|
|
287 |
|
|
|
288 |
para.auto = true; |
|
|
289 |
lv_auto = calc_clinical_para(dicom_path, auto_contour_path, para,1,para.image_number/para.phase_number,lv_manual.es,lv_manual.ed); |
|
|
290 |
|
|
|
291 |
%record result |
|
|
292 |
compare_result.auto_number_i = auto_number_i; %total auto inside contours |
|
|
293 |
compare_result.auto_number_o = auto_number_o; |
|
|
294 |
compare_result.manual_number_i = manual_number_i; |
|
|
295 |
compare_result.manual_number_o = manual_number_o; |
|
|
296 |
compare_result.detect_percent_i = auto_number_i / manual_number_i * 100; %percent of detected auto inside contours |
|
|
297 |
compare_result.detect_percent_o = auto_number_o / manual_number_o * 100; |
|
|
298 |
compare_result.auto_missing_index_i = auto_missing_index_i; % missing auto inside contours |
|
|
299 |
compare_result.auto_missing_index_o = auto_missing_index_o; |
|
|
300 |
|
|
|
301 |
compare_result.auto_bad_index_i = auto_bad_index_i; %bad auto inside contours' index (average distance larger than para.dist_limit) |
|
|
302 |
compare_result.auto_bad_index_o = auto_bad_index_o; |
|
|
303 |
compare_result.good_percent_i = (auto_number_i - length(auto_bad_index_i)) /manual_number_i * 100; %percent of good auto inside contours |
|
|
304 |
compare_result.good_percent_o = (auto_number_o - length(auto_bad_index_o)) /manual_number_o * 100; |
|
|
305 |
|
|
|
306 |
compare_result.auto_ef_pic = lv_auto.ef_pic;%auto's ef, '_pic' means Papillary Included in the LV Cavity |
|
|
307 |
compare_result.auto_ef_pim = lv_auto.ef_pim; %'_pim' means Papillary Included in the Myocardium |
|
|
308 |
compare_result.auto_lvm_pic = lv_auto.lvm_pic; %auto's lv mass |
|
|
309 |
compare_result.auto_lvm_pim = lv_auto.lvm_pim; |
|
|
310 |
|
|
|
311 |
compare_result.manual_ef_pic = lv_manual.ef_pic; %manual's ef |
|
|
312 |
compare_result.manual_ef_pim = lv_manual.ef_pim; |
|
|
313 |
compare_result.manual_lvm_pic = lv_manual.lvm_pic; %manual's lv mass |
|
|
314 |
compare_result.manual_lvm_pim = lv_manual.lvm_pim; |
|
|
315 |
|
|
|
316 |
compare_result.avg_dist_i = mean(avg_dist_i(auto_good_ix_i)); %average distance |
|
|
317 |
compare_result.avg_dist_o = mean(avg_dist_o(auto_good_ix_o)); |
|
|
318 |
compare_result.avg_dm_i = mean(dm_i(auto_good_ix_i)); %average dice metric |
|
|
319 |
compare_result.avg_dm_o = mean(dm_o(auto_good_ix_o)); |
|
|
320 |
|
|
|
321 |
end %compare contour |
|
|
322 |
|
|
|
323 |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
324 |
%sub functions |
|
|
325 |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
326 |
function sindex = add_zero_index(index,digit_length) |
|
|
327 |
%add zero before index with total length of digit_length, for instance, change 20 to 0020. |
|
|
328 |
sindex=int2str(index); |
|
|
329 |
while length(sindex) < digit_length |
|
|
330 |
sindex = ['0', sindex]; %#ok<AGROW> |
|
|
331 |
end |
|
|
332 |
end |
|
|
333 |
|
|
|
334 |
function full_contour_filename = get_contour_filename(contour_path, name_prefix, sindex, contour_mode, seg_mode) |
|
|
335 |
%gef contour filename with full path |
|
|
336 |
full_contour_filename = [contour_path filesep name_prefix, sindex, '-',contour_mode,'-',seg_mode, '.txt']; |
|
|
337 |
end |
|
|
338 |
|
|
|
339 |
function varargout = calc_dist(target_xy,reference_xy,contour_filename,para) |
|
|
340 |
%calc perpendicular distance between target and reference contours |
|
|
341 |
|
|
|
342 |
%plot contours |
|
|
343 |
if para.save_figure |
|
|
344 |
%create distance figure folder |
|
|
345 |
if (para.auto_based_noraml == 1) |
|
|
346 |
str = strrep(contour_filename, para.auto_contour_foldername, [para.distance_figure_foldername '_auto_based_normal']); |
|
|
347 |
else |
|
|
348 |
str = strrep(contour_filename, para.auto_contour_foldername, [para.distance_figure_foldername '_manual_based_normal']); |
|
|
349 |
end |
|
|
350 |
|
|
|
351 |
[pathstr, name, ext, versn] = fileparts(str); |
|
|
352 |
if (~exist(pathstr,'dir')) |
|
|
353 |
mkdir(pathstr) |
|
|
354 |
end |
|
|
355 |
|
|
|
356 |
%read dicom image |
|
|
357 |
str_dicom = strrep(contour_filename, [para.auto_contour_foldername filesep para.auto_contour_subfoldername],para.dicom_foldername); |
|
|
358 |
dicom_filename = [str_dicom(1:end-18),'.dcm']; %IM-0001-XXXX-icontour-auto.txt |
|
|
359 |
raw_image = dicomread(dicom_filename); |
|
|
360 |
%show box |
|
|
361 |
min_showbox_x = round(min([target_xy(:,1); reference_xy(:,1)])); |
|
|
362 |
max_showbox_x = round(max([target_xy(:,1); reference_xy(:,1)])); |
|
|
363 |
min_showbox_y = round(min([target_xy(:,2); reference_xy(:,2)])); |
|
|
364 |
max_showbox_y = round(max([target_xy(:,2); reference_xy(:,2)])); |
|
|
365 |
%validate |
|
|
366 |
if min_showbox_x <1 |
|
|
367 |
min_showbox_x = 1; |
|
|
368 |
end |
|
|
369 |
|
|
|
370 |
if min_showbox_y <1 |
|
|
371 |
min_showbox_y = 1; |
|
|
372 |
end |
|
|
373 |
|
|
|
374 |
showbox_offset= 3; |
|
|
375 |
if ((min_showbox_x(1)-showbox_offset)<1 || (min_showbox_y(1)-showbox_offset)<1 || (max_showbox_x(1)+showbox_offset)>size(raw_image,2) || (max_showbox_y(1)+showbox_offset)>size(raw_image,1)) |
|
|
376 |
showbox_offset = 0; |
|
|
377 |
end |
|
|
378 |
%crop image |
|
|
379 |
croped_image = raw_image(min_showbox_y(1)-showbox_offset:max_showbox_y(1)+showbox_offset,min_showbox_x(1)-showbox_offset:max_showbox_x(1)+showbox_offset); |
|
|
380 |
|
|
|
381 |
%show image |
|
|
382 |
h = figure; |
|
|
383 |
set(h,'Visible','off'); |
|
|
384 |
axis image; clf; |
|
|
385 |
imshow(croped_image,[],'InitialMagnification',1000,'Border','tight') |
|
|
386 |
|
|
|
387 |
%plot contours |
|
|
388 |
|
|
|
389 |
if (para.auto_based_noraml == 1) %calc normal based on auto contours, manual constour is the reference contour |
|
|
390 |
hold on; plot(target_xy(:,1)-min_showbox_x(1)+showbox_offset,target_xy(:,2)-min_showbox_y(1)+showbox_offset,'r.-'); |
|
|
391 |
hold on; plot(reference_xy(:,1)-min_showbox_x(1)+showbox_offset,reference_xy(:,2)-min_showbox_y(1)+showbox_offset,'g.-'); |
|
|
392 |
else |
|
|
393 |
hold on; plot(reference_xy(:,1)-min_showbox_x(1)+showbox_offset,reference_xy(:,2)-min_showbox_y(1)+showbox_offset,'r.-'); |
|
|
394 |
hold on; plot(target_xy(:,1)-min_showbox_x(1)+showbox_offset,target_xy(:,2)-min_showbox_y(1)+showbox_offset,'g.-'); |
|
|
395 |
end |
|
|
396 |
|
|
|
397 |
end |
|
|
398 |
|
|
|
399 |
%check closed contours |
|
|
400 |
if target_xy(1,1) == target_xy(end,1) && target_xy(1,2) == target_xy(end,2) |
|
|
401 |
target_xy = target_xy(1:end-1,:); |
|
|
402 |
end |
|
|
403 |
if reference_xy(1,1) == reference_xy(end,1) && reference_xy(1,2) == reference_xy(end,2) |
|
|
404 |
reference_xy = reference_xy(1:end-1,:); |
|
|
405 |
end |
|
|
406 |
|
|
|
407 |
%for finding a subset of reference that correspond to the current target |
|
|
408 |
target_mask = poly2mask (target_xy(:,1),target_xy(:,2),double(para.width),double(para.height)); |
|
|
409 |
reference_mask = poly2mask (reference_xy(:,1),reference_xy(:,2),double(para.width),double(para.height)); |
|
|
410 |
|
|
|
411 |
%centroid of refernece mask |
|
|
412 |
reference_cen = regionprops(bwlabel(reference_mask),'Centroid'); |
|
|
413 |
reference_cen_x = round(reference_cen.Centroid(1)); |
|
|
414 |
reference_cen_y = round(reference_cen.Centroid(2)); |
|
|
415 |
|
|
|
416 |
%check if target mask's centroid is in refernece's mask |
|
|
417 |
if target_mask(reference_cen_y,reference_cen_x) ==1 |
|
|
418 |
target_cen = reference_cen; |
|
|
419 |
else |
|
|
420 |
target_cen = regionprops(bwlabel(target_mask),'Centroid'); |
|
|
421 |
end |
|
|
422 |
|
|
|
423 |
%calc degrees between contours points and centroid |
|
|
424 |
target_degree = atan2( target_xy(:,2)-target_cen.Centroid(2),target_xy(:,1)-target_cen.Centroid(1))*180/pi; |
|
|
425 |
reference_degree = atan2( reference_xy(:,2)-reference_cen.Centroid(2),reference_xy(:,1)-reference_cen.Centroid(1))*180/pi; |
|
|
426 |
|
|
|
427 |
%initialize |
|
|
428 |
distance=zeros(size(target_xy,1),1); |
|
|
429 |
|
|
|
430 |
for idx = 1: size(target_xy,1) |
|
|
431 |
%Get 3 points,[left current right] |
|
|
432 |
if idx == 1 |
|
|
433 |
target_xy_3 = [target_xy(end,:); target_xy(idx:idx+1,:)]; |
|
|
434 |
elseif (idx == size(target_xy,1)) |
|
|
435 |
target_xy_3 = [target_xy(end-1:end,:); target_xy(1,:)]; |
|
|
436 |
else |
|
|
437 |
target_xy_3 = target_xy(idx-1:idx+1,:); |
|
|
438 |
end |
|
|
439 |
|
|
|
440 |
%current target to reference distance |
|
|
441 |
target_curr_x = target_xy_3(2,1); |
|
|
442 |
target_curr_y = target_xy_3(2,2); |
|
|
443 |
|
|
|
444 |
%find a subset of reference that correspond to the current target |
|
|
445 |
if ( abs(target_degree(idx)) >= 150 ) |
|
|
446 |
reference_xy_subset = reference_xy(find(abs(reference_degree)>=130),:); |
|
|
447 |
else |
|
|
448 |
reference_xy_subset = reference_xy(find(abs(reference_degree-target_degree(idx))<50),:); |
|
|
449 |
end |
|
|
450 |
|
|
|
451 |
%plot reference_xy_subset |
|
|
452 |
%hold on; plot(reference_xy_subset(:,1)-min_showbox_x(1)+showbox_offset, reference_xy_subset(:,2)-min_showbox_y(1)+showbox_offset ,'r*') |
|
|
453 |
|
|
|
454 |
%fit line |
|
|
455 |
target_X = target_xy_3(:,1); |
|
|
456 |
target_Y = target_xy_3(:,2); |
|
|
457 |
|
|
|
458 |
%3 points are the same |
|
|
459 |
if ( sum(abs(target_X - target_X(2))) + sum(abs(target_Y - target_Y(2))) == 0 ) |
|
|
460 |
continue; |
|
|
461 |
end |
|
|
462 |
|
|
|
463 |
target_LINE = [target_X ones(size(target_X))] \ target_Y; %left division-least squares, %y=LINE(1)*x+LINE(2) |
|
|
464 |
target_LINE_inv = [target_Y ones(size(target_Y))] \ target_X; %left division-least squares, %x=LINE(1)*y+LINE(2) Reverse the coordinates. |
|
|
465 |
|
|
|
466 |
%if the line is very close to parallel with the Y axis, and the residuals for the fit |
|
|
467 |
%improve by reversing the coordinates, then use the line found by |
|
|
468 |
%reversing the coordinates. |
|
|
469 |
if ( abs(target_LINE_inv(1))< 0.1 && sum(abs([target_Y ones(size(target_Y))] * target_LINE_inv - target_X)) < sum(abs([target_X ones(size(target_X))] * target_LINE - target_Y))) |
|
|
470 |
target_LINE = 1./(target_LINE_inv + 0.00001) ; |
|
|
471 |
%hold on; plot(target_X- min_showbox_x(1) + showbox_offset,target_Y - min_showbox_y(1) + showbox_offset,'Y*') |
|
|
472 |
end |
|
|
473 |
|
|
|
474 |
%Determine the line normal to the above tangent line (target_LINE). |
|
|
475 |
%Normal line: Ax+By+C=0; |
|
|
476 |
if abs(target_LINE(1)) < 0.01 %fit line parallel to X axis, normal parallel to Y axis |
|
|
477 |
A_N = 1; |
|
|
478 |
B_N = 0; |
|
|
479 |
C_N = -target_curr_x; |
|
|
480 |
reference_xy_subset_to_normal_distance = abs(reference_xy_subset(:,1)- target_curr_x); |
|
|
481 |
elseif abs(target_LINE(1)) > 100 %fit line parallel to Y axis, normal parallel to X axis |
|
|
482 |
A_N = 0; |
|
|
483 |
B_N = 1; |
|
|
484 |
C_N = -target_curr_y; |
|
|
485 |
reference_xy_subset_to_normal_distance = abs(reference_xy_subset(:,2)- target_curr_y); |
|
|
486 |
else |
|
|
487 |
A_N = -1/target_LINE(1); |
|
|
488 |
B_N = -1; |
|
|
489 |
C_N = target_curr_y - A_N * target_curr_x; |
|
|
490 |
reference_xy_subset_to_normal_distance = abs( (A_N*reference_xy_subset(:,1) + B_N*reference_xy_subset(:,2) + C_N))/sqrt(A_N^2 + B_N^2);%normal: y=N(1)*x+N(2); |
|
|
491 |
end |
|
|
492 |
|
|
|
493 |
%find reference point most close to normal |
|
|
494 |
[min_dist_subset, min_idx_subset] = min(reference_xy_subset_to_normal_distance); |
|
|
495 |
|
|
|
496 |
%if there are more than one min distance points, choose the one most close to current target |
|
|
497 |
min_idx_subset_1= find(reference_xy_subset_to_normal_distance <= 0.5); |
|
|
498 |
|
|
|
499 |
if (length(min_idx_subset_1)>=2) |
|
|
500 |
min_dist_subset_to_target_curr_distance = sqrt((target_curr_x - reference_xy_subset(min_idx_subset_1,1)).^2 + (target_curr_y - reference_xy_subset(min_idx_subset_1,2)).^2); |
|
|
501 |
[min_dist_subset, min_idx_subset_2] = min(min_dist_subset_to_target_curr_distance); |
|
|
502 |
reference_to_min_dist_subset_distance = sqrt((reference_xy(:,1) - reference_xy_subset(min_idx_subset_1(min_idx_subset_2),1)).^2 + (reference_xy(:,2) - reference_xy_subset(min_idx_subset_1(min_idx_subset_2),2)).^2); |
|
|
503 |
else |
|
|
504 |
reference_to_min_dist_subset_distance = sqrt((reference_xy(:,1) - reference_xy_subset(min_idx_subset,1)).^2 + (reference_xy(:,2) - reference_xy_subset(min_idx_subset,2)).^2); |
|
|
505 |
end |
|
|
506 |
|
|
|
507 |
min_idx_temp = find(reference_to_min_dist_subset_distance == 0); |
|
|
508 |
min_idx = min_idx_temp(1); |
|
|
509 |
|
|
|
510 |
%Get 3 points of reference,[left most-close-to-normal right] |
|
|
511 |
if min_idx == 1 |
|
|
512 |
reference_xy_3 = [reference_xy(end,:); reference_xy(min_idx:min_idx+1,:)]; |
|
|
513 |
elseif (min_idx == size(reference_xy,1)) |
|
|
514 |
reference_xy_3 = [reference_xy(end-1:end,:); reference_xy(1,:)]; |
|
|
515 |
else |
|
|
516 |
reference_xy_3 = reference_xy(min_idx-1:min_idx+1,:); |
|
|
517 |
end |
|
|
518 |
|
|
|
519 |
%fit reference line: Ax+By+C=0; |
|
|
520 |
reference_X = reference_xy_3(:,1); |
|
|
521 |
reference_Y = reference_xy_3(:,2); |
|
|
522 |
reference_LINE = [reference_X ones(size(reference_X))] \ reference_Y; %left division-least squares, %y=LINE(1)*x+LINE(2) |
|
|
523 |
|
|
|
524 |
reference_LINE_inv = [reference_Y ones(size(reference_Y))] \ reference_X; %left division-least squares, %y=LINE(1)*x+LINE(2) |
|
|
525 |
|
|
|
526 |
%fit line parallel to Y axis, |
|
|
527 |
if ( abs(reference_LINE_inv(1))< 0.1 && sum(abs([reference_Y ones(size(reference_Y))] * reference_LINE_inv - reference_X)) < sum(abs([reference_X ones(size(reference_X))] * reference_LINE - reference_Y))) |
|
|
528 |
reference_LINE = 1./(reference_LINE_inv + 0.00001) ; |
|
|
529 |
%hold on; plot(target_X- min_showbox_x(1) + showbox_offset,target_Y - min_showbox_y(1) + showbox_offset,'Y*') |
|
|
530 |
end |
|
|
531 |
|
|
|
532 |
if abs(reference_LINE(1)) < 0.01 % reference line parallel to X axis |
|
|
533 |
A_R = 0; |
|
|
534 |
B_R = 1; |
|
|
535 |
C_R = -reference_Y(2); |
|
|
536 |
elseif abs(reference_LINE(1)) >100 % reference line parallel to Y axis |
|
|
537 |
A_R = 1; |
|
|
538 |
B_R = 0; |
|
|
539 |
C_R = -reference_X(2); |
|
|
540 |
else |
|
|
541 |
A_R = reference_LINE(1); |
|
|
542 |
B_R = -1; |
|
|
543 |
C_R = reference_Y(2) - A_R * reference_X(2); |
|
|
544 |
end |
|
|
545 |
|
|
|
546 |
%intersection of normal and reference line |
|
|
547 |
AA=[A_N B_N; A_R B_R]; |
|
|
548 |
BB =[-C_N; -C_R]; |
|
|
549 |
XX =AA\BB; % AA is a square matrix, AA\BB is roughly the same as inv(AA)*BB |
|
|
550 |
|
|
|
551 |
%validate intersection point |
|
|
552 |
XX_reference_distance = sqrt((XX(1) - reference_xy_subset(:,1)).^2 + (XX(2) - reference_xy_subset(:,2)).^2 ); |
|
|
553 |
if min(XX_reference_distance)<1.5 |
|
|
554 |
%distance |
|
|
555 |
distance(idx) = sqrt((target_curr_x - XX(1))^2 + (target_curr_y - XX(2))^2); |
|
|
556 |
|
|
|
557 |
if para.save_figure |
|
|
558 |
%plot intersection point |
|
|
559 |
hold on; plot(XX(1)-min_showbox_x(1)+showbox_offset,XX(2)-min_showbox_y(1)+showbox_offset,'m*') |
|
|
560 |
%plot distance line |
|
|
561 |
hold on; plot([target_curr_x - min_showbox_x(1) + showbox_offset XX(1) - min_showbox_x(1) + showbox_offset], [target_curr_y - min_showbox_y(1) + showbox_offset XX(2) - min_showbox_y(1) + showbox_offset],'b'); |
|
|
562 |
end |
|
|
563 |
else |
|
|
564 |
distance(idx) = -999; |
|
|
565 |
end |
|
|
566 |
end |
|
|
567 |
|
|
|
568 |
%in mm |
|
|
569 |
distance = distance * para.pixel_spacing(1); |
|
|
570 |
distance_effective = distance(distance>0); |
|
|
571 |
varargout{1} =mean(distance_effective); |
|
|
572 |
|
|
|
573 |
if para.save_figure |
|
|
574 |
%plot minimum and maximum distance point |
|
|
575 |
min_dist_idx = find(distance == min(distance_effective)); |
|
|
576 |
max_dist_idx = find(distance == max(distance_effective)); |
|
|
577 |
hold on; plot(target_xy(min_dist_idx(1),1)-min_showbox_x(1)+showbox_offset,target_xy(min_dist_idx(1),2)-min_showbox_y(1)+showbox_offset,'yo') |
|
|
578 |
hold on; plot(target_xy(max_dist_idx(1),1)-min_showbox_x(1)+showbox_offset,target_xy(max_dist_idx(1),2)-min_showbox_y(1)+showbox_offset,'yd') |
|
|
579 |
|
|
|
580 |
%text minimum and maximum distance |
|
|
581 |
text(target_xy(min_dist_idx(1),1)-min_showbox_x(1)+showbox_offset+0.5,target_xy(min_dist_idx(1),2)-min_showbox_y(1)+showbox_offset+0.5,[num2str(distance(min_dist_idx(1))),'(mm)'],'color','g','FontSize',11) %+0.5, not polt overlap |
|
|
582 |
text(target_xy(max_dist_idx(1),1)-min_showbox_x(1)+showbox_offset+0.5,target_xy(max_dist_idx(1),2)-min_showbox_y(1)+showbox_offset+0.5,[num2str(distance(max_dist_idx(1))),'(mm)'],'color','g','FontSize',11) |
|
|
583 |
|
|
|
584 |
%text mean and std of distances |
|
|
585 |
text(2,2,['Mean:' num2str(mean(distance_effective)) '(mm)' ', Std:' num2str(std(distance_effective)) '(mm)'],'color','g','FontSize',11); |
|
|
586 |
|
|
|
587 |
%legend |
|
|
588 |
legend('target','reference','intersection','distance line') |
|
|
589 |
|
|
|
590 |
%save figure as png |
|
|
591 |
saveas(h, [str(1:end-9), '.png']); %IM-0001-XXXX-icontour-auto.txt |
|
|
592 |
close(h) |
|
|
593 |
end |
|
|
594 |
end |
|
|
595 |
|
|
|
596 |
function dm = calc_dm(autoPoints,manualPoints,para) |
|
|
597 |
%calc dice metric |
|
|
598 |
auto_mask = poly2mask (autoPoints(:,1),autoPoints(:,2),double(para.width),double(para.height)); |
|
|
599 |
manual_mask = poly2mask (manualPoints(:,1),manualPoints(:,2),double(para.width),double(para.height)); |
|
|
600 |
|
|
|
601 |
auto_size = sum(auto_mask(:)>0); |
|
|
602 |
manual_size = sum(manual_mask(:)>0); |
|
|
603 |
intersect_size = sum((auto_mask(:) + manual_mask(:))==2); |
|
|
604 |
dm = 2 * intersect_size / (auto_size + manual_size); |
|
|
605 |
end |
|
|
606 |
|
|
|
607 |
function lv = calc_clinical_para(dicomPath, contour_path, para,varargin) |
|
|
608 |
%calc ejection fraction and lv mass |
|
|
609 |
|
|
|
610 |
[dicomCount, sliceCount] = dicom_counter(dicomPath,para.phase_number); |
|
|
611 |
contTable_i = zeros(sliceCount, para.phase_number); |
|
|
612 |
contTable_o = zeros(sliceCount, para.phase_number); |
|
|
613 |
contTable_p1 = zeros(sliceCount, para.phase_number); |
|
|
614 |
contTable_p2 = zeros(sliceCount, para.phase_number); |
|
|
615 |
|
|
|
616 |
if length(varargin) > 3 |
|
|
617 |
startingSlice = varargin{1}; |
|
|
618 |
endingSlice = varargin{2}; |
|
|
619 |
systolePhase = varargin{3}; |
|
|
620 |
diastolePhase = varargin{4}; |
|
|
621 |
elseif length(varargin) > 1 |
|
|
622 |
startingSlice = varargin{1}; |
|
|
623 |
endingSlice = varargin{2}; |
|
|
624 |
systolePhase = 0; |
|
|
625 |
diastolePhase = 0; |
|
|
626 |
else |
|
|
627 |
startingSlice = 1; |
|
|
628 |
endingSlice = sliceCount; |
|
|
629 |
systolePhase = 0; |
|
|
630 |
diastolePhase = 0; |
|
|
631 |
end |
|
|
632 |
|
|
|
633 |
if startingSlice < 1 || startingSlice > sliceCount |
|
|
634 |
startingSlice = 1; |
|
|
635 |
end |
|
|
636 |
|
|
|
637 |
if endingSlice < 1 || endingSlice > sliceCount |
|
|
638 |
endingSlice = sliceCount; |
|
|
639 |
end |
|
|
640 |
|
|
|
641 |
if startingSlice > endingSlice |
|
|
642 |
endTemp = startingSlice; |
|
|
643 |
startingSlice = endingSlice; |
|
|
644 |
endingSlice = endTemp; |
|
|
645 |
end |
|
|
646 |
|
|
|
647 |
%list all contours |
|
|
648 |
contour_list = dir([contour_path filesep '*contour*.txt']); |
|
|
649 |
|
|
|
650 |
for contourIdx = 1:length(contour_list) |
|
|
651 |
contourFile = contour_list(contourIdx).name; |
|
|
652 |
|
|
|
653 |
%inside contour |
|
|
654 |
if (strcmp(contourFile(14),'i')) |
|
|
655 |
|
|
|
656 |
%manual contours |
|
|
657 |
if para.auto == false |
|
|
658 |
good_icontour = true; |
|
|
659 |
end |
|
|
660 |
%auto contours |
|
|
661 |
if para.auto == true |
|
|
662 |
good_icontour = any(ismember(para.auto_good_index_i, str2num(contourFile(9:12)))); |
|
|
663 |
end |
|
|
664 |
|
|
|
665 |
if ~good_icontour |
|
|
666 |
continue; |
|
|
667 |
end |
|
|
668 |
|
|
|
669 |
match = regexp((contourFile),'-','start'); |
|
|
670 |
imNum = contourFile(match(2) + 1:match(3) - 1); |
|
|
671 |
currSlice = getSlice(str2double(imNum),para.phase_number); |
|
|
672 |
currPhase = getPhase(str2double(imNum),para.phase_number); |
|
|
673 |
full_contour_filename = [contour_path filesep contourFile]; |
|
|
674 |
try |
|
|
675 |
xy = load(full_contour_filename); |
|
|
676 |
area = polyarea(xy(:,1),xy(:,2)); |
|
|
677 |
area_mm = area* para.pixel_spacing(1)^2; |
|
|
678 |
vol_cm3 = area_mm * (para.thickness + para.gap) *(1/10)^3; %change to cm3 |
|
|
679 |
contTable_i(currSlice, currPhase) = vol_cm3; |
|
|
680 |
catch |
|
|
681 |
s = lasterror; |
|
|
682 |
disp(s.message); |
|
|
683 |
return; |
|
|
684 |
end |
|
|
685 |
|
|
|
686 |
end |
|
|
687 |
|
|
|
688 |
%outside contour |
|
|
689 |
if (strcmp(contourFile(14),'o') ) |
|
|
690 |
if para.auto == false |
|
|
691 |
good_ocontour = true; |
|
|
692 |
end |
|
|
693 |
if para.auto == true |
|
|
694 |
good_ocontour = any(ismember(para.auto_good_index_o, str2num(contourFile(9:12)))); |
|
|
695 |
end |
|
|
696 |
if ~good_ocontour |
|
|
697 |
continue; |
|
|
698 |
end |
|
|
699 |
|
|
|
700 |
match = regexp((contourFile),'-','start'); |
|
|
701 |
imNum = contourFile(match(2) + 1:match(3) - 1); |
|
|
702 |
currSlice = getSlice(str2double(imNum),para.phase_number); |
|
|
703 |
currPhase = getPhase(str2double(imNum),para.phase_number); |
|
|
704 |
full_contour_filename = [contour_path filesep contourFile]; |
|
|
705 |
try |
|
|
706 |
xy = load(full_contour_filename); |
|
|
707 |
area = polyarea(xy(:,1),xy(:,2)); |
|
|
708 |
area_mm = area* para.pixel_spacing(1)^2; |
|
|
709 |
vol_cm3 = area_mm * (para.thickness + para.gap) *(1/10)^3;%change to cm3 |
|
|
710 |
contTable_o(currSlice, currPhase) = vol_cm3; |
|
|
711 |
catch |
|
|
712 |
s = lasterror; |
|
|
713 |
disp(s.message); |
|
|
714 |
return; |
|
|
715 |
end |
|
|
716 |
|
|
|
717 |
end |
|
|
718 |
|
|
|
719 |
%pap contour |
|
|
720 |
if (strcmpi(contourFile(14), 'p') ) |
|
|
721 |
if para.auto == false |
|
|
722 |
good_pcontour = true; |
|
|
723 |
end |
|
|
724 |
if para.auto == true |
|
|
725 |
good_pcontour = any(ismember([ para.auto_good_index_i; para.auto_good_index_o] , str2num(contourFile(9:12)))); |
|
|
726 |
end |
|
|
727 |
if ~good_pcontour |
|
|
728 |
continue; |
|
|
729 |
end |
|
|
730 |
|
|
|
731 |
match = regexp((contourFile),'-','start'); |
|
|
732 |
imNum = contourFile(match(2) + 1:match(3) - 1); |
|
|
733 |
currSlice = getSlice(str2double(imNum),para.phase_number); |
|
|
734 |
currPhase = getPhase(str2double(imNum),para.phase_number); |
|
|
735 |
full_contour_filename = [contour_path filesep contourFile]; |
|
|
736 |
try |
|
|
737 |
xy = load(full_contour_filename); |
|
|
738 |
area = polyarea(xy(:,1),xy(:,2)); |
|
|
739 |
area_mm = area* para.pixel_spacing(1)^2; |
|
|
740 |
vol_cm3 = area_mm * (para.thickness + para.gap) *(1/10)^3;%change to cm3 |
|
|
741 |
|
|
|
742 |
if ( strcmpi(contourFile(15), '1') ) |
|
|
743 |
contTable_p1(currSlice, currPhase) = vol_cm3; |
|
|
744 |
end |
|
|
745 |
if ( strcmpi(contourFile(15), '2') ) |
|
|
746 |
contTable_p2(currSlice, currPhase) = vol_cm3; |
|
|
747 |
end |
|
|
748 |
catch |
|
|
749 |
s = lasterror; |
|
|
750 |
disp(s.message); |
|
|
751 |
return; |
|
|
752 |
end |
|
|
753 |
end %end if |
|
|
754 |
|
|
|
755 |
end |
|
|
756 |
|
|
|
757 |
maxPhase = length(contTable_i(1,:)); |
|
|
758 |
if systolePhase < 1 || systolePhase > maxPhase || diastolePhase < 1 || diastolePhase > maxPhase |
|
|
759 |
[systolePhase, diastolePhase] = esedDetermine(contTable_i); |
|
|
760 |
end |
|
|
761 |
|
|
|
762 |
[contTableChecked_i, zeroed, slicesExcluded, slicesIncluded] = efCheck(contTable_i, systolePhase, diastolePhase, startingSlice, endingSlice); |
|
|
763 |
|
|
|
764 |
%calc ef - use only inside contour info. |
|
|
765 |
esvol_pic = sum(contTableChecked_i(:, systolePhase)); %'_pic' means pap included in the LV cavity |
|
|
766 |
esvol_pim = sum(contTableChecked_i(:, systolePhase))- sum(contTable_p1(slicesIncluded,systolePhase))- sum(contTable_p2(slicesIncluded,systolePhase));%'_pim' means pap included in the myocardium |
|
|
767 |
edvol_pic = sum(contTableChecked_i(:, diastolePhase)); |
|
|
768 |
edvol_pim = sum(contTableChecked_i(:, diastolePhase)) - sum(contTable_p1(slicesIncluded,diastolePhase))- sum(contTable_p2(slicesIncluded,diastolePhase)); |
|
|
769 |
strokeVol_pic = edvol_pic - esvol_pic; |
|
|
770 |
strokeVol_pim = edvol_pim - esvol_pim; |
|
|
771 |
ef_pic = strokeVol_pic / edvol_pic * 100; |
|
|
772 |
ef_pim = strokeVol_pim / edvol_pim * 100; |
|
|
773 |
|
|
|
774 |
lv.esv_pic = esvol_pic; |
|
|
775 |
lv.esv_pim = esvol_pim; |
|
|
776 |
lv.edv_pic = edvol_pic; |
|
|
777 |
lv.edv_pim = edvol_pim; |
|
|
778 |
lv.sv_pic = strokeVol_pic; |
|
|
779 |
lv.sv_pim = strokeVol_pim; |
|
|
780 |
lv.ef_pic = ef_pic; |
|
|
781 |
lv.ef_pim = ef_pim; |
|
|
782 |
lv.es = systolePhase; |
|
|
783 |
lv.ed = diastolePhase; |
|
|
784 |
lv.zeroed = zeroed; |
|
|
785 |
lv.slice_excluded = slicesExcluded; |
|
|
786 |
|
|
|
787 |
%calc lv mass |
|
|
788 |
combined_table(:,1) = contTable_i(:,diastolePhase); |
|
|
789 |
combined_table(:,2) = contTable_o(:,diastolePhase); |
|
|
790 |
common_slice = ((combined_table(:,1) ~= 0) + (combined_table(:,2) ~= 0)) == 2 ; |
|
|
791 |
|
|
|
792 |
edv_i_pic = sum(combined_table(common_slice, 1)); |
|
|
793 |
edv_o_pic = sum(combined_table(common_slice, 2)); |
|
|
794 |
|
|
|
795 |
lvm_pic = (edv_o_pic - edv_i_pic) * 1.05; %1.05 (g/cm3) |
|
|
796 |
lvm_pim = (edv_o_pic - edv_i_pic + sum(contTable_p1(common_slice,diastolePhase)) + sum(contTable_p2(common_slice,diastolePhase)) ) * 1.05; |
|
|
797 |
|
|
|
798 |
lv.lvm_pic = lvm_pic; |
|
|
799 |
lv.lvm_pim = lvm_pim; |
|
|
800 |
end |
|
|
801 |
|
|
|
802 |
function [systolePhase, diastolePhase] = esedDetermine(contTable) |
|
|
803 |
%determin ES and ED phase |
|
|
804 |
|
|
|
805 |
contVolSum = sum(contTable); |
|
|
806 |
minVol = 9999; |
|
|
807 |
maxVol = 0; |
|
|
808 |
|
|
|
809 |
for x = 1:length(contVolSum) |
|
|
810 |
valueUnderExamination = contVolSum(x); |
|
|
811 |
|
|
|
812 |
if valueUnderExamination > 0 |
|
|
813 |
if minVol > valueUnderExamination |
|
|
814 |
minVol = contVolSum(x); |
|
|
815 |
systolePhase = x; |
|
|
816 |
end |
|
|
817 |
|
|
|
818 |
if maxVol < valueUnderExamination |
|
|
819 |
maxVol = contVolSum(x); |
|
|
820 |
diastolePhase = x; |
|
|
821 |
end |
|
|
822 |
end |
|
|
823 |
end |
|
|
824 |
end |
|
|
825 |
|
|
|
826 |
function sliceNum = getSlice(imageNum,phase_number) |
|
|
827 |
% calc slice number from the image number |
|
|
828 |
sliceNum = ceil(imageNum/phase_number); |
|
|
829 |
end |
|
|
830 |
|
|
|
831 |
function phaseNum = getPhase(imageNum,phase_number) |
|
|
832 |
%calc phase number from image number |
|
|
833 |
remainder = mod(imageNum,phase_number); |
|
|
834 |
if (remainder ~= 0) |
|
|
835 |
phaseNum = remainder; |
|
|
836 |
else |
|
|
837 |
phaseNum = phase_number; |
|
|
838 |
end |
|
|
839 |
end |
|
|
840 |
|
|
|
841 |
function [volTableCropped, zeroed, slicesExcluded,slicesIncluded] = efCheck(volTable, systolePhase, diastolePhase, startSlice, endSlice) |
|
|
842 |
% modifies the contour table that is passed in by... |
|
|
843 |
% 1. removing the slices that are to be excluded using startSlice and |
|
|
844 |
% endSlice by zeroing all those values |
|
|
845 |
% 2. removing ED/ES pairs that are missing the other value. |
|
|
846 |
% that is, if there is a zero in the systolic phase but not in the |
|
|
847 |
% diastolic phase in that slice, zero the other |
|
|
848 |
|
|
|
849 |
% 1. removing the non-included slices |
|
|
850 |
volTableCropped = zeros(length(volTable(:,1)), length(volTable(1,:))); |
|
|
851 |
volTableCropped(startSlice:endSlice, :) = volTable(startSlice:endSlice, :); |
|
|
852 |
|
|
|
853 |
% 2. finding the ES/ED slices with missing values |
|
|
854 |
sysZeros = find(volTable(:, systolePhase) == 0); |
|
|
855 |
diaZeros = find(volTable(:, diastolePhase) == 0); |
|
|
856 |
|
|
|
857 |
if ~isempty(sysZeros) |
|
|
858 |
volTableCropped(sysZeros, :) = 0; |
|
|
859 |
end |
|
|
860 |
|
|
|
861 |
if ~isempty(diaZeros) |
|
|
862 |
volTableCropped(diaZeros, :) = 0; |
|
|
863 |
end |
|
|
864 |
|
|
|
865 |
slicesExcluded = find(volTableCropped(:, systolePhase) == 0); |
|
|
866 |
slicesIncluded = find(volTableCropped(:, systolePhase) ~= 0); |
|
|
867 |
|
|
|
868 |
zeroed = length(slicesExcluded); |
|
|
869 |
end |
|
|
870 |
|
|
|
871 |
function [dicom_count, slice_count] = dicom_counter(dicom_path, phase_number) |
|
|
872 |
%calc dicom image number and slice number |
|
|
873 |
dicom_count = 0; |
|
|
874 |
slice_count = 0; |
|
|
875 |
|
|
|
876 |
if ~exist(dicom_path,'dir') |
|
|
877 |
return |
|
|
878 |
end |
|
|
879 |
|
|
|
880 |
dicom_files = dir([dicom_path filesep '*.dcm']); |
|
|
881 |
if isempty(dicom_files) |
|
|
882 |
disp([dicom_path ' :NO DICOM files!']) |
|
|
883 |
return; |
|
|
884 |
else |
|
|
885 |
dicom_count = length(dicom_files); |
|
|
886 |
slice_count = ceil(dicom_count / phase_number); |
|
|
887 |
end |
|
|
888 |
end |