-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnormalcy.m
More file actions
1573 lines (1394 loc) · 73.1 KB
/
normalcy.m
File metadata and controls
1573 lines (1394 loc) · 73.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%**************************************************************************
%
% PROGRAM TITLE normalcy.m
%
% WRITTEN BY David G. Politte and Kirk E. Smtih
% DATE WRITTEN May 12, 2011
% WRITTEN FOR Pediatric head modeling project
%
% REVISIONS May 18, 2011; Kirk Smith
% REVISIONS July 7, 2011; Gregory G. Reiker
% REVISIONS July 21, 2011; Gregory G. Reiker
% REVISIONS August 10, 2011; Kirk Smith
% REVISIONS August 4-25, 2011; Gregory G. Reiker
% REVISIONS September 1-29, 2011; Gregory G. Reiker
% REVISIONS October 1-30, 2011; Kirk Smith
% REVISIONS October 6-27, 2011; Gregory G. Reiker
% REVISIONS November 1-30, 2011; Kirk Smith
% REVISIONS November 1-30, 2011; Gregory G. Reiker
% REVISIONS December 1-30, 2011; Kirk Smith
% REVISIONS December 1-30, 2011; Gregory G. Reiker
%
% CALLING SYNTAX
% Use the following syntax:
% normalcy(inputs_directory, outputs_directory, ...
% gray_file_name_root, ref_local_file_name, ...
% ref_landmark_file_name, out_file_name_root, ...
% mr_file_name_root, ...
% mr_ref_landmark_file_name, r_patch_mm, ...
% display_verbose, pause_before, pause_between) ;
%
% where
% inputs_directory is the name of the directory (folder) where
% the inputs are located. The inputs are: (1)
% the header file of the CT grayscale image
% volume, (2) the image file of the CT grayscale
% image volume, (3) the header file of the
% MR grayscale image volume, (4)
% the image file of the MR grayscale image
% volume.
% The image volumes (items 1-4) are in Analyze 7.5
% format. (char)
% Revision (5-18-2011):
% 6) a text file that contains the (x,y,z)
% coordinates of the reference points that
% define the local coordinate system (NAS,
% PAR, PAL)
% 7) a text file that contains azimuth and
% elevation angles to define landmarks where
% surface normals are calculated.
% outputs_directory is the name of the directory (folder) where
% the outputs will be located. The outputs are:
% (1) a ".mat" file that contains the contents
% of important variables, vectors, and
% matrices, (2) a ".csv" (comma separated
% values) list that contains the contents of
% important variables, vectors, and matrices
% and can be opened in Microsoft Excel. Items
% (1) and (2) are cumulative outputs for all of
% the reference points that were specified. In
% addition, for each reference point, the
% following outputs are created: (3) a ".png"
% graphic file of Figure 4, which is a
% three-panel representation of the transverse
% image slice of the reference point, (4) a
% ".png" graphic file of Figure 5, which is a
% three-panel representation of the sagittal
% image slice of the reference point, (5) a
% ".png" graphic file of Figure 6, which is a
% three-panel representation of the coronal
% image slice of the reference point, and (6) a
% ".png" file of the graph of the interpolated
% image values of the grayscale CT image along
% the computed surface normal. (char)
% gray_file_name_root is the root name of the grayscale CT image.
% The names of the header and image files are
% given by gray_file_name_root with ".hdr" or
% ".img" appended, respectively. (char)
% ref_local_file_name is the name of the text file that
% contains the (x,y,z) coordinates of the
% CT reference points (NAS,PAR,PAL) that define a
% local coordinate system.
% ref_landmark_file_name is the name of the text file that contains
% the (azimuth_deg,elevation_deg,radius_mm)
% values of the reference points that are used
% to define the landmarks. default r=1.
% out_file_name_root is the root name of the output files that are
% generated. See "outputs_directory" above for
% a list of output files. (char)
% mr_file_name_root is the root name of the MR image.
% The names of the header and image files are
% given by mr_file_name_root with ".hdr" or
% ".img" appended, respectively. (char)
% mr_ref_landmark_file_name is the name of the text file that contains
% the (azimuth_deg,elevation_deg,radius_mm)
% values of the reference points that are used
% to define the landmarks for MR/CT registration.
% default r=1.
% r_patch_mm is the radius in mm of the patch of voxels
% that will be included in the fit to a sphere.
% All voxels on the surface border within
% r_patch_mm of the reference point are
% included in the patch. (double)
% display_verbose indicates the degree of verbosity in the
% displayed results. If display_verbose equals
% 1, three movies of the transverse, sagittal,
% and coronal planes are displayed. If
% display_verbose equals 0, these movies are
% not shown. (double)
% pause_before is the number of seconds between the time a
% figure window appears and the contents of the
% figure appear. This gives the user time to
% move and/or resize the window while giving
% demonstrations of the program. For maximal
% speed, set pause_before to 0 seconds.
% (double)
% pause_between is the number of seconds between frames of
% the transverse, sagittal, and coronal movies.
% For maximal speed, set pause_between to 0
% seconds. This parameter has no effect if
% display_verbose is 0. (double)
%
% PROGRAM DESCRIPTION
% normalcy is the main Matlab function for fitting spheres to
% convex patches on the surface of the scalp. It also computes the
% values of a grayscale CT image along the surface normals.
% Also, it calculates braincase segmentation, accecpts MR data
% on which analyses are performed, performs 3D rotation and segmentation of
% original grayscale CT image, calculates CSF measurement from MR data,
% and performs 3D registration of MR image with CT image volume.
%
% FILES
% Inputs (located in "inputs_directory"):
% grayscale CT image header (Analyze 7.5 format)
% grayscale CT image volume (Analyze 7.5 format)
% binary-valued segmentation image header (Analyze 7.5 format)
% binary-valued segmentation image volume (Analyze 7.5 format)
% log file with (x,y,z) coordinates of reference points (can be
% generated by Analyze)
% MR image header (Analyze 7.5 format)
% MR image volume (Analyze 7.5 format)
%
% text file with (x,y,z) coordinates of reference points
% text file with azimuth, elevation, and radius of landmark points
%
% standard input - not used
%
% Outputs (located in "outputs_directory"):
% .mat file with output variables from entire run
% .csv file with output variables from entire run
% .png files for (1) transverse section, (2) sagittal section, (3)
% coronal section, and (4) surface normal attentuation profile.
% The .png files are computed for each reference point.
% standard output - not used
%
% DEPENDENCIES
% MATLAB (win64) Version 7.12.0.635 (R2011a)
% Image Acquisition Toolbox Version 4.1 (R2011a)
% Image Processing Toolbox Version 7.2 (R2011a)
% Optimization Toolbox Version 6.0 (R2011a)
% Parallel Computing Toolbox Version 5.1 (R2011a)
% Signal Processing Toolbox Version 6.15 (R2011a)
% Statistics Toolbox Version 7.5
%
% normalcy requires that the sampling of the input images in x, y, and
% z be isotropic.
%
% Code dependencies are indicated by the level of indent:
% normalcy
% calc_rot_angles
% read_ref_landmark_file
% rotate_3d_ct
% calc_border_bbox
% calc_landmarks
% read_ref_landmarks_file
% keep_connected_only
% disp_transverse_movie
% disp_sagittal_movie
% disp_coronal_movie
% seg_thresh
% fminsearch (from Optimization Toolbox)
% fit_function_rotation
% rotate_3d_mr
% fminsearch (from Optimization Toolbox)
% fit_function_sphere
% disp_transverse_normal
% disp_sagittal_normal
% disp_coronal_normal
% find_edges_prof
% find_edges_prof_mr
% disp_transverse_normal_mr_csf
% disp_sagittal_normal_mr_csf
% disp_coronal_normal_mr_csf
% Calc_braincase
% calc_circumf_angles
% rotate_ct_circumf
% suptitle.m
%
% normalcy is a function that is called by another function of Matlab
% script or is invoked at the command line.
%
% REVISION HISTORY
% Version Date Comment
% ------- ----------- -------------------------------------------
% 1.0 12 May 2011 Initial release.
% 1.1 18 May 2011 Adding auto landmark calculation
% 1.2 23 June 2011 Improved image fill
% 1.3 7 July 2011 Added braincase segmentation
% 1.4 21 July 2011 Added MR data and analyses
% 1.5 11 August 2011 Added 3D rotation and segmentation of
% original grayscale CT image
% 1.6 1 September 2011 Added CSF measurement from MR data
% 1.7 8 September 2011 Added 3D rotation of original MR image
% and alignment with CT image
% 1.8 27 October 2011 Added 3D registration of MR image with
% CT image
% 1.9 23 November 2011 Revised algorithm
%
% COPYRIGHT
%
% Copyright (c) 2011 Washington University in St. Louis
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%**************************************************************************
%
function [] = normalcy(inputs_directory, outputs_directory, ...
gray_file_name_root, ref_local_file_name, ...
ref_landmark_file_name, out_file_name_root, ...
mr_file_name_root, ...
mr_ref_landmark_file_name, r_patch_mm, ...
display_verbose, pause_before, pause_between)
disp(' ') ;
disp(' ') ;
% Create the output directory.
if (exist(outputs_directory, 'dir') == 0)
[success, ~, ~] = mkdir(outputs_directory) ;
if (success == 0)
disp('ERROR: UNABLE TO CREATE OUTPUT DIRECTORY. EXITING!') ;
return ;
end
end
% Concatenate the directory and file names to find the complete path.
full_ref_local_file_name = [inputs_directory '\' ...
ref_local_file_name] ;
full_ref_landmark_file_name = [inputs_directory '\' ...
ref_landmark_file_name] ;
full_gray_file_hdr_name = ...
[inputs_directory '\' gray_file_name_root '.hdr'] ;
full_gray_file_img_name = ...
[inputs_directory '\' gray_file_name_root '.img'] ;
full_mr_file_img_name = ...
[inputs_directory '\' mr_file_name_root '.img'] ;
full_mr_file_hdr_name = ...
[inputs_directory '\' mr_file_name_root '.hdr'] ;
full_mr_ref_landmark_file_name = [inputs_directory '\' ...
mr_ref_landmark_file_name] ;
% check to see if an MR file is present and set flag to 0 if present
mr_empty = isempty(mr_file_name_root) ;
% Read the MR image
if (mr_empty == 0)
mr_header = analyze75info(full_mr_file_hdr_name) ;
mr_drcp_mm = [double(mr_header.PixelDimensions(1)) ...
double(mr_header.PixelDimensions(2)) ...
double(mr_header.PixelDimensions(3))] ;
%mr_r_patch_rcp = r_patch_mm / mr_drcp_mm(2) ;
mr_3d = double(analyze75read(full_mr_file_img_name)) ;
% Calculate a threshold value for MR image. Find the mean intensity for
% all voxels where intensity greater than 20 (assumed background) and
% then divide by 2 to account for partial volume averaging.
mr_head_thres = (mean(mr_3d(find(mr_3d > 20)))/2);
end
% Read the grayscale and segmentation images. drcp_mm contains the voxel
% dimensions. Assumed to be isotropic so only the Y dimension is read.
% Adjusting to account for auto segmentation
disp('Reading headers and image data ...') ;
header = analyze75info(full_gray_file_hdr_name) ;
drcp_mm = [double(header.PixelDimensions(1)) ...
double(header.PixelDimensions(2)) ...
double(header.PixelDimensions(3))] ;
r_patch_rcp = r_patch_mm / drcp_mm(2) ;
% Read in grayscale CT image
g_3d = double(analyze75read(full_gray_file_img_name)) ;
% Rotate grayscale CT image with its PAR as the reference. Return s_3d
% which becomes the aligned and clipped binary file; g_3d which is the
% aligned grayscale file; g_3d_orig is the original grayscale file
% before alignment. Calc_rot_angles becomes the inputs for rotate_3d_ct.
% Rotate_3d_ct does a connect operation so stray voxels should be gone,
% but it does not do a fill operation.
[image_center_in, alpha, beta, gamma, nas, par, pal]= ...
calc_rot_angles(full_ref_local_file_name);
image_center_out = image_center_in ;
[g_3d_orig, s_3d, g_3d]= rotate_3d_ct(g_3d, image_center_in, image_center_out, ...
alpha, beta, gamma);
%Save aligned CT image
full_align_file_name = [outputs_directory '\' out_file_name_root ...
'.alignCT.vol'] ;
fid=fopen(full_align_file_name,'w');
g_3d_an75 = permute(g_3d,[2 1 3]);
fwrite(fid,g_3d_an75,'int16');
fclose(fid);
% set to even voxel value for later and saving
par = round(par);
pal=round(pal);
nas=round(nas);
disp(['Aligned CT PAR = ' num2str(par)]) ;
disp(['Aligned CT PAL = ' num2str(pal)]) ;
disp(['Aligned CT NAS = ' num2str(nas)]) ;
% For CT data
% Set the first two and last two rows, columns, and planes of the
% segmentation image to zero. Some of the algorithms used below require
% 2 "guard planes" on all sides.
disp( ...
'Creating 2 "guard" planes on all sides of the segmentation ...') ;
s_3d(1:2, :, : ) = 0 ;
s_3d(end-1:end, :, : ) = 0 ;
s_3d(:, 1:2, : ) = 0 ;
s_3d(:, end-1:end, : ) = 0 ;
s_3d(:, :, 1:2 ) = 0 ;
s_3d(:, :, end-1:end) = 0 ;
% Clean up the segmentation image by filling in holes. This is
% intentionally done in 2D in transverse, sagittal, and coronal planes.
temp0 = size(s_3d) ;
num_row = temp0(1) ;
num_col = temp0(2) ;
num_pln = temp0(3) ;
disp(['Filling in holes of segmentation image on 2D transverse ' ...
'slices ...']) ;
temp1 = zeros(size(s_3d)) ;
parfor pln_num = 1:num_pln
temp1(:, :, pln_num) = imfill(s_3d(:, :, pln_num), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D sagittal ' ...
'slices ...']) ;
temp2 = zeros(size(temp1)) ;
parfor col_num = 1:num_col
temp2(:, col_num, :) = imfill(squeeze(temp1(:, col_num, :)), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D coronal ' ...
'slices ...']) ;
temp3 = zeros(size(temp2)) ;
parfor row_num = 1:num_row
temp3(row_num, :, :) = imfill(squeeze(temp2(row_num, :, :)), 'holes') ;
end
s_3d = temp3 ;
% Calculate a bounding box.
[min_rcp_of_data, max_rcp_of_data, list_border_bc_rcp, b_3d_bc] = ...
calc_border_bbox(s_3d);
% At this point list_border_bc_rcp is the border points or the segmented
% and filled CT head.
% Only if MR data
if (mr_empty == 0)
% CT registation landmark calculations only -------------------------------------
% Auto calulate landmarks based on spherical coordinate system. This
% function reads in the local coordinate system points, reads in the
% specified azimuth and elevation angles, calculates a line and border
% voxel intersection, and returns the inputs to the main loop below.
% Border voxels must have been calculated prior to this function call.
[ref_point_mat_xyz, num_ref_points, local_origin] = ...
calc_landmarks(full_mr_ref_landmark_file_name, list_border_bc_rcp, ...
min_rcp_of_data, max_rcp_of_data, nas, par, pal) ;
for nn=1:num_ref_points
disp(['CT ref pt = ' num2str(ref_point_mat_xyz(nn,:))]) ;
end
%--------------------------------------------------------------------
% %Display Registration landmarks on CT image
% hold on; % for figure(1)
% ref_point_mat_rcp = [ref_point_mat_xyz(:, 2) ...
% ref_point_mat_xyz(:, 1) ref_point_mat_xyz(:, 3)];
% ref_pt_empty = isempty(ref_point_mat_xyz) ;
% if (ref_pt_empty == 0)
% temp1 = size(ref_point_mat_rcp) ;
% land_tot = temp1(1) ;
% parfor land = 1:land_tot
% hold on;
% coord3d = ref_point_mat_rcp(land,:) ;
% xc = coord3d(1) ;
% yc = coord3d(2) ;
% zc = coord3d(3) ;
% [x,y,z]=ellipsoid(xc,yc,zc,7,7,7) ;
% mesh(x,y,z) ;
% end
% else
% disp('ERROR: NO REFERENCE POINTS SPECIFIED. EXITING!') ;
% return ;
% end
%------------------------------------------------------------------
% For Registration calculate the landmarks and patches of voxels
% Process all of the reference points. This is the main loop of this
% function. This will be for the registration points only.
% num_rand determines the number of points to be sampled within
% each patch, therefore, the total number of ct ref pts is rand_num
% times the number of landmarks used, so best to keep small for
% runtime issues.
% Does fewer random pts work better? Should the number of pts be
% increased per iteration?
num_rand = 10 ; % Defines number of points to randomly sample from a patch
% cum_ct_pts_rcp = zeros((num_rand * num_ref_points), 3);
cum_ct_pts_rcp = [];
num_reg_pts = num_rand * num_ref_points ;
% put parfor here
parfor ref_point_numb = 1:num_ref_points
ref_point_xyz = ref_point_mat_xyz(ref_point_numb, :) ;
ref_point_rcp = [ref_point_xyz(2) ref_point_xyz(1) ...
ref_point_xyz(3)] ;
disp(' ') ;
disp(['Processing reference point ' num2str(ref_point_numb) ...
' out of ' num2str(num_ref_points) '.']) ;
% Clean up the border by discarding all stray voxels that are not
% connected to the actual border. First, find a "seed point," a
% point on the borderm which is closest to the reference point.
% It might be possible to delete this next section but need to check
% the impact of all the variables like b_3d_bc.
disp('Cleaning up border by discarding unconnected voxels ...') ;
dist_away_rcp = ...
sqrt(((list_border_bc_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_border_bc_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_border_bc_rcp(:,3) - ref_point_rcp(3)).^2)) ;
[~, index_of_closest_point] = min(dist_away_rcp) ;
rcp_seed = [list_border_bc_rcp(index_of_closest_point, 1) ...
list_border_bc_rcp(index_of_closest_point, 2) ...
list_border_bc_rcp(index_of_closest_point, 3)] ;
[b_3d, list_border_rcp] = keep_connected_only(b_3d_bc, rcp_seed) ;
disp('*********************************************************') ;
disp(['Number of border voxels before cleanup = ' ...
num2str(sum(sum(sum(b_3d_bc)))) '.']) ;
disp(['Number of border voxels after cleanup = ' ...
num2str(sum(sum(sum(b_3d)))) '.']) ;
disp('*********************************************************') ;
%
% Now define the "patch" of points on the border that will be used
% to fit the sphere. First, we eliminate all points that are too
% far away from the reference point.
disp(['Creating a "patch" of voxels that the sphere will be ' ...
'fitted to ...']) ;
dist_away_rcp = sqrt( ...
((list_border_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_border_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_border_rcp(:,3) - ref_point_rcp(3)).^2)) ;
rrr = find(dist_away_rcp <= r_patch_rcp) ;
list_patch_bc_rcp = [] ;
list_patch_bc_rcp(:,1) = list_border_rcp(rrr, 1) ;
list_patch_bc_rcp(:,2) = list_border_rcp(rrr, 2) ;
list_patch_bc_rcp(:,3) = list_border_rcp(rrr, 3) ;
% Build an image volume for the patch. At this time, the patch is
% the set of all border voxels which are close enough to the
% reference point.
p_3d_bc = zeros(size(s_3d)) ;
for i = 1:length(list_patch_bc_rcp(:,1))
p_3d_bc(list_patch_bc_rcp(i,1), ...
list_patch_bc_rcp(i,2), ...
list_patch_bc_rcp(i,3)) = 1 ;
end
% Clean up the patch by discarding all stray voxels that are not
% connected to the seeds point. The seed point is the voxel on the
% border closest to the reference point.
disp('Cleaning up patch by discarding unconnected voxels ...') ;
dist_away_rcp = ...
sqrt(((list_patch_bc_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_patch_bc_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_patch_bc_rcp(:,3) - ref_point_rcp(3)).^2)) ;
[~, index_of_closest_point] = min(dist_away_rcp) ;
rcp_seed = [list_patch_bc_rcp(index_of_closest_point, 1) ...
list_patch_bc_rcp(index_of_closest_point, 2) ...
list_patch_bc_rcp(index_of_closest_point, 3)] ;
[p_3d, list_patch_rcp] = keep_connected_only(p_3d_bc, rcp_seed) ;
patch_size = (sum(sum(sum(p_3d))));
rand_sel = randi(patch_size, 1, num_rand) ;
ct_pts_rcp = zeros(num_rand, 3) ;
for cntr=1:num_rand
ct_pts_rcp(cntr, :) = list_patch_rcp(rand_sel(cntr), :);
end
cum_ct_pts_rcp = [cum_ct_pts_rcp; ct_pts_rcp] ;
disp('*********************************************************') ;
disp(['Number of patch voxels before cleanup = ' ...
num2str(sum(sum(sum(p_3d_bc)))) '.']) ;
disp(['Number of patch voxels after cleanup = ' ...
num2str(sum(sum(sum(p_3d)))) '.']) ;
% this needs fixed to display the voxels kept
disp(['Number of voxels kept for registration = ' ...
num2str(num_rand) '.']) ;
disp('*********************************************************') ;
% Build an image volume that is equal to one at the border voxels
% and equal to two at the border voxels that are close enough to
% the reference point. This image is used for display purposes
% only.
b_3d_with_patch = b_3d + p_3d ;
% Display all of the planes of the images.
if (display_verbose == 1)
disp('Displaying a movie of transverse planes ...') ;
disp_transverse_movie(g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
disp('Displaying a movie of sagittal planes ...') ;
disp_sagittal_movie( g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
disp('Displaying a movie of coronal planes ...') ;
disp_coronal_movie( g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
end
end
% This is the end of the loop that calculates the patches for
% the ct registration points. Each patch is randomly sampled and
% the cummulative points are used as the ct refence points for the
% registraiton. The ref_points need to be
% passed into the registration routine along with the mr boundary
% points. The interstection between the ct line and the mr boundary
% point becomes the mr ref point and is used to minimize the error.
%------------------------------------------------------------------
end
% Use this when not prealigning. Segment MR head, make binary, Display
clip_plane=par(3);
if (mr_empty == 0)
[mr_s_3d] = seg_thresh(mr_3d, mr_head_thres, clip_plane) ;
% For MR data
% Set the first two and last two rows, columns, and planes of the
% segmentation image to zero. Some of the algorithms used below require
% 2 "guard planes" on all sides.
disp('Creating 2 "guard" planes on all sides of the segmentation ...') ;
mr_s_3d(1:2, :, : ) = 0 ;
mr_s_3d(end-1:end, :, : ) = 0 ;
mr_s_3d(:, 1:2, : ) = 0 ;
mr_s_3d(:, end-1:end, : ) = 0 ;
mr_s_3d(:, :, 1:2 ) = 0 ;
mr_s_3d(:, :, end-1:end) = 0 ;
% Clean up the segmentation image by filling in holes. This is
% intentionally done in 2D in transverse, sagittal, and coronal planes.
temp0 = size(mr_s_3d) ;
num_row = temp0(1) ;
num_col = temp0(2) ;
num_pln = temp0(3) ;
disp(['Filling in holes of segmentation image oncalc 2D transverse ' ...
'slices ...']) ;
temp1 = zeros(size(mr_s_3d)) ;
parfor pln_num = 1:num_pln
temp1(:, :, pln_num) = imfill(mr_s_3d(:, :, pln_num), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D sagittal ' ...
'slices ...']) ;
temp2 = zeros(size(temp1)) ;
parfor col_num = 1:num_col
temp2(:, col_num, :) = imfill(squeeze(temp1(:, col_num, :)), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D coronal ' ...
'slices ...']) ;
temp3 = zeros(size(temp2)) ;
parfor row_num = 1:num_row
temp3(row_num, :, :) = imfill(squeeze(temp2(row_num, :, :)), 'holes') ;
end
mr_s_3d = temp3 ;
% Calculate a bounding box for the segmentation.
[mr_min_rcp_of_data, mr_max_rcp_of_data, mr_list_border_bc_rcp, mr_b_3d_bc] = ...
calc_border_bbox(mr_s_3d);
end
%-----------------------------------------------------------------
%
tic
disp(['Beginning MR to CT registration']) ;
% CT and MR registration, only if MR --------------------------------------
if (mr_empty == 0)
% For rotation, initialize center to actual volume center and 0 deg
% for rotation angles
temp0 = size(mr_s_3d) ;
num_x = temp0(2) ;
num_y = temp0(1) ;
num_z = temp0(3) ;
% center_init_xyz and therefore center_xyz should be the centroid
% of the binary mr data to speed up registration. probably not a
% big deal for heads as the center of the volume is well within the
% head and near the center of rotation anyway.
step_size=460; %step is actually 5% of 1/2 this value
center_xyz = [(num_x+1)/2 (num_y+1)/2 (num_z+1)/2]; % center of volume
alpha_est = 0;
beta_est = 0 ;
gamma_est = 0 ;
center_est_xyz(1) = 0 ;
center_est_xyz(2) = 0 ;
center_est_xyz(3) = 0 ;
sse_prev = 0;
reg_loop = 4;
for iter = 1:reg_loop
step_size = step_size/2 ;
parms_init(1) = alpha_est + step_size ;
parms_init(2) = beta_est + step_size ;
parms_init(3) = gamma_est + step_size ;
parms_init(4) = center_est_xyz(1) + step_size ;
parms_init(5) = center_est_xyz(2) + step_size ;
parms_init(6) = center_est_xyz(3) + step_size ;
disp(['Starting registration iteration = ' num2str(iter)]) ;
% pass in a random sampling of ct pts that come from the patches
cum_ct_pts_xyz = cum_ct_pts_rcp(:,[2 1 3]);
% Estimate the parameters of the best-fit rotation and translation
% using fminsearch, passing in MR border points.
%Will calculate corresponding points within the function.
% It would be best if we clipped off the bottom portion of
% mr_list_border_bc_rcp prior to sending it in. Could do this
% with lowest z value of bounding box?
f = @(x)fit_function_rotation(x, cum_ct_pts_xyz, ...
mr_list_border_bc_rcp, center_xyz, ...
local_origin, step_size) ;
[parms_out, sse_reg, exitflag, foutput] = fminsearch(f, parms_init, optimset('MaxFunEvals',4000)) ;
disp(['Sum of squared errors between Registered landmarks = ' ...
num2str(sse_reg)]) ;
if (exitflag == 1)
disp(['Optimization routine reported satisfactory ' ...
'convergence.']) ;
else
disp(['WARNING: OPTIMIZATION ROUTINE FAILED. EXITFLAG = ' ...
num2str(exitflag) '.']) ;
end
% Adjust and save parameters
center_est_xyz(1:3) = parms_out(4:6) - step_size;
alpha_est = parms_out(1) - step_size ;
beta_est = parms_out(2) - step_size ;
gamma_est = parms_out(3) - step_size ;
if (or(abs(sse_reg - sse_prev) < 1, iter == reg_loop))
break;
end
sse_prev = sse_reg;
end
%Rotate MR based on fit results center_init_xyz or center_xyz
center_est_xyz(1:3) = parms_out(4:6) + center_xyz - step_size;
clip_plane = par(3); %CT PAR plane
[mr_3d_orig, mr_s_3d, mr_3d]= rotate_3d_mr(mr_3d, center_xyz, center_est_xyz, ...
alpha_est, beta_est, gamma_est, clip_plane, mr_head_thres);
% For MR data
% Set the first two and last two rows, columns, and planes of the
% segmentation image to zero. Some of the algorithms used below require
% 2 "guard planes" on all sides.
disp('Creating 2 "guard" planes on all sides of the segmentation ...') ;
mr_s_3d(1:2, :, : ) = 0 ;
mr_s_3d(end-1:end, :, : ) = 0 ;
mr_s_3d(:, 1:2, : ) = 0 ;
mr_s_3d(:, end-1:end, : ) = 0 ;
mr_s_3d(:, :, 1:2 ) = 0 ;
mr_s_3d(:, :, end-1:end) = 0 ;
% Clean up the segmentation image by filling in holes. This is
% intentionally done in 2D in transverse, sagittal, and coronal planes.
temp0 = size(mr_s_3d) ;
num_row = temp0(1) ;
num_col = temp0(2) ;
num_pln = temp0(3) ;
disp(['Filling in holes of segmentation image on 2D transverse ' ...
'slices ...']) ;
temp1 = zeros(size(mr_s_3d)) ;
parfor pln_num = 1:num_pln
temp1(:, :, pln_num) = imfill(mr_s_3d(:, :, pln_num), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D sagittal ' ...
'slices ...']) ;
temp2 = zeros(size(temp1)) ;
parfor col_num = 1:num_col
temp2(:, col_num, :) = imfill(squeeze(temp1(:, col_num, :)), 'holes') ;
end
disp(['Filling in holes of segmentation image on 2D coronal ' ...
'slices ...']) ;
temp3 = zeros(size(temp2)) ;
parfor row_num = 1:num_row
temp3(row_num, :, :) = imfill(squeeze(temp2(row_num, :, :)), 'holes') ;
end
mr_s_3d = temp3 ;
% need to recalculate border points and bounding box after rotation
[mr_min_rcp_of_data, mr_max_rcp_of_data, mr_list_border_bc_rcp, mr_b_3d_bc] = ...
calc_border_bbox(mr_s_3d);
% MR landmark calculations. Only used for visualization.
% Auto calulate landmarks based on spherical coordinate system. This
% function reads in the local coordinate system points, reads in the
% specified azimuth and elevation angles, calculates a line and border
% voxel intersection, and returns the inputs to the main loop below.
% Border voxels must have been calculated prior to this function call.
% Don't clip MR volume until after registered otherwise will lose some
% of the volume during registration. The local coordinate system is
% based on the CT data as that is what is used for profile
% plotting.
[mr_ref_point_mat_xyz, mr_num_ref_points, mr_local_z] = ...
calc_landmarks(full_mr_ref_landmark_file_name, mr_list_border_bc_rcp, ...
min_rcp_of_data, max_rcp_of_data, nas, par, pal) ;
for nn=1:mr_num_ref_points
disp(['MR ref pt = ' num2str(mr_ref_point_mat_xyz(nn,:))]) ;
end
% Plotting position of landmarks on segmented displayed surface.
% The points are plotted in rcp to account for some anomaly in
% displaying on top of the volume image. This could be due to axes
% handles. The ellipsoid/mesh plot axes are likely different from
% the default figure axes and inherit the wrong axes.
% figure(1); %to display on CT head, remove if wanted on new MR data
% hold on;
% mr_ref_point_mat_rcp = [mr_ref_point_mat_xyz(:, 2) ...
% mr_ref_point_mat_xyz(:, 1) mr_ref_point_mat_xyz(:, 3)];
% ref_pt_empty = isempty(mr_ref_point_mat_xyz) ;
% if (ref_pt_empty == 0)
% temp1 = size(mr_ref_point_mat_rcp) ;
% land_tot = temp1(1) ;
% parfor land = 1:land_tot
% hold on;
% coord3d = mr_ref_point_mat_rcp(land,:) ;
% xc = coord3d(1) ;
% yc = coord3d(2) ;
% zc = coord3d(3) ;
% [x,y,z]=ellipsoid(xc,yc,zc,7,7,7) ;
% mesh(x,y,z) ;
% end
% else
% disp('ERROR: NO REFERENCE POINTS SPECIFIED. EXITING!') ;
% return;
% end
%Save aligned MR image
full_mr_align_file_name = [outputs_directory '\' out_file_name_root ...
'.alignMR.vol'] ;
fid=fopen(full_mr_align_file_name,'w');
mr_3d_an75 = permute(mr_3d,[2 1 3]);
fwrite(fid,mr_3d_an75,'int16');
fclose(fid);
end
toc
%-------------------------------------------------------------------------
% Auto calulate landmarks based on spherical coordinate system. This
% function reads in the local coordinate system points, reads in the
% specified azimuth and elevation angles, calculates a line and border
% voxel intersection, and returns the inputs to the main loop below.
% Border voxels must have been calculated prior to this function call.
[ref_point_mat_xyz, num_ref_points, local_origin] = ...
calc_landmarks(full_ref_landmark_file_name, list_border_bc_rcp, ...
min_rcp_of_data, max_rcp_of_data, nas, par, pal) ;
%____Display Only___________________________________________________
%Display reference landmarks on CT image
% hold on; % for figure(1)
% ref_point_mat_rcp = [ref_point_mat_xyz(:, 2) ...
% ref_point_mat_xyz(:, 1) ref_point_mat_xyz(:, 3)];
% ref_pt_empty = isempty(ref_point_mat_xyz) ;
% if (ref_pt_empty == 0)
% temp1 = size(ref_point_mat_rcp) ;
% land_tot = temp1(1) ;
% parfor land = 1:land_tot
% hold on;
% coord3d = ref_point_mat_rcp(land,:) ;
% xc = coord3d(1) ;
% yc = coord3d(2) ;
% zc = coord3d(3) ;
% [x,y,z]=ellipsoid(xc,yc,zc,7,7,7) ;
% mesh(x,y,z) ;
% end
% else
% disp('ERROR: NO REFERENCE POINTS SPECIFIED. EXITING!') ;
% return ;
% end
%___________________________________________________________________
% Initialize the vectors that will be estimated.
center_est_mat_xyz = zeros(num_ref_points, 3) ;
radius_est_vec_mm = zeros(num_ref_points, 1) ;
direction_vec_mat_xyz = zeros(num_ref_points, 3) ;
% Process all of the reference points. This is the main loop of this
% function.
for ref_point_num = 1:num_ref_points
ref_point_xyz = ref_point_mat_xyz(ref_point_num, :) ;
ref_point_rcp = [ref_point_xyz(2) ref_point_xyz(1) ...
ref_point_xyz(3)] ;
disp(' ') ;
disp(['Processing reference point ' num2str(ref_point_num) ...
' out of ' num2str(num_ref_points) '.']) ;
% Clean up the border by discarding all stray voxels that are not
% connected to the actual border. First, find a "seed point," a
% point on the borderm which is closest to the reference point.
disp('Cleaning up border by discarding unconnected voxels ...') ;
dist_away_rcp = ...
sqrt(((list_border_bc_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_border_bc_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_border_bc_rcp(:,3) - ref_point_rcp(3)).^2)) ;
[~, index_of_closest_point] = min(dist_away_rcp) ;
rcp_seed = [list_border_bc_rcp(index_of_closest_point, 1) ...
list_border_bc_rcp(index_of_closest_point, 2) ...
list_border_bc_rcp(index_of_closest_point, 3)] ;
[b_3d, list_border_rcp] = keep_connected_only(b_3d_bc, rcp_seed) ;
disp('*********************************************************') ;
disp(['Number of border voxels before cleanup = ' ...
num2str(sum(sum(sum(b_3d_bc)))) '.']) ;
disp(['Number of border voxels after cleanup = ' ...
num2str(sum(sum(sum(b_3d)))) '.']) ;
disp('*********************************************************') ;
% Now define the "patch" of points on the border that will be used
% to fit the sphere. First, we eliminate all points that are too
% far away from the reference point.
disp(['Creating a "patch" of voxels that the sphere will be ' ...
'fitted to ...']) ;
dist_away_rcp = sqrt( ...
((list_border_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_border_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_border_rcp(:,3) - ref_point_rcp(3)).^2)) ;
rrr = find(dist_away_rcp <= r_patch_rcp) ;
list_patch_bc_rcp = [] ;
list_patch_bc_rcp(:,1) = list_border_rcp(rrr, 1) ;
list_patch_bc_rcp(:,2) = list_border_rcp(rrr, 2) ;
list_patch_bc_rcp(:,3) = list_border_rcp(rrr, 3) ;
% Build an image volume for the patch. At this time, the patch is
% the set of all border voxels which are close enough to the
% reference point.
p_3d_bc = zeros(size(s_3d)) ;
for i = 1:length(list_patch_bc_rcp(:,1))
p_3d_bc(list_patch_bc_rcp(i,1), ...
list_patch_bc_rcp(i,2), ...
list_patch_bc_rcp(i,3)) = 1 ;
end
% Clean up the patch by discarding all stray voxels that are not
% connected to the seeds point. The seed point is the voxel on the
% border closest to the reference point.
disp('Cleaning up patch by discarding unconnected voxels ...') ;
dist_away_rcp = ...
sqrt(((list_patch_bc_rcp(:,1) - ref_point_rcp(1)).^2) ...
+ ((list_patch_bc_rcp(:,2) - ref_point_rcp(2)).^2) ...
+ ((list_patch_bc_rcp(:,3) - ref_point_rcp(3)).^2)) ;
[~, index_of_closest_point] = min(dist_away_rcp) ;
rcp_seed = [list_patch_bc_rcp(index_of_closest_point, 1) ...
list_patch_bc_rcp(index_of_closest_point, 2) ...
list_patch_bc_rcp(index_of_closest_point, 3)] ;
[p_3d, list_patch_rcp] = keep_connected_only(p_3d_bc, rcp_seed) ;
list_patch_rcp(:,1) = list_patch_rcp(:,1) ;
list_patch_rcp(:,2) = list_patch_rcp(:,2) ;
list_patch_rcp(:,3) = list_patch_rcp(:,3) ;
disp('*********************************************************') ;
disp(['Number of patch voxels before cleanup = ' ...
num2str(sum(sum(sum(p_3d_bc)))) '.']) ;
disp(['Number of patch voxels after cleanup = ' ...
num2str(sum(sum(sum(p_3d)))) '.']) ;
disp('*********************************************************') ;
% Build an image volume that is equal to one at the border voxels
% and equal to two at the border voxels that are close enough to
% the reference point. This image is used for display purposes
% only.
b_3d_with_patch = b_3d + p_3d ;
% Display all of the planes of the images.
if (display_verbose == 1)
disp('Displaying a movie of transverse planes ...') ;
disp_transverse_movie(g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
disp('Displaying a movie of sagittal planes ...') ;
disp_sagittal_movie( g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
disp('Displaying a movie of coronal planes ...') ;
disp_coronal_movie( g_3d, s_3d, b_3d_with_patch, ...
pause_before, pause_between) ;
end
% Estimate the parameters of the best-fit sphere using fminsearch.
disp('Performing fminsearch to find sphere of best fit ...') ;
center_init_rcp(1) = 0.5 * ...
(min_rcp_of_data(1) + max_rcp_of_data(1)) ;
center_init_rcp(2) = 0.5 * ...
(min_rcp_of_data(2) + max_rcp_of_data(2)) ;
center_init_rcp(3) = 0.5 * ...
(min_rcp_of_data(3) + max_rcp_of_data(3)) ;
radius_init_rcp = mean(sqrt( ...
((list_patch_rcp(:,1) - center_init_rcp(1)).^2) ...
+ ((list_patch_rcp(:,2) - center_init_rcp(2)).^2) ...
+ ((list_patch_rcp(:,3) - center_init_rcp(3)).^2))) ;
parms_init = [center_init_rcp(1) center_init_rcp(2) ...
center_init_rcp(3) radius_init_rcp] ;
f = @(x)fit_function_sphere(x, list_patch_rcp(:,1), ...
list_patch_rcp(:,2), list_patch_rcp(:,3)) ;
[parms_out, sse_search, exitflag] = fminsearch(f, parms_init) ;
center_est_rcp(1:3) = parms_out(1:3) ;
radius_est_rcp = parms_out(4) ;
center_est_xyz = center_est_rcp([2 1 3]) ;
center_est_mat_xyz(ref_point_num, :) = center_est_xyz ;
radius_est_mm = radius_est_rcp * drcp_mm(2) ;
radius_est_vec_mm(ref_point_num) = radius_est_mm ;
% Now display the results if CT only.
if (mr_empty ~= 0)
disp(['Displaying the transverse plane at the reference point ' ...
'and the normal vector ...']) ;
disp_transverse_normal(g_3d, b_3d, p_3d, ref_point_rcp, ...
center_est_rcp, pause_before) ;
figure(4) ;
title_string = ['Reference Point xyz = (' ...
num2str(ref_point_xyz(1)) ',' num2str(ref_point_xyz(2)) ...
',' num2str(ref_point_xyz(3)) ')'] ;
suptitle(title_string) ;
print_string = ['print -dpng -r600 ' outputs_directory '\' ...
out_file_name_root '_' num2str(ref_point_num, '%3.3d') ...
'_transverse.png'] ;
eval(print_string) ;
disp(['Displaying the sagittal plane at the reference point ' ...
'and the normal vector ...']) ;
disp_sagittal_normal(g_3d, b_3d, p_3d, ref_point_rcp, ...
center_est_rcp, pause_before) ;
figure(5) ;