forked from xiangruili/dicm2nii
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nii_viewer.m
3532 lines (3306 loc) · 143 KB
/
nii_viewer.m
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
function varargout = nii_viewer(fname, varargin)
% Basic tool to visualize NIfTI images.
%
% NII_VIEWER('/data/subj2/fileName.nii.gz')
% NII_VIEWER('background.nii', 'overlay.nii')
% NII_VIEWER('background.nii', {'overlay1.nii' 'overlay2.nii'})
%
% If no input is provided, the viewer will load included MNI_2mm brain as
% background NIfTI. Although the preferred format is NIfTI, NII_VIEWER accepts
% any files that can be converted into NIfTI by dicm2nii, including NIfTI,
% dicom, PAR/REC, etc. In case of CIfTI file, it will show the volume view, as
% well as surface view if GIfTI is available.
%
% Here are some features and usage.
%
% The basic use is to open a NIfTI file to view. When a NIfTI (background) is
% open, the display always uses the image plane close to xyz axes (voxel space)
% even for oblique acquisition. The possible confusion arises if the acquisition
% was tilted with a large angle, and then the orientation labels will be less
% accurate. The benefit is that no interpolation is needed for the background
% image, and there is no need to switch coordinate system when images with
% different systems are overlaid. The display is always in correct scale at
% three axes even with non-isotropic voxels. The displayed IJK always correspond
% to left -> right, posterior -> anterior and inferior -> superior directions,
% while the NIfTI data may not be saved in this order or along these directions.
% The I-index is increasing from left to right even when the display is flipped
% as radiological convention (right on left side).
%
% Navigation in 4D can be by mouse click, dialing IJK and volume numbers, or
% using keys (arrow keys and [ ] for 3D, and < > for volume).
%
% After the viewer is open, one can drag-and-drop a NIfTI file into the viewer
% to open as background, or drop a NIfTI with Control key down to add it as
% overlay.
%
% By default, the viewer shows full view of the background image data. The
% zoom-in always applies to three views together, and enlarges around the
% location of the crosshair. To zoom around a different location, set the
% crosshair to the interested location, and apply zoom again either by View ->
% Zoom in, or pressing Ctrl (Cmd) and +/-. View -> Set crosshair at -> center of
% view (or pressing key C) can set the crosshair to the center of display for
% all three views.
%
% Overlays are always mapped onto the coordinate of background image, so
% interpolation (nearest/linear/cubic/spline) is usually involved. The overlay
% makes sense only when it has the same coordinate system as the background
% image, while the resolution and dimension can be different. The viewer tries
% to match any of sform and qform between the images. If there is no match, a
% warning message will show up.
%
% A special overlay feature "Add aligned overlay" can be used to check the
% effect of registration, or overlay an image to a different coordinate system
% without creating a transformed file. It will ask for two files. The first is
% the overlay NIfTI file, and the second is either a transformation matrix file
% which aligns the overlay to the background image, or a warp file which
% transforms the overlay into the background reference.
%
% Here is an example to check FSL alignment. From a .feat/reg folder, Open
% "highres" as background image. "Add overlay" for "example_func". If there is
% head movement between the highres and the functional scans, the overlap will
% be off. Now "Add aligned overlay" for "example_func", and use
% example_func2highres.mat as the matrix. The two dataset should overlap well if
% the alignment matrix is accurate.
%
% Here is another example to overlay to a different coordinate system for FSL
% result. Open .feat/reg/standard.nii.gz as background. If an image like
% .feat/stats/zstat1.nii.gz is added as an overlay, a warning message will say
% inconsistent coordinate system since the zstat image is in Scanner Anat. The
% correct way is to "Add aligned overlay" for zstat1, and either use
% .feat/reg/example_func2standard.mat for linear transformation or better use
% .feat/reg/example_func2standard_warp.nii.gz if available for alignment.
%
% When the mouse pointer is moved onto a voxel, the x/y/z coordinates and voxel
% value will show on the panel. If there is an overlay, the overlay voxel value
% will also show up, unless its display is off. When the pointer moves onto the
% panel or out of an image, the information for the voxel at crosshair will be
% displayed. The display format is as following with val of the top image
% displayed first:
% (x,y,z): val_1 val_2 val_3 ...
%
% Note that although the x/y/z coordinates are shared by background and overlay
% images, IJK indices are always for background image (name shown on title bar).
%
% The mouse-over display can be turned on/off from Help -> Preferences ...
%
% If there is a .txt label file in the same folder as the NIfTI file, like for
% AAL, the labels will be shown instead of voxel value. The txt file should have
% a voxel value and ROI label pair per line, like
% 1 Precentral_L
% 2 Precentral_R
% 3 ...
%
% Image display can be smoothed in 3D (default is off). The smooth is slow when
% the image dimension is large, even when the current implementation of smooth
% does not consider voxel size.
%
% Background image and overlays are listed at the top-left of the panel. All
% parameters of the bottom row of the panel are for the selected image. This
% feature is indicated by a frame grouping these parameters. Each NIfTI file has
% its own set of parameters (display min and max value, LUT, alpha, whether to
% smooth, interpolation method, and volume number) to control its display.
% Moving the mouse onto a parameter will show the meaning of the parameter.
%
% The lastly added overlay is on the top of display and top of the file list.
% This also applies in case there is surface figure for CIfTI files. The
% background and overlay order can be changed by the two small arrows next to
% the list, or from Overlay -> Move selected image ...
%
% Each NIfTI display can be turned on/off by clicking the small checkbox at the
% left side of the file (or pressing spacebar for the selected NIfTI). This
% provides a way to turn on/off an overlay to view the overlap. Most operations
% are applied to the selected NIfTI in the list, such as Show NIfTI hdr/ext
% under Window menu, Move/Close overlay under Overlay menu, and most operations
% under File menu.
%
% A NIfTI mask can be applied to the selected image. Ideally, the mask should be
% binary, and the image corresponding to the non-zero part of the mask will be
% displayed. If non-binary mask is detected, a threshold to binarize will be
% asked. If the effect is not satisfied with the threshold, one can apply the
% mask with a different threshold without re-loading image. The way to remove a
% mask is to Remove, then Add overlay again. In case one likes to modulate the
% selected image with another NIfTI image (multiply two images), File -> Apply
% modulation will do it. If the mask image is not within 0 and 1, the lower and
% upper bound will be asked to normalize the range to [0 1]. A practical use of
% modulation is to use dti_FA map as weight for dti_V1 RGB image.
%
% For multi-volume data, one can change the Volume Number (the parameter at
% rightmost of the panel) to check the head motion. Click in the number dialer
% and or press < or > key, to simulate movie play. It is better to open the 4D
% data as background, since it may be slower to map it to the background image.
%
% Popular LUT options are implemented. Custom LUT can be added by Overlay ->
% Load LUT for selected overlay. The custom LUT file can be in text format (each
% line represents an RGB triplet, while the first line corresponds to the value
% 0 in the image data), or binary format (uint8 for all red, then green then
% blue). The color coding can be shown by View -> Show colorbar. There are
% several special LUTs. The "two-sided" allows to show both positive and
% negative data in one view. For example, if the display range is 3 to 10 for a
% t-map, positive T above 3 will be coded as red-yellow, and T below -3 will be
% coded as blue-green. This means the absolute display range values are used.
%
% One of the special LUT is "lines" in red text. This is for normalized vector
% display, e.g. for diffusion vector. The viewer will refuse the LUT selection
% if the data is not normalized vector. Under this LUT, all other parameters for
% the display are ignored. The color of the "lines" is the max color of previous
% LUT. For example, if one likes to show blue vector lines, choose LUT "blue"
% first, then change it to "lines".
%
% In case of complex image, most LUTs will display only the magnitude of the
% data, except the following three phase LUTs, where the magnitude is used as
% mask. Here is how the 3 phase LUTs encodes phase from 0 to 360 degree:
% phase: red-yellow monotonically,
% phase3: red-yellow-green-yellow-red circularly, and
% phase6: red-yellow-green/violet-blue-cyan, with sharp change at 180 degree.
% These LUTs are useful to visualize activation of periodic stimulus, such as
% those by expanding/shrinking ring or rotating wedge for retinotopy. To use
% this feature, one can save an complex NIfTI storing the Fourier component at
% the stimulus frequency.
%
% Note that, for RGB NIfTI, the viewer always displays the data as color
% regardless of the LUT option. The display min and max value also have no
% effect on RGB image. There is a special LUT, RGB, which is designed to display
% non-RGB NIfTI data as RGB, e.g. FSL-style 3-volome data.
%
% The viewer figure can be copied into clipboard (not available for Linux) or
% saved as variety of image format. For high quality picture, one can increase
% the output resolution by Help -> Preferences -> Resolution. Higher resolution
% will take longer time to copy or save, and result in larger file. If needed,
% one can change to white background for picture output. With white background,
% the threshold for the background image needs to be carefully selected to avoid
% strange effect with some background images. For this reason, white background
% is intended only for picture output.
%
% The selected NIfTI can also be saved as different format from File -> Save
% NIfTI as. For example, a file can be saved as a different resolution. With a
% transformation matrix, a file can also be saved into a different template. The
% latter is needed for FSLview since it won't allow overlay with different
% resolution or dimension at least till version 5.0.8.
%
% See also NII_TOOL DICM2NII NII_XFORM
%% By Xiangrui Li (xiangrui.li at gmail.com)
% History(yymmdd):
% 151021 Almost ready to publish.
% 151104 Include drag&drop by Maarten van der Seijs.
% 151105 Bug fix for Show NIfTI hdr/ext.
% 151106 Use p.interp to avoid unnecessary interp for overlay;
% Use check mark for colorbar/crosshair/background menu items.
% 151111 Take care of slope/inter of img; mask applies to DTI lines.
% 151114 Avoid see-thu for white background.
% 151119 Make smooth faster by using only 3 slices; Allow flip L/R.
% 151120 Implement key navigation and zoom in/out.
% 151121 Implement erode for white background; Add 'Show NIfTI essentials'.
% 151122 Bug fix for background img with sform=0.
% 151123 Show coordinate system in fig title; show masked/aligned in file list;
% Bug fix for alpha (0/1 only); Bug fix for background image R change.
% 151125 Avoid recursion for white background (failed under Linux).
% 151128 Change checkbox to radiobutton: looks better;
% Fix the bug in reorient dim(perm), introduced in last revision.
% 151129 Correct Center crosshair (was off by 0.5); Use evt.Key & add A/C/X/F1.
% 151201 Keep fig location & pref when 'open' & dnd: more user friendly.
% 151202 java robot trick allows to add overlay by Ctrl-dnd.
% 151207 Implement 'Add modulation'.
% 151208 Add xyz display for each view.
% 151217 Callback uses subfunc directly, and include fh as input.
% 151222 Show ROI labels (eg AAL) if .txt file with same name exists.
% 151224 Implement more matlab LUTs and custom LUT.
% 151230 Use listbox for files; Add stack buttons; file order reversed.
% 160102 Store p.R0 if need interp; No coordinate change for background.
% 160104 set_cdata: avoid indexing for single vol img: may be much faster!
% jscroll_handle from findjobj.m to set vertical scoll bar as needed.
% 160107 Rename XYZ label to IJK, implement "Set crosshair at XYZ".
% 160108 Fix the case of 2-form_code for background and addMask.
% 160109 Allow to turn off mouse-over display from Preferences;
% Implement Help -> Check update for easy update;
% Use Matlab built-in pref method for all files in the package.
% 160111 Use image ButtonDownFcn; Mouse down&move now is same as mouse move.
% 160112 Change back to line for crosshair: quiver is slow;
% Bug fix for white backgrnd & saveas backgrnd due to list order reverse.
% 160113 Implement time course for a cube.
% 160114 Figure visible avoids weird hanging problem for some matlab versions.
% 160119 Allow adding multiple overlays with cellstr as 2nd input.
% 160131 set_file: bug fix for cb(j); dnd: restore mouse location.
% 160208 Allow moving background image in stack.
% 160209 RGBA data supported; Background image can use lut "lines".
% 160218 "lines": Fix non-isovoxel display; Same-dim background not required.
% 160402 nii_xform_mat: make up R for possible Analyze file.
% 160506 phase LUT to map complex img: useful for retinotopy.
% 160509 Have 3 phase LUTs; Implement 'Open in new window'.
% 160512 KeyPressFcn bug fix: use the smallest axis dim when zoom in.
% 160517 KeyPressFcn: avoid double-dlg by Ctrl-A; phase6: bug fix for blue b3.
% 160531 use handle() for fh & others: allow dot convention for early matlab.
% 160601 Add cog and to image center, re-organize 'Set crosshair at' menu.
% 160602 bug fix for 'closeAll' files.String(ib); COG uses abs and excludes NaN.
% 160605 Add 'RGB' LUT to force RGB display: DTI_V1 or fsl style RGB file.
% 160608 javaDropFcn: 2 more method for ctlDn; bug fix for fh.xxx in Resize.
% 160620 Use JIDE CheckBoxList; Simplify KeyFcn by not focusing on active items.
% 160627 javaDropFcn: robot-click drop loc to avoid problem with vnc display.
% 160701 Implement hist for current volume; hs.value show top overlay first.
% 160703 bug fix for 'Add aligned' complex img: set p.phase after re-orient.
% 160710 Implement 'Create ROI file'; Time coure is for sphere, not cube.
% 160713 javaDropFnc for Linux: Robot key press replaces mouse click;
% Implement 'Set crosshair at' 'Smoothed maximum'.
% 160715 lut2img: bug fix for custom LUT; custom LUT uses rg [0 255]; add gap=0.
% 160721 Implement 'Set crosshair at' & 'a point with value of'.
% 160730 Allow to load single volume for large dataset.
% 161003 Add aligned overlay: accept FSL warp file as transformation.
% 161010 Implement 'Save volume as'; xyzr2roi: use valid q/sform.
% 161018 Take care of issue converting long file name to var name.
% 161103 Fix qform-only overlay, too long fig title, overlay w/o valid formCode.
% 161108 Implement "Crop below crosshair" to remove excessive neck tissue.
% 161115 Use .mat file for early spm Analyze file.
% 161216 Show more useful 4x4 R for both s/q form in nii essentials.
% 170109 bug fix: add .phase for background nifti.
% 170130 get_range: use nii as input, so always take care of slope/inter.
% 170210 Use flip for flipdim if available (in multiple files).
% 170212 Can open nifti-convertible files; Add Save NIfTI as -> a copy.
% 170421 java_dnd() changed as func, ControlDown OS independent by ACTION_LINK.
% 170515 Use area to normalize histogram.
% 171031 Implement layout. axes replace subplot to avoid overlap problem.
% 171129 bug fix save_nii_as(): undo img scale for no_save_nii.
% 171214 Try to convert back to volume in case of CIfTI (need anatomical gii).
% 171226 Store all info into sag img handle (fix it while it aint broke :)
% 171228 Surface view for gii (include HCP gii template).
% 171229 combine into one overlay for surface view.
% 180103 Allow inflated surface while mapping to correct location in volume.
% 180108 set_file back to nii_viewer_cb for cii_view_cb convenience.
% 180128 Preference stays for current window, and applies to new window only.
% 180228 'Add overlay' check nii struct in base workspace first.
% 180309 Implement 'Standard deviation' like for 'time course'.
% 180522 set_xyz: bug fix for display val >2^15.
%%
if nargin==2 && ischar(fname) && strcmp(fname, 'func_handle')
varargout{1} = str2func(varargin{1});
return;
elseif nargin>0 && ischar(fname) && strcmp(fname, 'LocalFunc')
[varargout{1:nargout}] = feval(varargin{:});
return;
end
if nargin<1 || isempty(fname) % open the included standard_2mm
fname = fullfile(fileparts(mfilename('fullpath')), 'example_data.mat');
fname = load(fname, 'nii'); fname = fname.nii;
end
nii = get_nii(fname);
[p, hs.form_code, rg, dim] = read_nii(nii); % re-oriented
p.Ri = inv(p.R);
nVol = size(p.nii.img, 4);
hs.bg.dim = p.nii.hdr.dim(2:4); % original dim and pixdim
hs.bg.pixdim = p.nii.hdr.pixdim(2:4);
hs.bg.R = p.R;
hs.bg.Ri = p.Ri;
hs.bg.perm = p.perm;
hs.bg.flip = p.flip;
hs.bg.hdr = p.nii.hdr;
if ~isreal(p.nii.img)
p.phase = angle(p.nii.img); % -pi to pi
p.phase = mod(p.phase/(2*pi), 1); % 0~1
p.nii.img = abs(p.nii.img); % real now
end
hs.dim = single(dim); % single saves memory for ndgrid
hs.pixdim = p.pixdim;
hs.gap = min(hs.pixdim) ./ hs.pixdim * 3; % in unit of smallest pixdim
p.lb = rg(1); p.ub = rg(2);
p = dispPara(p);
[pName, niiName, ext] = fileparts(p.nii.hdr.file_name);
if strcmpi(ext, '.gz'), [~, niiName] = fileparts(niiName); end
if nargin>1 && any(ishandle(varargin{1})) % called by Open or dnd
fh = varargin{1};
hsN = guidata(fh);
pf = hsN.pref.UserData; % use the current pref for unless new figure
fn = get(fh, 'Number');
close(fh);
else
pf = getpref('nii_viewer_para');
if isempty(pf) || ~isfield(pf, 'layout') % check lastly-added field
pf = struct('openPath', pwd, 'addPath', pwd, 'interp', 'linear', ...
'extraV', NaN, 'dpi', '0', 'rightOnLeft', false, ...
'mouseOver', true, 'layout', 2);
setpref('nii_viewer_para', fieldnames(pf), struct2cell(pf));
end
set(0, 'ShowHiddenHandles', 'on');
a = handle(findobj('Type', 'figure', 'Tag', 'nii_viewer'));
set(0, 'ShowHiddenHandles', 'off');
if isempty(a)
fn = 'ni' * 256.^(1:2)'; % start with a big number for figure
elseif numel(a) == 1
fn = get(a, 'Number') + 1; % this needs handle() to work
else
fn = max(cell2mat(get(a, 'Number'))) + 1;
end
end
[siz, axPos, figPos] = plot_pos(dim.*hs.pixdim, pf.layout);
fh = figure(fn);
hs.fig = handle(fh); % have to use numeric for uipanel for older matlab
figNam = p.nii.hdr.file_name;
if numel(figNam)>40, figNam = [figNam(1:40) '...']; end
figNam = ['nii_viewer - ' figNam ' (' formcode2str(hs.form_code(1)) ')'];
set(fh, 'Toolbar', 'none', 'Menubar', 'none', 'Renderer', 'opengl', ...
'NumberTitle', 'off', 'Tag', 'nii_viewer', 'DockControls', 'off', ...
'Position', [figPos siz+[0 64]], 'Name', figNam);
cb = @(cmd) {@nii_viewer_cb cmd hs.fig}; % callback shortcut
xyz = [0 0 0]; % start cursor location
c = round(p.Ri * [xyz 1]'); c = c(1:3)' + 1; %
ind = c<=1 | c>=dim;
c(ind) = round(dim(ind)/2);
% c = round(dim/2); % start cursor at the center of images
xyz = round(p.R * [c-1 1]'); % take care of rounding error
%% control panel
pos = getpixelposition(fh); pos = [1 pos(4)-64 pos(3) 64];
hs.panel = uipanel(fh, 'Units', 'pixels', 'Position', pos, 'BorderType', 'none');
hs.focus = uicontrol(hs.panel, 'Style', 'text'); % dummy uicontrol for focus
% file list by JIDE CheckBoxList: check/selection independent
mdl = handle(javax.swing.DefaultListModel, 'CallbackProperties'); % dynamic item
mdl.add(0, niiName);
mdl.IntervalAddedCallback = cb('width');
mdl.IntervalRemovedCallback = cb('width');
mdl.ContentsChangedCallback = cb('width'); % add '(mask)' etc
h = handle(com.jidesoft.swing.CheckBoxList(mdl), 'CallbackProperties');
h.setFont(java.awt.Font('Tahoma', 0, 11));
% h.ClickInCheckBoxOnly = true; % it's default
h.setSelectionMode(0); % single selection
h.setSelectedIndex(0); % 1st highlighted
h.addCheckBoxListSelectedIndex(0); % check it
h.ValueChangedCallback = cb('file'); % selection change
h.MouseReleasedCallback = @(~,~)uicontrol(hs.focus); % move focus away
% h.Focusable = false;
h.setToolTipText(['<html>Select image to show/modify its display ' ...
'parameters.<br>Click checkbox to turn on/off image']);
jScroll = com.mathworks.mwswing.MJScrollPane(h);
width = h.getPreferredScrollableViewportSize.getWidth;
width = max(60, min(width+20, pos(3)-408)); % 20 pixels for vertical scrollbar
[~, hs.scroll] = javacomponent(jScroll, [2 4 width 60], hs.panel);
hCB = handle(h.getCheckBoxListSelectionModel, 'CallbackProperties');
hCB.ValueChangedCallback = cb('toggle'); % check/uncheck
hs.files = javaObjectEDT(h); % trick to avoid java error by Yair
% panel for controls except hs.files
pos = [width 1 pos(3)-width pos(4)];
ph = uipanel(hs.panel, 'Units', 'pixels', 'Position', pos, 'BorderType', 'none');
clr = get(ph, 'BackgroundColor');
hs.params = ph;
feature('DefaultCharacterSet', 'UTF-8'); % for old matlab to show triangles
hs.overlay(1) = uicontrol(ph, 'Style', 'pushbutton', 'FontSize', 7, ...
'Callback', cb('stack'), 'Enable', 'off', 'SelectionHighlight', 'off', ...
'String', char(9660), 'Position', [1 36 16 15], 'Tag', 'down', ...
'TooltipString', 'Move selected image one level down');
hs.overlay(2) = copyobj(hs.overlay(1), ph);
set(hs.overlay(2), 'Callback', cb('stack'), ...
'String', char(9650), 'Position', [1 50 16 15], 'Tag', 'up', ...
'TooltipString', 'Move selected image one level up');
hs.value = uicontrol(ph, 'Style', 'text', 'Position', [208 38 pos(3)-208 20], ...
'BackgroundColor', clr, 'FontSize', 8+(~ispc && ~ismac), ...
'TooltipString', '(x,y,z): top ... bottom');
% IJK java spinners
labls = 'IJK';
str = {'Left to Right' 'Posterior to Anterior' 'Inferior to Superior'};
pos = [38 44 22]; posTxt = [36 10 20];
for i = 1:3
loc = [(i-1)*64+34 pos];
txt = sprintf('%s, 1:%g', str{i}, dim(i));
hs.ijk(i) = java_spinner(loc, [c(i) 1 dim(i) 1], ph, cb('ijk'), '#', txt);
uicontrol(ph, 'Style', 'text', 'String', labls(i), 'BackgroundColor', clr, ...
'Position', [loc(1)-11 posTxt], 'TooltipString', txt, 'FontWeight', 'bold');
end
% Controls for each file
uipanel('Parent', ph, 'Units', 'pixels', 'Position', [1 2 412 34], ...
'BorderType', 'etchedin', 'BorderWidth', 2);
hs.lb = java_spinner([7 8 48 22], [p.lb -inf inf p.lb_step], ph, ...
cb('lb'), '#.##', 'min value (threshold)');
hs.ub = java_spinner([59 8 56 22], [p.ub -inf inf p.ub_step], ph, ...
cb('ub'), '#.##', 'max value (clipped)');
hs.lutStr = {'grayscale' 'red' 'green' 'blue' 'violet' 'yellow' 'cyan' ...
'red-yellow' 'blue-green' 'two-sided' '<html><font color="red">lines' ...
'parula' 'jet' 'hsv' 'hot' 'cool' 'spring' 'summer' 'autumn' 'winter' ...
'bone' 'copper' 'pink' 'prism' 'flag' 'phase' 'phase3' 'phase6' 'RGB' 'custom'};
hs.lut = uicontrol(ph, 'Style', 'popup', 'Position', [113 8 74 22], ...
'String', hs.lutStr, 'BackgroundColor', 'w', 'Callback', cb('lut'), ...
'Value', p.lut, 'TooltipString', 'Lookup table options for non-RGB data');
if p.lut==numel(hs.lutStr), set(hs.lut, 'Enable', 'off'); end
hs.alpha = java_spinner([187 8 44 22], [1 0 1 0.1], ph, cb('alpha'), '#.#', ...
'Alpha: 0 transparent, 1 opaque');
hs.smooth = uicontrol(ph, 'Style', 'checkbox', 'Value', p.smooth, ...
'Position', [231 8 60 22], 'String', 'smooth', 'BackgroundColor', clr, ...
'Callback', cb('smooth'), 'TooltipString', 'Smooth image in 3D');
hs.interp = uicontrol(ph, 'Style', 'popup', 'Position', [291 8 68 22], ...
'String', {'nearest' 'linear' 'cubic' 'spline'}, 'Value', p.interp, ...
'Callback', cb('interp'), 'Enable', 'off', 'BackgroundColor', 'w', ...
'TooltipString', 'Interpolation method for overlay');
hs.volume = java_spinner([361 8 44 22], [1 1 nVol 1], ph, cb('volume'), '#', ...
['Volume number, 1:' num2str(nVol)]);
hs.volume.setEnabled(nVol>1);
%% Three views: sag, cor, tra
% this panel makes resize easy: axes relative to panel
hs.frame = uipanel(fh, 'Units', 'pixels', 'Position', [1 1 siz], ...
'BorderType', 'none', 'BackgroundColor', 'k');
for i = 1:3
j = 1:3; j(j==i) = [];
hs.ax(i) = axes('Position', axPos(i,:), 'Parent', hs.frame);
hs.hsI(i) = handle(image(zeros(dim(j([2 1])), 'single')));
hold(hs.ax(i), 'on');
x = [c(j(1))+[-1 1 0 0]*hs.gap(j(1)); 0 dim(j(1))+1 c(j(1))*[1 1]];
y = [c(j(2))+[0 0 -1 1]*hs.gap(j(2)); c(j(2))*[1 1] 0 dim(j(2))+1];
hs.cross(i,:) = line(x, y);
hs.xyz(i) = text(0.02, 0.96, num2str(xyz(i)), 'Units', 'normalized', ...
'Parent', hs.ax(i), 'FontSize', 12);
end
set(hs.hsI, 'ButtonDownFcn', cb('mousedown'));
p.hsI = hs.hsI; % background img
p.hsI(1).UserData = p; % store everything in sag img UserData
labls='ASLSLP';
pos = [0.95 0.5; 0.47 0.96; 0 0.5; 0.47 0.96; 0 0.5; 0.47 0.05];
for i = 1:numel(labls)
hs.ras(i) = text(pos(i,1), pos(i,2), labls(i), 'Units', 'normalized', ...
'Parent', hs.ax(ceil(i/2)), 'FontSize', 12, 'FontWeight', 'bold');
end
% early matlab's colormap works only for axis, so ax(4) is needed.
hs.ax(4) = axes('Position', axPos(4,:), 'Parent', hs.frame);
try
hs.colorbar = colorbar(hs.ax(4), 'YTicks', [0 0.5 1], 'Color', [1 1 1], ...
'Location', 'West', 'PickableParts', 'none', 'Visible', 'off');
catch % for early matlab
colorbar('peer', hs.ax(4), 'Location', 'West', 'Units', 'Normalized');
hs.colorbar = findobj(fh, 'Tag', 'Colorbar');
set(hs.colorbar, 'Visible', 'off', 'HitTest', 'off', 'EdgeColor', [1 1 1]);
end
% image() reverses YDir. Turn off ax and ticks
set(hs.ax, 'YDir', 'normal', 'Visible', 'off');
set([hs.ras hs.cross(:)' hs.xyz], 'Color', 'b', 'UIContextMenu', '');
try set([hs.ras hs.cross(:)' hs.xyz], 'PickableParts', 'none'); % new matlab
catch, set([hs.ras hs.cross(:)' hs.xyz], 'HitTest', 'off'); % old ones
end
%% menus
h = uimenu(fh, 'Label', '&File');
uimenu(h, 'Label', 'Open', 'Accelerator', 'O', 'UserData', pName, 'Callback', cb('open'));
uimenu(h, 'Label', 'Open in new window', 'Callback', cb('open'));
uimenu(h, 'Label', 'Apply mask', 'Callback', @addMask);
uimenu(h, 'Label', 'Apply modulation', 'Callback', @addMask);
h_savefig = uimenu(h, 'Label', 'Save figure as');
h_saveas = uimenu(h, 'Label', 'Save NIfTI as');
uimenu(h, 'Label', 'Save volume as ...', 'Callback', cb('saveVolume'));
uimenu(h, 'Label', 'Crop below crosshair', 'Callback', cb('cropNeck'));
uimenu(h, 'Label', 'Create ROI file ...', 'Callback', cb('ROI'));
uimenu(h, 'Label', 'Close window', 'Accelerator', 'W', 'Callback', 'close gcf');
uimenu(h_saveas, 'Label', 'SPM 3D NIfTI (one file/pair per volume)', 'Callback', @save_nii_as);
uimenu(h_saveas, 'Label', 'NIfTI standard RGB (for AFNI, later mricron)', ...
'Callback', @save_nii_as, 'Separator', 'on');
uimenu(h_saveas, 'Label', 'FSL style RGB (RGB saved in dim 4)', 'Callback', @save_nii_as);
uimenu(h_saveas, 'Label', 'Old mricron style RGB (RGB saved in dim 3)', 'Callback', @save_nii_as);
uimenu(h_saveas, 'Label', 'a copy', 'Callback', @save_nii_as, 'Separator', 'on');
uimenu(h_saveas, 'Label', 'file with a new resolution', 'Callback', @save_nii_as);
uimenu(h_saveas, 'Label', 'file matching background', 'Callback', @save_nii_as);
uimenu(h_saveas, 'Label', 'file in aligned template space', 'Callback', @save_nii_as);
fmt = {'png' 'jpg' 'tif' 'bmp' 'pdf' 'eps'};
if ispc, fmt = [fmt 'emf']; end
for i = 1:numel(fmt)
uimenu(h_savefig, 'Label', fmt{i}, 'Callback', cb('save'));
end
if ispc || ismac
h = uimenu(fh, 'Label', '&Edit');
uimenu(h, 'Label', 'Copy figure', 'Callback', cb('copy'));
end
h_over = uimenu(fh, 'Label', '&Overlay');
uimenu(h_over, 'Label', 'Add overlay', 'Accelerator', 'A', 'Callback', cb('add'));
uimenu(h_over, 'Label', 'Add aligned overlay', 'Callback', cb('add'));
h = uimenu(h_over, 'Label', 'Move selected image', 'Enable', 'off');
uimenu(h, 'Label', 'to top', 'Callback', cb('stack'), 'Tag', 'top');
uimenu(h, 'Label', 'to bottom', 'Callback', cb('stack'), 'Tag', 'bottom');
uimenu(h, 'Label', 'one level up', 'Callback', cb('stack'), 'Tag', 'up');
uimenu(h, 'Label', 'one level down', 'Callback', cb('stack'), 'Tag', 'down');
hs.overlay(3) = h;
hs.overlay(5) = uimenu(h_over, 'Label', 'Remove overlay', 'Accelerator', 'R', ...
'Callback', cb('close'), 'Enable', 'off');
hs.overlay(4) = uimenu(h_over, 'Label', 'Remove overlays', ...
'Callback', cb('closeAll'), 'Enable', 'off');
uimenu(h_over, 'Label', 'Load LUT for current overlay', 'Callback', cb('custom'));
h_view = uimenu(fh, 'Label', '&View');
h = uimenu(h_view, 'Label', 'Zoom in by');
for i = [1 1.2 1.5 2 3 4 5 8 10 20]
uimenu(h, 'Label', num2str(i), 'Callback', cb('zoom'));
end
h = uimenu(h_view, 'Label', 'Layout', 'UserData', pf.layout);
uimenu(h, 'Label', 'one-row', 'Callback', cb('layout'));
uimenu(h, 'Label', 'two-row sag on right', 'Callback', cb('layout'));
uimenu(h, 'Label', 'two-row sag on left', 'Callback', cb('layout'));
uimenu(h_view, 'Label', 'White background', 'Callback', cb('background'));
hLR = uimenu(h_view, 'Label', 'Right on left side', 'Callback', cb('flipLR'));
uimenu(h_view, 'Label', 'Show colorbar', 'Callback', cb('colorbar'));
uimenu(h_view, 'Label', 'Show crosshair', 'Separator', 'on', ...
'Checked', 'on', 'Callback', cb('cross'));
h = uimenu(h_view, 'Label', 'Set crosshair at');
uimenu(h, 'Label', 'center of view', 'Callback', cb('viewCenter'));
uimenu(h, 'Label', 'center of image', 'Callback', cb('center'));
uimenu(h, 'Label', 'COG of image', 'Callback', cb('cog'));
uimenu(h, 'Label', 'Smoothed maximum', 'Callback', cb('maximum'));
uimenu(h, 'Label', 'a point [x y z] ...', 'Callback', cb('toXYZ'));
uimenu(h, 'Label', 'a point with value of ...', 'Callback', cb('toValue'));
uimenu(h_view, 'Label', 'Crosshair color', 'Callback', cb('color'));
h = uimenu(h_view, 'Label', 'Crosshair gap');
for i = [0 1 2 3 4 5 6 8 10 20 40]
str = num2str(i); if i==6, str = [str ' (default)']; end %#ok
uimenu(h, 'Label', str, 'Callback', cb('gap'));
end
h = uimenu(h_view, 'Label', 'Crosshair thickness');
uimenu(h, 'Label', '0.5 (default)', 'Callback', cb('thickness'));
for i = [0.75 1 2 4 8]
uimenu(h, 'Label', num2str(i), 'Callback', cb('thickness'));
end
h = uimenu(fh, 'Label', '&Window');
uimenu(h, 'Label', 'Show NIfTI essentials', 'Callback', cb('essential'));
uimenu(h, 'Label', 'Show NIfTI hdr', 'Callback', cb('hdr'));
uimenu(h, 'Label', 'Show NIfTI ext', 'Callback', cb('ext'));
uimenu(h, 'Label', 'DICOM to NIfTI converter', 'Callback', 'dicm2nii', 'Separator', 'on');
th = uimenu(h, 'Label', 'Time course ...', 'Callback', cb('tc'), 'Separator', 'on');
setappdata(th, 'radius', 6);
th = uimenu(h, 'Label', 'Standard deviation ...', 'Callback', cb('tc'));
setappdata(th, 'radius', 6);
uimenu(h, 'Label', 'Histogram', 'Callback', cb('hist'));
h = uimenu(fh, 'Label', '&Help');
hs.pref = uimenu(h, 'Label', 'Preferences', 'UserData', pf, 'Callback', @pref_dialog);
uimenu(h, 'Label', 'Key shortcut', 'Callback', cb('keyHelp'));
uimenu(h, 'Label', 'Show help text', 'Callback', 'doc nii_viewer');
checkUpdate = dicm2nii('', 'checkUpdate', 'func_handle');
uimenu(h, 'Label', 'Check update', 'Callback', @(~,~)checkUpdate('nii_viewer'));
uimenu(h, 'Label', 'About', 'Callback', cb('about'));
%% finalize gui
if isnumeric(fh) % for older matlab
fh = handle(fh);
schema.prop(fh, 'Number', 'mxArray'); fh.Number = fn;
hs.lut = handle(hs.lut);
hs.frame = handle(hs.frame);
hs.value = handle(hs.value);
hs.panel = handle(hs.panel);
hs.params = handle(hs.params);
hs.scroll = handle(hs.scroll);
hs.pref = handle(hs.pref);
end
guidata(fh, hs); % store handles and data
%% java_dnd based on dndcontrol at matlabcentral/fileexchange/53511
try % panel has JavaFrame in later matlab
jFrame = handle(hs.frame.JavaFrame.getGUIDEView, 'CallbackProperties');
catch
warning('off', 'MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
jFrame = fh.JavaFrame.getAxisComponent;
end
try java_dnd(jFrame, cb('drop')); catch me, disp(me.message); end
set(fh, 'ResizeFcn', cb('resize'), ... % 'SizeChangedFcn' for later matlab
'WindowKeyPressFcn', @KeyPressFcn, 'CloseRequestFcn', cb('closeFig'), ...
'PaperPositionMode', 'auto', 'InvertHardcopy', 'off', 'HandleVisibility', 'Callback');
nii_viewer_cb(fh, [], 'resize', fh); % avoid some weird problem
if pf.mouseOver, set(fh, 'WindowButtonMotionFcn', cb('mousemove')); end
if pf.rightOnLeft, nii_viewer_cb(hLR, [], 'flipLR', fh); end
set_cdata(hs);
set_xyz(hs);
if nargin>1
if ischar(varargin{1})
addOverlay(varargin{1}, fh);
elseif iscellstr(varargin{1})
for i=1:numel(varargin{1}), addOverlay(varargin{1}{i}, fh); end
end
end
if hs.form_code(1)<1
warndlg(['There is no valid form code in NIfTI. The orientation ' ...
'labeling is likely meaningless.']);
end
if isfield(p.nii, 'cii'), cii_view(hs); end
%% Get info from sag img UserData
function p = get_para(hs, iFile)
if nargin<2, iFile = hs.files.getSelectedIndex + 1; end
hsI = findobj(hs.ax(1), 'Type', 'image', '-or', 'Type', 'quiver');
p = get(hsI(iFile), 'UserData');
%% callbacks
function nii_viewer_cb(h, evt, cmd, fh)
hs = guidata(fh);
switch cmd
case 'ijk' % IJK spinner
ix = find(h == hs.ijk);
set_cdata(hs, ix);
set_cross(hs, ix);
xyz = set_xyz(hs);
for i = 1:3, set(hs.xyz(i), 'String', xyz(i)); end % need 3 if oblique
case 'mousedown' % image clicked
% if ~strcmp(get(fh, 'SelectionType'), 'normal'), return; end
ax = gca;
c = get(ax, 'CurrentPoint');
c = round(c(1, 1:2));
i = 1:3;
i(ax==hs.ax(1:3)) = [];
hs.ijk(i(1)).setValue(c(1));
hs.ijk(i(2)).setValue(c(2));
case {'lb' 'ub' 'lut' 'alpha' 'smooth' 'interp' 'volume'}
if ~strcmp(cmd, 'volume'), uicontrol(hs.focus); end % move away focus
p = get_para(hs);
val = get(h, 'Value');
if val == 11 && val~=p.lut
hs.lut.UserData = p.lut; % remember old lut
end
if strcmp(cmd, 'lut')
err = false;
if val == 11 % error check for vector lines
err = true;
if size(p.nii.img,4)~=3
errordlg('Not valid vector data: dim4 is not 3');
else
a = sum(p.nii.img.^2, 4); a = a(a(:)>1e-4);
if any(abs(a-1)>0.1)
errordlg('Not valid vector data: squared sum is not 1');
else, err = false; % passed all checks
end
end
elseif any(val == 26:28) % error check for phase img
err = ~isfield(p, 'phase');
if err, warndlg('Seleced image is not complex data.'); end
elseif val == 29 % RGB
err = size(p.nii.img,4)~=3;
if err, errordlg('RGB LUT requres 3-volume data.'); end
elseif val == numel(hs.lutStr)
err = true;
errordlg('Custom LUT is used be NIfTI itself');
end
if err, hs.lut.Value = p.lut; return; end % undo selection
end
p.hsI(1).UserData.(cmd) = val;
if any(strcmp(cmd, {'lut' 'lb' 'ub' 'volume'})), set_colorbar(hs); end
if strcmp(cmd, 'volume'), set_xyz(hs); end
set_cdata(hs);
case 'resize'
if isempty(hs), return; end
cb = fh.ResizeFcn;
fh.ResizeFcn = []; drawnow; % avoid weird effect
clnObj = onCleanup(@() set(fh, 'ResizeFcn', cb)); % restore func
posP = getpixelposition(hs.panel); % get old height in pixels
posF = getpixelposition(fh); % asked position by user
posI = getpixelposition(hs.frame); % old size
siz = hs.frame.Position(3:4);
res = screen_pixels(1);
oldF = round([posI(3) posI(4)+posP(4)]); % previous fig size
if isequal(oldF, posF(3:4)), return; end % moving without size change
if all(posF(3:4) >= oldF) % enlarge
a = max([posF(3) posF(4)-posP(4)] ./ siz) * siz;
a(1) = min(a(1), res(1)-30); % leave space for MAC dock etc
a(2) = min(a(2), res(2)-92-posP(4)); % leave space for title bar etc
a = min(a ./ siz) * siz;
elseif all(posF(3:4) <= oldF) % shrink
a = min([posF(3) posF(4)-posP(4)] ./ siz) * siz;
else % one side enlarge, another side shrink: use old size
a = posI(3:4);
end
d = posF(1)+a(1)-res(1);
if d>0, posF(1) = posF(1) - d; end
d = posF(2)+a(2)+posP(4)+92-res(2);
if d>0, posF(2) = posF(2) - d; end
posF(1) = max(posF(1), 10);
posF(2) = max(posF(2), 50);
posF(3:4) = [a(1) a(2)+posP(4)]; % final figure size
fh.Position = posF; % done for fig
posP(2) = posF(4)-posP(4)+1;
posP(3) = posF(3);
hs.panel.Position = posP; % done for control panel
hs.frame.Position = [1 1 a]; % done for image panel
nii_viewer_cb([], [], 'width', fh);
case 'toggle' % turn on/off NIfTI
i = h.getAnchorSelectionIndex+1;
if i<1, return; end
checked = hs.files.getCheckBoxListSelectedIndices+1;
p = get_para(hs, i);
if p.show == any(checked==i), return; end % no change
p.show = ~p.show;
p.hsI(1).UserData = p;
states = {'off' 'on'};
try %#ok<*TRYNC>
set(p.hsI, 'Visible', states{p.show+1});
if p.show, set_cdata(hs); end
set_xyz(hs);
end
case 'mousemove'
% if ~strcmp(get(fh, 'SelectionType'), 'normal'), return; end
c = cell2mat(get(hs.ax(1:3), 'CurrentPoint'));
c = c([1 3 5], 1:2); % 3x2
x = cell2mat(get(hs.ax(1:3), 'XLim'));
y = cell2mat(get(hs.ax(1:3), 'YLim'));
I = cell2mat(get(hs.ijk, 'Value'))';
if c(1,1)>x(1,1) && c(1,1)<x(1,2) && c(1,2)>y(1,1) && c(1,2)<y(1,2)%sag
I = [I(1) c(1,:)];
elseif c(2,1)>x(2,1) && c(2,1)<x(2,2) && c(2,2)>y(2,1) && c(2,2)<y(2,2)%cor
I = [c(2,1) I(2) c(2,2)];
elseif c(3,1)>x(3,1) && c(3,1)<x(3,2) && c(3,2)>y(3,1) && c(3,2)<y(3,2)%tra
I = [c(3,:) I(3)];
end
set_xyz(hs, I);
case 'open' % open on current fig or new fig
pName = hs.pref.UserData.openPath;
[fname, pName] = uigetfile([pName '/*.nii; *.hdr;*.nii.gz; *.hdr.gz'], ...
'Select a NIfTI to view', 'MultiSelect', 'on');
if isnumeric(fname), return; end
fname = strcat([pName '/'], fname);
if strcmp(get(h, 'Label'), 'Open in new window'), nii_viewer(fname);
else, nii_viewer(fname, fh);
end
return;
case 'add' % add overlay
vars = evalin('base', 'who');
is_nii = @(v)evalin('base', ...
sprintf('isstruct(%s) && all(isfield(%s,{''hdr'',''img''}))', v, v));
for i = numel(vars):-1:1, if ~is_nii(vars{i}), vars(i) = []; end; end
if ~isempty(vars)
a = listdlg('SelectionMode', 'single', 'ListString', vars, ...
'ListSize', [300 100], 'CancelString', 'File dialog', ...
'Name', 'Select a NIfTI in the list or click File dialog');
if ~isempty(a), fname = evalin('base', vars{a}); end
end
pName = hs.pref.UserData.addPath;
label = get(h, 'Label');
if strcmp(label, 'Add aligned overlay')
if ~exist('fname', 'var')
[fname, pName] = uigetfile([pName '/*.nii; *.hdr;*.nii.gz;' ...
'*.hdr.gz'], 'Select overlay NIfTI');
if ~ischar(fname), return; end
fname = fullfile(pName, fname);
end
[mtx, pName] = uigetfile([pName '/*.mat;*_warp.nii;*_warp.nii.gz'], ...
'Select FSL mat file or warp file transforming the nii to background');
if ~ischar(mtx), return; end
mtx = fullfile(pName, mtx);
addOverlay(fname, fh, mtx);
else
if ~exist('fname', 'var')
[fname, pName] = uigetfile([pName '/*.nii; *.hdr;*.nii.gz;' ...
'*.hdr.gz'], 'Select overlay NIfTI', 'MultiSelect', 'on');
if ~ischar(fname) && ~iscell(fname), return; end
fname = get_nii(strcat([pName filesep], fname));
end
addOverlay(fname, fh);
end
setpref('nii_viewer_para', 'addPath', pName);
case 'closeAll' % close all overlays
for j = hs.files.getModel.size:-1:1
p = get_para(hs, j);
if p.hsI(1) == hs.hsI(1), continue; end
delete(p.hsI); % remove image
hs.files.getModel.remove(j-1);
end
hs.files.setSelectedIndex(0);
set_xyz(hs);
case 'close' % close selected overlay
jf = hs.files.getSelectedIndex+1;
p = get_para(hs, jf);
if p.hsI(1) == hs.hsI(1), return; end % no touch to background
delete(p.hsI); % 3 view
hs.files.getModel.remove(jf-1);
hs.files.setSelectedIndex(max(0, jf-2));
set_xyz(hs);
case {'hdr' 'ext' 'essential'} % show hdr ext or essential
jf = hs.files.getSelectedIndex+1;
p = get_para(hs, jf);
if strcmp(cmd, 'hdr')
hdr = p.nii.hdr;
elseif strcmp(cmd, 'ext')
if ~isfield(p.nii, 'ext')
errordlg('No extension for the selected NIfTI');
return;
end
hdr = {};
for i = 1:numel(p.nii.ext)
if ~isfield(p.nii.ext(i), 'edata_decoded'), continue; end
hdr{end+1} = p.nii.ext(i).edata_decoded; %#ok
end
if isempty(hdr)
errordlg('No known extension for the selected NIfTI');
return;
elseif numel(hdr) == 1, hdr = hdr{1};
end
elseif strcmp(cmd, 'essential')
hdr = nii_essential(p);
end
nam = hs.files.getModel.get(jf-1);
if ~isstrprop(nam(1), 'alpha'), nam = ['x' nam]; end % like genvarname
nam(~isstrprop(nam, 'alphanum')) = '_'; % make it valid for var name
nam = [nam '_' cmd];
nam = strrep(nam, '__', '_');
n = numel(nam); nm = namelengthmax;
if n>nm, nam(nm-4:n-4) = ''; end
assignin('base', nam, hdr);
evalin('base', ['openvar ' nam]);
case 'cross' % show/hide crosshairs and RAS labels
if strcmp(get(h, 'Checked'), 'on')
set(h, 'Checked', 'off');
set([hs.cross(:)' hs.ras hs.xyz], 'Visible', 'off');
else
set(h, 'Checked', 'on');
set([hs.cross(:)' hs.ras hs.xyz], 'Visible', 'on');
end
case 'color' % crosshair color
c = uisetcolor(get(hs.ras(1), 'Color'), 'Pick crosshair color');
if numel(c) ~= 3, return; end
set([hs.cross(:)' hs.ras hs.xyz], 'Color', c);
case 'thickness' % crosshair thickness
c = strtok(get(h, 'Label'));
set(hs.cross(:)', 'LineWidth', str2double(c));
case 'gap' % crosshair gap
c = str2double(strtok(get(h, 'Label')));
hs.gap = min(hs.pixdim) ./ hs.pixdim * c / 2;
guidata(fh, hs);
set_cross(hs, 1:3);
case 'copy' % copy figure into clipboard
fh1 = ancestor(h, 'figure');
if strncmp(get(fh1, 'Name'), 'nii_viewer', 10)
set(hs.panel, 'Visible', 'off');
clnObj = onCleanup(@() set(hs.panel, 'Visible', 'on'));
end
print(fh1, '-dbitmap', '-noui', ['-r' hs.pref.UserData.dpi]);
% print('-dmeta', '-painters');
case 'save' % save figure as picture
ext = get(h, 'Label');
fmt = ext;
if strcmp(ext, 'jpg'), fmt = 'jpeg';
elseif strcmp(ext, 'tif'), fmt = 'tiff';
elseif strcmp(ext, 'eps'), fmt = 'epsc';
elseif strcmp(ext, 'emf'), fmt = 'meta';
end
[fname, pName] = uiputfile(['*.' ext], 'Input file name to save figure');
if ~ischar(fname), return; end
fname = fullfile(pName, fname);
if any(strcmp(ext, {'eps' 'pdf' 'emf'})), render = '-painters';
else, render = '-opengl';
end
fh1 = ancestor(h, 'figure');
if strncmp(get(fh1, 'Name'), 'nii_viewer', 10)
set(hs.panel, 'Visible', 'off');
clnObj = onCleanup(@() set(hs.panel, 'Visible', 'on'));
end
print(fh1, fname, render, '-noui', ['-d' fmt], ['-r' hs.pref.UserData.dpi], '-cmyk');
case 'colorbar' % colorbar on/off
if strcmpi(get(hs.colorbar, 'Visible'), 'on')
set(hs.colorbar, 'Visible', 'off');
set(h, 'Checked', 'off');
else
set(hs.colorbar, 'Visible', 'on');
set(h, 'Checked', 'on');
set_colorbar(hs);
end
case 'about'
reviseDate = dicm2nii('', 'reviseDate', 'func_handle');
str = sprintf(['nii_viewer.m by Xiangrui Li\n\n' ...
'Last updated on 20%s\n\n', ...
'Feedback to: [email protected]\n'], reviseDate(mfilename));
helpdlg(str, 'About nii_viewer')
case 'stack'
uicontrol(hs.focus); % move focus out of buttons
jf = hs.files.getSelectedIndex+1;
p = get_para(hs, jf);
n = hs.files.getModel.size;
switch get(h, 'Tag') % for both uimenu and pushbutton
case 'up' % one level up
if jf==1, return; end
for j = 1:3, uistack(p.hsI(j)); end
ind = [1:jf-2 jf jf-1 jf+1:n]; jf = jf-1;
case 'down' % one level down
if jf==n, return; end
for j = 1:3, uistack(p.hsI(j), 'down'); end
ind = [1:jf-1 jf+1 jf jf+2:n]; jf = jf+1;
case 'top'
if jf==1, return; end
for j = 1:3, uistack(p.hsI(j), 'up', jf-1); end
ind = [jf 1:jf-1 jf+1:n]; jf = 1;
case 'bottom'
if jf==n, return; end
for j = 1:3, uistack(p.hsI(j), 'down', n-jf); end
ind = [1:jf-1 jf+1:n jf]; jf = n;
otherwise
error('Unknown stack level: %s', get(h, 'Tag'));
end
str = cell(hs.files.getModel.toArray);
str = str(ind);
for j = 1:n, hs.files.getModel.set(j-1, str{j}); end
chk = false(1,n);
chk(hs.files.getCheckBoxListSelectedIndices+1) = true;
chk = find(chk(ind)) - 1;
if ~isempty(chk), hs.files.setCheckBoxListSelectedIndices(chk); end
hs.files.setSelectedIndex(jf-1);
set_xyz(hs);
case 'zoom'
m = str2double(get(h, 'Label'));
a = min(hs.dim) / m;
if a<1, m = min(hs.dim); end
set_zoom(m, hs);
case 'background'
if strcmp(get(h, 'Checked'), 'on')
set(h, 'Checked', 'off');
hs.frame.BackgroundColor = [0 0 0];
set(hs.colorbar, 'EdgeColor', [1 1 1]);
else
set(h, 'Checked', 'on');
hs.frame.BackgroundColor = [1 1 1];
set(hs.colorbar, 'EdgeColor', [0 0 0]);
end
set_cdata(hs);
case 'flipLR'
hs.pref.UserData.rightOnLeft = strcmp(get(h, 'Checked'), 'on');
if hs.pref.UserData.rightOnLeft
set(h, 'Checked', 'off');
set(hs.ax([2 3]), 'XDir', 'normal');
set(hs.ras([3 5]), 'String', 'L');
else
set(h, 'Checked', 'on');
set(hs.ax([2 3]), 'XDir', 'reverse');
set(hs.ras([3 5]), 'String', 'R');
end
case 'layout'
submenu = {'one-row' 'two-row sag on right' 'two-row sag on left'};
layout = find(strcmp(get(h, 'Label'), submenu));