-
Notifications
You must be signed in to change notification settings - Fork 45
/
nii_viewer.m
3393 lines (3165 loc) · 136 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 both the volume and
% 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 vies 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 highlighted image. This
% feature is indicated by the highlighted frame the 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
% 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". 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.
% 160506 phase LUT to map complex img: useful for retinotopy.
% 160620 Use JIDE CheckBoxList; Simplify KeyFcn by not focusing on active items.
% 161003 Add aligned overlay: accept FSL warp file as transformation.
% 161115 Use .mat file for early spm Analyze file.
% 170421 java_dnd() changed as func, ControlDown OS independent by ACTION_LINK.
% 170515 Use area to normalize histogram.
% 171214 Try to convert back to volume in case of CIfTI (need anatomical gii).
% 171228 Surface view for gii (include HCP gii template).
% 180103 Allow inflated surface while mapping to correct location in volume.
% Later update history can be found at github.
%%
if nargin==2 && ischar(fname) && strcmp(fname, 'func_handle')
varargout{1} = str2func(varargin{1});
return;
elseif nargin>1 && 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.R = p.R;
hs.bg.Ri = p.Ri;
hs.bg.hdr = p.hdr0;
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
a = handle(findall(0, 'Type', 'figure', 'Tag', 'nii_viewer'));
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
if isempty(fn), fn = 'ni' * 256.^(1:2)'; end
else
fn = max(cell2mat(get(a, 'Number'))) + 1;
end
end
[siz, axPos, figPos] = plot_pos(dim.*hs.pixdim, pf.layout);
fh = figure(fn);
if nargout, varargout{1} = fh; end
hs.fig = handle(fh); % have to use numeric for uipanel for older matlab
figNam = char(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+[2 66]], '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); %#ok<*JAPIMATHWORKS>
width = h.getPreferredScrollableViewportSize.getWidth;
width = max(60, min(width+20, pos(3)-408)); % 20 pixels for vertical scrollbar
warning('off', 'MATLAB:ui:javacomponent:FunctionToBeRemoved');
[~, hs.scroll] = javacomponent(jScroll, [2 4 width 60], hs.panel); %#ok<*JAVCM>
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 37 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 40 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 = [42 44 22]; posTxt = [40 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
h = hs.files.SelectionBackground; fClr = [h.getRed h.getGreen h.getBlue]/255;
uicontrol(ph, 'Style', 'frame', 'Position', [1 5 412 32], 'ForegroundColor', fClr);
hs.lb = java_spinner([7 10 48 22], [p.lb -inf inf p.lb_step], ph, ...
cb('lb'), '#.##', 'min value (threshold)');
hs.ub = java_spinner([59 10 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 10 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 10 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 10 60 22], 'String', 'smooth', 'BackgroundColor', clr, ...
'Callback', cb('smooth'), 'TooltipString', 'Smooth image in 3D');
hs.interp = uicontrol(ph, 'Style', 'popup', 'Position', [291 10 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 10 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', [2 2 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')));
set(hs.ax(i), 'DataAspectRatio', [1./hs.pixdim(j) 1]);
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)), 'Parent', hs.ax(i), ...
'Units', 'normalized', '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);
hs.colorbar = colorbar(hs.ax(4), 'Color', [1 1 1], 'Location', 'West', ...
'PickableParts', 'none', 'Visible', 'off');
% 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', '');
set([hs.ras hs.cross(:)' hs.xyz], 'PickableParts', 'none');
%% 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', 'Export as movie ...', 'Callback', cb('MP4'));
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'));
hs.overlay(5) = uimenu(h_over, 'Label', 'Manual alignment', 'Callback', ...
@manual_align, 'Enable', 'off');
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(7) = 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');
hs.overlay(6) = uimenu(h_over, 'Label', 'Load LUT for current overlay', ...
'Callback', cb('custom'), 'Enable', 'off');
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'), 'Tag', '1');
uimenu(h, 'Label', 'two-row sag on right', 'Callback', cb('layout'), 'Tag', '2');
uimenu(h, 'Label', 'two-row sag on left', 'Callback', cb('layout'), 'Tag', '3');
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'));
guidata(fh, hs); % store handles and data
%% java_dnd based on dndcontrol at matlabcentral/fileexchange/53511
try % panel has JavaFrame in later matlab
warning('off', 'MATLAB:ui:javaframe:PropertyToBeRemoved');
jFrame = handle(hs.frame.JavaFrame.getGUIDEView, 'CallbackProperties');
catch
warning('off', 'MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
jFrame = fh.JavaFrame.getAxisComponent; %#ok<*JAVFM>
end
if usejava('awt')
try java_dnd(jFrame, cb('drop')); catch me, disp(me.message); end
end
% iconPNG = fullfile(fileparts(mfilename('fullpath')), 'nii_viewer.png');
% fh.JavaFrame.setFigureIcon(javax.swing.ImageIcon(iconPNG)); % windows only
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}) || isstruct(varargin{1})
addOverlay(varargin{1}, fh);
elseif iscell(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 strcmp(cmd, 'smooth')
if val==1 && numel(p.nii.img(:,:,:,1))<2
set(h, 'Value', 0); return;
end
elseif strcmp(cmd, 'lut')
err = false;
if val == 11 % error check for vector lines
if p.lut~=11, hs.lut.UserData = p.lut; end % remember old lut
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 requries 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
htP = hs.panel.Position(4); % get old height in pixels
posF = getpixelposition(fh); % asked size by user
hs.panel.Position(2:3) = posF([4 3]) - [htP 2]; % control panel
hs.frame.Position(3:4) = posF(3:4) - [2 htP]; % 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.hdr0;
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'
getVersion = dicm2nii('', 'getVersion', 'func_handle');
str = sprintf(['nii_viewer.m by Xiangrui Li\n\n' ...
'Last updated on %s\n\n', ...
'Feedback to: [email protected]\n'], getVersion());
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'
layout = str2double(get(h, 'Tag'));
parent = get(h, 'Parent');
if get(parent, 'UserData') == layout, return; end
set(parent, 'UserData', layout);
htP = hs.panel.Position(4);
[siz, axPos, figPos] = plot_pos(hs.dim.*hs.pixdim, layout);
hs.fig.Position = [figPos siz+[2 htP+2]];
hs.frame.Position(3:4) = siz;
hs.panel.Position(2:3) = [hs.fig.Position(4)-htP siz(1)+2];
for i = 1:4, set(hs.ax(i), 'Position', axPos(i,:)); end
case 'keyHelp'
str = sprintf([ ...
'Key press available when focus is not in a number dialer:\n\n' ...
'Left or Right arrow key: Move crosshair left or right.\n\n' ...
'Up or Down arrow key: Move crosshair superior or inferior.\n\n' ...
'[ or ] key: Move crosshair posterior or anterior.\n\n' ...
'< or > key: Decrease or increase volume number.\n\n' ...
'Ctrl + or - key: Zoom in or out by 10%% around crosshair.\n\n' ...
'A: Toggle on/off crosshair.\n\n' ...
'C: Crosshair to view center.\n\n' ...
'Space: Toggle on/off selected image.\n\n' ...
'F1: Show help text.\n']);
helpdlg(str, 'Key Shortcut');
case 'center' % image center
p = get_para(hs);
dim = p.nii.hdr.dim(2:4);
c = round(hs.bg.Ri * (p.R * [dim/2-1 1]')) + 1;
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'viewCenter'
c(1) = mean(get(hs.ax(2), 'XLim'));
c(2) = mean(get(hs.ax(1), 'XLim'));
c(3) = mean(get(hs.ax(1), 'YLim'));
c = round(c-0.5);
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'toXYZ'
c0 = cell2mat(get(hs.ijk, 'Value'));
c0 = hs.bg.R * [c0-1; 1];
c0 = sprintf('%g %g %g', round(c0(1:3)));
str = 'X Y Z coordinates in mm';
while 1
a = inputdlg(str, 'Crosshair to xyz', 1, {c0});
if isempty(a), return; end
c = sscanf(a{1}, '%g %g %g');
if numel(c) == 3, break; end
end
c = round(hs.bg.Ri * [c(:); 1]) + 1;
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'toValue'
def = getappdata(h, 'Value');
if isempty(def), def = 1; end
def = num2str(def);
str = 'Input the voxel value';
while 1
a = inputdlg(str, 'Crosshair to a value', 1, {def});
if isempty(a), return; end
val = sscanf(a{1}, '%g');
if ~isnan(val), break; end
end
setappdata(h, 'Value', val);
jf = hs.files.getSelectedIndex+1;
p = get_para(hs, jf);
img = p.nii.img(:,:,:, hs.volume.getValue);
c = find(img(:)==val, 1);
if isempty(c)
nam = strtok(hs.files.getModel.get(jf-1), '(');
errordlg(sprintf('No value of %g found in %s', val, nam));
return;
end
dim = size(img); dim(numel(dim)+1:3) = 1;
[c(1), c(2), c(3)] = ind2sub(dim, c); % ijk+1
c = round(hs.bg.Ri * (p.R * [c(:)-1; 1])) + 1;
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'cog' % crosshair to img COG
p = get_para(hs);
img = p.nii.img(:,:,:, hs.volume.getValue);
c = img_cog(img);
if any(isnan(c)), errordlg('No valid COG found'); return; end
c = round(hs.bg.Ri * (p.R * [c-1; 1])) + 1;
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'maximum' % crosshair to img max
p = get_para(hs);
img = p.nii.img(:,:,:,hs.volume.getValue);
if sum(img(:)~=0) < 1
errordlg('All value are the same. No maximum!');
return;
end
img = smooth23(img, 'gaussian', 5);
img(isnan(img)) = 0;
img = abs(img);
[~, I] = max(img(:));
dim = size(img); dim(end+1:3) = 1;
c = zeros(3, 1);
[c(1), c(2), c(3)] = ind2sub(dim, I);
c = round(hs.bg.Ri * (p.R * [c-1; 1])) + 1;
for i = 1:3, hs.ijk(i).setValue(c(i)); end
case 'custom' % add custom lut
p = get_para(hs);
pName = fileparts(p.nii.hdr.file_name);
[fname, pName] = uigetfile([pName '/*.lut'], 'Select LUT file for current overlay');
if ~ischar(fname), return; end
fid = fopen(fullfile(pName, fname));
p.map = fread(fid, '*uint8');
fclose(fid);
if mod(numel(p.map),3)>0 || sum(p.map<8)<3 % guess text file
try, p.map = str2num(char(p.map'));
catch, errordlg('Unrecognized LUT file'); return;
end
if max(p.map(:)>1), p.map = single(p.map) / 255; end
else
p.map = reshape(p.map, [], 3);
p.map = single(p.map) / 255;
end
if isequal(p.nii.img, round(p.nii.img))
try, p.map = p.map(1:max(p.nii.img(:))+1, :); end
end
p.lut = numel(hs.lutStr);
p.hsI(1).UserData = p;
set(hs.lut, 'Value', p.lut, 'Enable', 'off');
set_cdata(hs);
set_colorbar(hs);
case 'tc' % time course or std
jf = hs.files.getSelectedIndex+1;
p = get_para(hs, jf);