Switch to unified view

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