From bd82813de618525c3437681c1dec9d6157ee5101 Mon Sep 17 00:00:00 2001 From: locastre Date: Mon, 4 Mar 2024 16:31:29 -0500 Subject: [PATCH 01/10] include IVIM code from Dr. Amita Shukla-Dave Lab at MSKCC --- .../MRI-QAMPER_IVIM/IVIM_standard_bcin.m | 113 + .../MRI-QAMPER_IVIM/README.asv | 33 + .../MRI-QAMPER_IVIM/README.md | 30 + .../MRI-QAMPER_IVIM/avgRepeatBvalVols.m | 16 + .../MRI-QAMPER_IVIM/buildBatchFromCSV.m | 107 + .../MRI-QAMPER_IVIM/buildBatchStruct.m | 336 ++ .../MRI-QAMPER_IVIM/bvalOrderFix.m | 15 + .../MRI-QAMPER_IVIM/dataPrepDWI.m | 30 + .../MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv | 15 + .../MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m | 24 + .../MRI-QAMPER_IVIM/getTestData.m | 8 + .../MRI-QAMPER_IVIM/inpaint_nans.m | 1 + .../MRI-QAMPER_IVIM/ivim_modeling_noise.m | 17 + .../qa_fitting/outputHNMPAResults.m | 57 + .../MRI-QAMPER_IVIM/qa_fitting/qualityFit.m | 31 + .../qa_fitting/returnFeatures.log | 0 .../qa_fitting/returnFeatures.m | 10 + .../qa_fitting/write_param_txt.m | 24 + .../MRI-QAMPER_IVIM/runQAMPERBatch.m | 181 + .../MRI-QAMPER_IVIM/run_QAMPER_IVIM.m | 109 + .../MRI-QAMPER_IVIM/utils/Dir2Arr.m | 60 + .../MRI-QAMPER_IVIM/utils/arr2map2.m | 55 + .../MRI-QAMPER_IVIM/utils/deb.m | 3 + .../MRI-QAMPER_IVIM/utils/flipCheck.m | 13 + .../MRI-QAMPER_IVIM/utils/map2arr.m | 28 + .../utils/nifti_toolbox/FAQ.pdf | Bin 0 -> 206445 bytes .../utils/nifti_toolbox/NIfTI_tools.pdf | Bin 0 -> 61950 bytes .../utils/nifti_toolbox/UseANALYZE.pdf | Bin 0 -> 91343 bytes .../utils/nifti_toolbox/affine.m | 554 ++ .../utils/nifti_toolbox/bipolar.m | 94 + .../utils/nifti_toolbox/bresenham_line3d.m | 189 + .../utils/nifti_toolbox/clip_nii.m | 115 + .../utils/nifti_toolbox/collapse_nii_scan.m | 260 + .../utils/nifti_toolbox/collapse_nii_scan2.m | 260 + .../utils/nifti_toolbox/examples.txt | 130 + .../utils/nifti_toolbox/expand_nii_scan.m | 48 + .../utils/nifti_toolbox/extra_nii_hdr.m | 255 + .../utils/nifti_toolbox/flip_lr.m | 84 + .../utils/nifti_toolbox/get_nii_frame.m | 164 + .../utils/nifti_toolbox/license.txt | 24 + .../utils/nifti_toolbox/load_nii.m | 198 + .../utils/nifti_toolbox/load_nii_ext.m | 207 + .../utils/nifti_toolbox/load_nii_hdr.m | 280 + .../utils/nifti_toolbox/load_nii_img.m | 392 ++ .../nifti_toolbox/load_untouch0_nii_hdr.m | 200 + .../nifti_toolbox/load_untouch_header_only.m | 187 + .../utils/nifti_toolbox/load_untouch_nii.m | 191 + .../nifti_toolbox/load_untouch_nii_hdr.m | 217 + .../nifti_toolbox/load_untouch_nii_img.m | 468 ++ .../utils/nifti_toolbox/make_ana.m | 210 + .../utils/nifti_toolbox/make_nii.m | 256 + .../utils/nifti_toolbox/mat_into_hdr.m | 83 + .../utils/nifti_toolbox/pad_nii.m | 142 + .../utils/nifti_toolbox/reslice_nii.m | 321 ++ .../utils/nifti_toolbox/rri_file_menu.m | 179 + .../utils/nifti_toolbox/rri_orient.m | 106 + .../utils/nifti_toolbox/rri_orient_ui.m | 251 + .../utils/nifti_toolbox/rri_select_file.m | 636 +++ .../utils/nifti_toolbox/rri_xhair.m | 92 + .../utils/nifti_toolbox/rri_zoom_menu.m | 33 + .../utils/nifti_toolbox/save_nii.m | 286 + .../utils/nifti_toolbox/save_nii_ext.m | 38 + .../utils/nifti_toolbox/save_nii_hdr.m | 227 + .../nifti_toolbox/save_untouch0_nii_hdr.m | 219 + .../nifti_toolbox/save_untouch_header_only.m | 71 + .../utils/nifti_toolbox/save_untouch_nii.m | 232 + .../nifti_toolbox/save_untouch_nii_hdr.m | 207 + .../utils/nifti_toolbox/save_untouch_slice.m | 580 ++ .../utils/nifti_toolbox/unxform_nii.m | 40 + .../utils/nifti_toolbox/verify_nii_ext.m | 45 + .../utils/nifti_toolbox/view_nii.m | 4873 +++++++++++++++++ .../utils/nifti_toolbox/view_nii_menu.m | 480 ++ .../utils/nifti_toolbox/xform_nii.m | 521 ++ .../MRI-QAMPER_IVIM/utils/nii_load.m | 45 + .../MRI-QAMPER_IVIM/utils/nii_save.m | 14 + 75 files changed, 16020 insertions(+) create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m create mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m new file mode 100755 index 00000000..09828228 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m @@ -0,0 +1,113 @@ +function [f_arr, D_arr, Dx_arr, s0_arr, fitted_dwi_arr, RSS, rms_val, chi, AIC, BIC, R_sq] = IVIM_standard_bcin(dwi_arr,bval_arr,sigmadwi,LB0,UB0,x0in,parallelFlag,previewMode,previewAxes) + + + +if size(bval_arr,2) < size(bval_arr,1) + bval_arr = bval_arr'; +end + +numVoxels = size(dwi_arr,2); +numBvals = length(bval_arr); + +f_lb = 0; +% f_ub = 0.5; +f_ub = 1.0; +% f_ub = 0.75; +% D_lb = 0; +D_lb = 1e-6; +D_ub = 0.003; +% D_ub = 4e-3; +% Dx_lb = 0; +Dx_lb = 1e-6; +Dx_ub = 5e-2; +% Dx_ub = 0.5; +% Dx_ub = 300e-3; +s0_lb = 0; + +% %Yousef bounds Jul 16 2018 +d10 = 1e-3; +d20 = 1e-2; +f0 = 0.2; + +% p0(i,:) = [ smax(i) d10 d20 f0]; +% lb(i,:) = [0.5*smax(i) 0.25*d10 0.25*d20 0.0]; +% ub(i,:) = [2.0*smax(i) 4.0*d10 4.0*d20 1.0]; + +if nargin < 6 + x0in = [f0 d10 d20 1]; % entry for s (4th) is multiplicative factor, not absolute + if nargin < 4 + % LB = [f_lb D_lb Dx_lb s0_lb]; + % UB = [f_ub D_ub Dx_ub 0]; + % entry for s (4th) is multiplicative factor, not absolute + LB0 = [0 0.25*d10 0.25*d20 0.5]; + UB0 = [1 4*d10 4*d20 2]; + end +end + +optimization_iterations = 4; + +options = optimset('MaxFunEvals',400,'MaxIter',200, ... + 'TolFun',1e-6,'TolX',1e-6,'Display','off'); + +axisLabels = {'b-value (s/mm^2)','signal (a.u.)'}; + +tic +for i = 1:numVoxels + if rem(i,1000) == 0 + toc + disp(['Voxel ' num2str(i) ' of ' num2str(numVoxels)]); + tic + end + s = dwi_arr(:,i); + + LB = LB0; + UB = UB0; + x0 = x0in; + + LB(end) = LB0(end)*max(s); + UB(end) = UB0(end)*max(s); + x0(end) = x0in(end)*max(s); + % disp(['Voxel ' num2str(i) ' of ' num2str(numVoxels)]); + if ~parallelFlag + for j = 1:optimization_iterations + % x0 = (LB + UB) / 2; %+ (UB - LB) * (rand - 0.5); + [x_fit(j,:),resnorm(j),~,~,~] = lsqcurvefit(@(x,b)ivim_modeling_noise(x,b,sigmadwi), ... + x0, bval_arr', s, LB, UB, options); + end + else + parfor j = 1:optimization_iterations + % x0 = (LB + UB) / 2; %+ (UB - LB) * (rand - 0.5); + [x_fit(j,:),resnorm(j),~,~,~] = lsqcurvefit(@(x,b)ivim_modeling_noise(x,b,sigmadwi), ... + x0, bval_arr', s, LB, UB, options); + end + end + [~,best_fit_idx] = min(resnorm); + p = x_fit(best_fit_idx,:); + + fitted_dwi_arr(:,i) = ivim_modeling_noise(p,bval_arr',sigmadwi); + f_arr(i) = p(1); + D_arr(i) = p(2)*(1e3); + if p(3) > 1e-5 + Dx_arr(i) = p(3)*(1e3); + else + Dx_arr(i) = 1e-5; + end + s0_arr(i) = p(4); + + if previewMode == 1 || previewMode == 100 + axisLabels{3} = ['IVIM Fit (Voxel # ' num2str(i) '/' num2str(numVoxels) ')']; + if previewMode == 100 && i < 101 + updatePreviewAxes(previewAxes,bval_arr',s,fitted_dwi_arr(:,i),axisLabels); + elseif previewMode == 1 + updatePreviewAxes(previewAxes,bval_arr',s,fitted_dwi_arr(:,i),axisLabels); + end + end + +end + +% Quantification of Fitting Quality +Nb = numel(bval_arr); +Np = numel(p); +observed_arr = dwi_arr; +predicted_arr = fitted_dwi_arr; +[RSS,rms_val,chi,AIC,BIC,R_sq] = qualityFit(Nb,Np,observed_arr,predicted_arr); diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv new file mode 100755 index 00000000..7fe62189 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv @@ -0,0 +1,33 @@ +# MRI-QAMPER_IVIM + +This is the codebase for intravoxel incoherent motion (IVIM) code from the MRI-QAMPER MATLAB package, developed by Dr. Shukla-Dave's lab at Memorial Sloan Kettering Cancer Center + +Authors: Eve LoCastro (locastre@mskcc.org), Dr. Ramesh Paudyal (paudyalr@mskcc.org), Dr. Amita Shukla-Dave (davea@mskcc.org) +Institution: Memorial Sloan Kettering Cancer Center +Department: Medical Physics +Address: 321 E 61st St, New York, NY 10022 + +The codebase and subdirectories should be added to the MATLAB path. An example usage script is provided in `demo_QAMPER_IVIM.m`. + +``` +% This is an example usage script for MSK Medical Physics Dave Lab QAMPER IVIM +% Please replace the variable names below with path values for +% qamper_path: path to MRI-QAMPER_IVIM folder +% img_nii: multi-b value DWI (4-D NIfTI) +% bval_file: b-value information (txt file) +% roi_nii: single-volume mask ROI image (NIfTI) + + + +qamper_path = 'path:\to\MRI-QAMPER_IVIM'; +addpath(genpath(qamper_path)); + +img_nii = 'dwi.nii'; +bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); +roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); + +save_folder = fullfile(qamper_path,'test_data'); + +batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); + +``` \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md new file mode 100755 index 00000000..b24e0a7f --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md @@ -0,0 +1,30 @@ +# MRI-QAMPER_IVIM + +This is the codebase for intravoxel incoherent motion (IVIM) code from the MRI-QAMPER MATLAB package, developed by Dr. Shukla-Dave's lab at Memorial Sloan Kettering Cancer Center. + +__Authors:__ Eve LoCastro (locastre@mskcc.org), Dr. Ramesh Paudyal (paudyalr@mskcc.org), Dr. Amita Shukla-Dave (davea@mskcc.org)
+__Institution:__ Memorial Sloan Kettering Cancer Center
+__Department:__ Medical Physics
+__Address:__ 321 E 61st St, New York, NY 10022 + +The codebase and subdirectories should be added to the MATLAB path. An example usage script is provided in `demo_QAMPER_IVIM.m`. + +``` +% This is an example usage script for MSK Medical Physics Dave Lab QAMPER IVIM +% Please replace the variable names below with path values for +% qamper_path: path to MRI-QAMPER_IVIM folder +% img_nii: multi-b value DWI (4-D NIfTI) +% bval_file: b-value information (txt file) +% roi_nii: single-volume mask ROI image (NIfTI) + +qamper_path = 'path:\to\MRI-QAMPER_IVIM'; +addpath(genpath(qamper_path)); + +img_nii = 'dwi.nii'; +bval_file = 'dwi.bval'; +roi_nii = 'roi.nii'; + +save_folder = fullfile(qamper_path,'test_data'); + +batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); +``` \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m new file mode 100755 index 00000000..75296b3e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m @@ -0,0 +1,16 @@ +function [uniq_bval_arr, dwi_img_new] = avgRepeatBvalVols(bval_arr,dwi_img) + +disp('Averaging multiple bval vols to increase signal') + +[uniq_bval_arr,IA,~] = unique(bval_arr); + +dwi_img_new = []; + +for i = 1:numel(uniq_bval_arr) + if i < numel(uniq_bval_arr) + dwi_avg_vol = mean(dwi_img(:,:,:,IA(i):IA(i+1)-1),4); + else + dwi_avg_vol = mean(dwi_img(:,:,:,IA(i):end),4); + end + dwi_img_new = cat(4,dwi_img_new, dwi_avg_vol); +end \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m new file mode 100755 index 00000000..bf4541d2 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m @@ -0,0 +1,107 @@ +function batchStruct = buildBatchFromCSV(img_nii,roi_nii,qamper_mode,optionS) + +batchStruct = []; +batchStruct.batch_id = num2str(now); + +if strcmp(qamper_mode,'DWI') + if isfield(optionS,'DWIstruct') + DWIstruct = optionS.DWIstruct; + end + + DWIstruct.runDWI = 1; + [droot,fbase,ext] = fileparts(img_nii); + batchStruct.currentNiiPath = fullfile(droot,'dwi_maps'); + + + if strcmp(ext,'.gz') + [~,fbase,~] = fileparts(fbase); + end + + bval_mat = fullfile(droot,[fbase '.mat']); + + T = load(bval_mat); + + DWIstruct.bval_arr = T.bval_arr; + + img = nii_load(img_nii,1); + vol_num = size(img.img,4); + + DWIstruct.files{1} = img_nii; DWIstruct.vols = vol_num; + DWIstruct.files{2} = roi_nii; + + if ~isfield(DWIstruct,'ADC') + DWIstruct.ADC = 0; + end + if ~isfield(DWIstruct,'IVIM') + DWIstruct.IVIM = 0; + end + if ~isfield(DWIstruct,'NGIVIM') + DWIstruct.NGIVIM = 0; + end + if ~isfield(DWIstruct,'Kurtosis') + DWIstruct.Kurtosis = 0; + end + + if ~isfield(DWIstruct,'roiType') + DWIstruct.roiType = 'node'; + end + + if ~isfield(DWIstruct,'avgRepeatBvals') + DWIstruct.avgRepeatBvals = 1; + end + + if ~isfield(DWIstruct,'channels') + DWIstruct.channels = 16; % + end + + if ~isfield(DWIstruct, 'avgChannels') + DWIstruct.avgChannels = 2; % + end + + if DWIstruct.IVIM + if ~isfield(DWIstruct,'LB_IVIM') + IVIMBoundsTable = [{0} {0} {0} {0}; {0.5} {0.005} {0.5} {1.0}; {0.05} {0.0005} {0.01} {0.5}]; + + DWIstruct.LB_IVIM = cell2mat(IVIMBoundsTable(1,:)); + DWIstruct.UB_IVIM = cell2mat(IVIMBoundsTable(2,:)); + DWIstruct.x0_IVIM = cell2mat(IVIMBoundsTable(3,:)); + end + end + if DWIstruct.NGIVIM + if ~isfield(DWIstruct,'LB_NG') + NGIVIMDefaultBoundsTable = [{0} {0} {0} {0} {0}; {0.5} {0.005} {0.1} {1.0} {2.0}; {0.05} {0.0005} {0.01} {1.0} {0.5}]; + + DWIstruct.LB_NG = cell2mat(NGIVIMDefaultBoundsTable(1,:)); + DWIstruct.UB_NG = cell2mat(NGIVIMDefaultBoundsTable(2,:)); + DWIstruct.x0_NG = cell2mat(NGIVIMDefaultBoundsTable(3,:)); + end + end + + if ~isfield(DWIstruct,'Parallel') + DWIstruct.Parallel = 1; + end + + DWIstruct.bval_tf = ones(size(DWIstruct.bval_arr)); +% +% DWIstruct.previewAxes = handles.fittingAxes; +% + DWIstruct.nanopt = 1; + + if ~isfield(DWIstruct,'kernelsize') + DWIstruct.kernelsize = 1; + end + if ~isfield(DWIstruct,'smoothopt') + DWIstruct.smoothopt = 1; + end + + DWIstruct.previewMode = 0; + DWIstruct.previewAxes = 0; + +else + DWIstruct.runDWI = 0; +end + +batchStruct.DWIstruct = DWIstruct; +batchStruct.T1struct.runT1 = 0; +batchStruct.DCEstruct.runDCE = 0; +batchStruct.T2struct.runT2 = 0; \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m new file mode 100755 index 00000000..2a4846d0 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m @@ -0,0 +1,336 @@ + +function batchStruct = buildBatchStruct(handles) + +batchStruct.batch_id = handles.batch_id; + + +batchStruct.currentNiiPath = handles.outputDirectoryText.String; + +qamperMode = handles.qamperMode; + +if strcmp(qamperMode,'DWI') + DWIstruct.runDWI = 1; +% +% DWIstruct.ADC = handles.ADCCheckBox.Value; +% DWIstruct.IVIM = handles.IVIMCheckBox.Value; +% DWIstruct.NGIVIM = handles.NGIVIMCheckBox.Value; +% DWIstruct.Kurtosis = handles.KurtosisCheckBox.Value; +% DWIstruct.Parallel = handles.ParallelButton_DWI.Value; + + fn = handles.fn; +% +% DWIstruct.files{1} = handles.inputDataText.String; +% DWIstruct.files{2} = handles.inputROIText.String; +% + for i = 1:numel(fn) + inputfile{i} = fn(i).filename; vol_num{i} = fn(i).filevolnum; + DWIstruct.bval_arr(i) = fn(i).bval_arr; + end +% dwinputfilename = unique(inputfile); +% if numel(dwinputfilename) == 1 +% % = string(dwinputfilename); +% dwinputfilename = char(dwinputfilename); +% end + DWIstruct.files{1} = inputfile; DWIstruct.vols = vol_num; + DWIstruct.files{2} = handles.inputROIText.String; + +% [currentNiiPath,~,~] = fileparts(DWIstruct.files{1}); +% batchStruct.currentNiiPath = currentNiiPath; + + DWIstruct.ADC = handles.ADCCheckBox.Value; + DWIstruct.IVIM = handles.IVIMCheckBox.Value; + DWIstruct.NGIVIM = handles.NGIVIMCheckBox.Value; + DWIstruct.Kurtosis = handles.DKICheckBox.Value; + + DWIstruct.roiType = handles.ROITypeDropDown.String{handles.ROITypeDropDown.Value}; + + DWIstruct.avgRepeatBvals = handles.avgRepeatBvalsCheckBox.Value; + + % DWIstruct.channels = handles.channels; + + if isfield(handles, 'channels') + DWIstruct.channels = handles.channels; + else + DWIstruct.channels = 24; % + end + + % DWIstruct.avgChannels = handles.avgChannels; + + if isfield(handles, 'avgChannels') + DWIstruct.avgChannels = handles.avgChannels; + else + DWIstruct.avgChannels = 24; % + end + +% +% presetValue = handles.presetPopupMenu.Value; +% if presetValue == 1 +% presetValue = 2; +% end +% +% DWIstruct.preset = handles.presetPopupMenu.String{presetValue}; + + if DWIstruct.IVIM + DWIstruct.LB_IVIM = cell2mat(handles.IVIMUITable.Data(1,:)); + DWIstruct.UB_IVIM = cell2mat(handles.IVIMUITable.Data(2,:)); + DWIstruct.x0_IVIM = cell2mat(handles.IVIMUITable.Data(3,:)); + end + if DWIstruct.NGIVIM + DWIstruct.LB_NG = cell2mat(handles.NGIVIMUITable.Data(1,:)); + DWIstruct.UB_NG = cell2mat(handles.NGIVIMUITable.Data(2,:)); + DWIstruct.x0_NG = cell2mat(handles.NGIVIMUITable.Data(3,:)); + end + + if ~handles.parallelCheckBox.Value + DWIstruct.Parallel = 0; + + else + DWIstruct.Parallel = 1; + end + + previewMode = handles.fitPreviewButtonGroup.SelectedObject.Tag; + + switch previewMode + case 'previewNoneRadioButton' + DWIstruct.previewMode = 0; + case 'preview100RadioButton' + DWIstruct.previewMode = 100; + case 'previewFullRadioButton' + DWIstruct.previewMode = 1; + end + +% DWIstruct.bval_arr = handles.bval_arr; + DWIstruct.bval_tf = ones(size(DWIstruct.bval_arr)); + + DWIstruct.previewAxes = handles.fittingAxes; + + DWIstruct.nanopt = 1; + + if str2num(handles.smoothKernelEdit.String) > 0 + DWIstruct.smoothopt = 1; + DWIstruct.kernelsize = str2num(handles.smoothKernelEdit.String); + else + DWIstruct.smoothopt = 0; + end + +% if isempty(DWIstruct.bval_arr) +% dwiniifile = DWIstruct.files{1}; +% [d,f,~] = fileparts(dwiniifile); +% load([d filesep f(1:end-4) '.mat']); +% DWIstruct.bval_arr = bval_arr; +% if exist(bval_tf,'var') +% DWIstruct.bval_tf = bval_tf; +% else +% DWIstruct.bval_tf = ones(size(bval_arr)); +% end +% end + +else + DWIstruct.runDWI = 0; +end + +if strcmp(qamperMode,'T1') + T1struct.runT1 = 1; + T1struct.AIFonly = 0; %handles.AIFcalcOnlyCheckBox.Value; + + fn = handles.fn; + T1struct.FA = zeros(numel(fn),1); + T1struct.TR = zeros(numel(fn),1); + + for i = 1:numel(fn) + T1struct.files{i} = fn(i).filename; + T1struct.FA(i) = fn(i).FA; + T1struct.TR(i) = fn(i).TR; + end + + T1struct.ROI = handles.inputROIText.String; + + if strcmp(handles.fitMethodButtonGroup.SelectedObject.String,'Non-Linear Fitting') && numel(fn) > 2 + T1struct.Nonlinear = 1; + T1struct.LB = cell2mat(handles.T1NonLinearTable.Data(1,:)); + T1struct.UB = cell2mat(handles.T1NonLinearTable.Data(2,:)); + else + T1struct.Nonlinear = 0; + end + + switch handles.fitPreviewButtonGroup.SelectedObject.String + case 'No Preview' + T1struct.previewMode = 0; + case 'First 200 Points Preview' + T1struct.previewMode = 100; + case 'Full Preview' + T1struct.previewMode = 1; + end + + T1struct.Parallel = handles.parallelCheckBox.Value; + T1struct.previewAxes = handles.fittingAxes; + +else + T1struct.runT1 = 0; +end + +if strcmp(qamperMode,'T2') + T2struct.runT2 = 1; + + fn = handles.fn; + T2struct.TE = zeros(numel(fn),1); + T2struct.TR = zeros(numel(fn),1); + + for i = 1:numel(fn) + T2struct.files{i} = fn(i).filename; + T2struct.TE(i) = fn(i).TE; + T2struct.TR(i) = fn(i).TR; + end + + T2struct.ROI = handles.inputROIText.String; + +% if strcmp(handles.fitMethodButtonGroup.SelectedObject.String,'Non-Linear Fitting') && numel(fn) > 2 +% T2struct.Nonlinear = 1; +% else +% T2struct.Nonlinear = 0; +% end + + switch handles.fitPreviewButtonGroup.SelectedObject.String + case 'No Preview' + T2struct.previewMode = 0; + case 'First 200 Points Preview' + T2struct.previewMode = 100; + case 'Full Preview' + T2struct.previewMode = 1; + end + + T2struct.Parallel = handles.parallelCheckBox.Value; + T2struct.previewAxes = handles.fittingAxes; + +else + T2struct.runT2 = 0; +end + +if strcmp(qamperMode,'DCE') + DCEstruct.runDCE = 1; + + fn = handles.fn; + + DCEstruct.AIF = handles.aifPopupMenu.String{handles.aifPopupMenu.Value}; + DCEstruct.Cpmat = handles.Cpmat; + DCEstruct.Cp = handles.Cp; + + DCEstruct.SM2 = handles.SM2CheckBox.Value; + DCEstruct.SM3 = handles.SM3CheckBox.Value; + DCEstruct.SSM = handles.SSMCheckBox.Value; + DCEstruct.Patlak = handles.PatlakCheckBox.Value; + DCEstruct.CTUM = handles.CTUMCheckBox.Value; + DCEstruct.CXM = handles.CXMCheckBox.Value; + + DCEstruct.roiType = handles.ROITypeDropDown.String{handles.ROITypeDropDown.Value}; + +% dcemodelidx = handles.modelPopupMenu.Value; +% dcemodel = handles.modelPopupMenu.String{dcemodelidx}; +% if dcemodelidx == 5 || dcemodelidx == 6 +% dcemodel = dcemodel(2:end); +% end + if DCEstruct.SM2 == 1 + eval('DCEstruct.SM2_LB = cell2mat(handles.SM2UITable.Data(1,:));'); + eval('DCEstruct.SM2_UB = cell2mat(handles.SM2UITable.Data(2,:));'); + end + if DCEstruct.SM3 == 1 + eval('DCEstruct.SM3_LB = cell2mat(handles.SM3UITable.Data(1,:));'); + eval('DCEstruct.SM3_UB = cell2mat(handles.SM3UITable.Data(2,:));'); + end + if DCEstruct.SSM == 1 + eval('DCEstruct.SSM_LB = cell2mat(handles.SSMUITable.Data(1,:));'); + eval('DCEstruct.SSM_UB = cell2mat(handles.SSMUITable.Data(2,:));'); + end + if DCEstruct.CTUM == 1 + eval('DCEstruct.CTUM_LB = cell2mat(handles.CTUMUITable.Data(1,:));'); + eval('DCEstruct.CTUM_UB = cell2mat(handles.CTUMUITable.Data(2,:));'); + end + if DCEstruct.CXM == 1 + eval('DCEstruct.CXM_LB = cell2mat(handles.CXMUITable.Data(1,:));'); + eval('DCEstruct.CXM_UB = cell2mat(handles.CXMUITable.Data(2,:));'); + end +% if ~strcmp(dcemodel(1:3),'AIF') +% eval(['DCEstruct.' dcemodel ' = 1']); +% if ~strcmp(dcemodel(1:3),'Pat') +% eval(['DCEstruct.LB = cell2mat(handles. ' dcemodel 'UITable.Data(1,:));']); +% eval(['DCEstruct.UB = cell2mat(handles. ' dcemodel 'UITable.Data(2,:));']); +% end +% end + if handles.numItersPopupMenu.Value == 1 + DCEstruct.numIters = 4; + else + DCEstruct.numIters = str2num(handles.numItersPopupMenu.String{handles.numItersPopupMenu.Value}); + end + +% DCEstruct.T10 = handles.T10TextEdit.String; + DCEstruct.T1 = ''; + DCEstruct.r1 = str2num(handles.RelaxivityTextEdit.String); + if handles.constantT10CheckBox.Value + DCEstruct.T1 = 'constant'; + DCEstruct.T10 = str2num(handles.T10TextEdit.String); + else + DCEstruct.T10file = handles.T10DataText.String; + DCEstruct.m0file = handles.m0DataText.String; + end + +% DCEstruct.Cpmat = handles.Cpmat; + +% if isempty(DCEstruct.acqDuration) + DCEstruct.acqDuration = fn(end).t; +% end +% +% if str2num(handles.smoothKernelEdit.String) > 0 +% DCEstruct.smoothopt = 1; +% DCEstruct.kernelsize = str2num(handles.smoothKernelEdit.String); +% else + DCEstruct.smoothopt = 0; +% end + + DCEstruct.Parallel = handles.parallelCheckBox.Value; + + % DCEstruct.files{1} = handles.inputDataText.String; + DCEstruct.t = zeros(1,numel(fn)); + DCEstruct.dcenii = fn(1).nii; + for i = 1:numel(fn) + inputfile{i} = fn(i).filename; + DCEstruct.t(i) = fn(i).t; + end + dceinputfilename = unique(inputfile); + if numel(dceinputfilename) == 1 + dceinputfilename = char(dceinputfilename); + end + DCEstruct.files{1} = dceinputfilename; + DCEstruct.files{2} = handles.inputROIText.String; + + DCEstruct.FA = fn(end).FA; + DCEstruct.TR = fn(end).TR; + +% DCEstruct.t = handles.t; + + previewMode = handles.fitPreviewButtonGroup.SelectedObject.Tag; + + switch previewMode + case 'previewNoneRadioButton' + DCEstruct.previewMode = 0; + case 'preview100RadioButton' + DCEstruct.previewMode = 100; + otherwise + DCEstruct.previewMode = 1; + end + + DCEstruct.previewAxes = handles.fittingAxes; + DCEstruct.fn = fn; +else + DCEstruct.runDCE = 0; +end + +if strcmp(qamperMode,'T2') + T2struct.runT2 = 1; +else + T2struct.runT2 = 0; +end + +batchStruct.DWIstruct = DWIstruct; +batchStruct.T1struct = T1struct; +batchStruct.DCEstruct = DCEstruct; +batchStruct.T2struct = T2struct; diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m new file mode 100755 index 00000000..eca6bbdf --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m @@ -0,0 +1,15 @@ +function [bval_arr_new, dwi_img_new] = bvalOrderFix(bval_arr,dwi_img) + +disp(['Reordering B0 to start of imaging sequence']) + +if size(bval_arr,1) > size(bval_arr,2) + bval_arr = bval_arr'; +end + +% bval_arr_new = [bval_arr(end) bval_arr(1:end-1)]; +[bval_arr_new,idx] = sort(bval_arr); + +% dwi_img_new = cat(4,dwi_img(:,:,:,end),dwi_img(:,:,:,1:end-1)); +dwi_img_new = dwi_img(:,:,:,idx); + +disp(bval_arr_new); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m new file mode 100755 index 00000000..260566b2 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m @@ -0,0 +1,30 @@ +function [dwi_arr, bval_arr, sigmadwi, SNR, noise] = dataPrepDWI(dwi_img,bval_arr,ROI,channels,N_avgs) + +bgMask = zeros(size(dwi_img,1),size(dwi_img,2)); +bgMask(10:30,10:30) = 1; +% bgidx = sub2ind(size(bgMask),10:30,10:30); +bgidx = find(bgMask); + +maskidx = find(ROI); + +dwi_arr = map2arr(dwi_img,ROI); +dwi_arr(:,end+1) = mean(dwi_arr,2); + +noise_square = bgMask.*dwi_img(:,:,1,1); +noise_arr = noise_square(bgidx);noise_arr; + +%n = 8; % need to update with new protocol +%navg = 4; +n = channels; +navg = N_avgs; + +% if data were converted to nifti before conv_panel started to extract +% channels and N_avgs, then those numbers default to this +if isempty(n) + n = 16; + navg = 2; +end + +sigmadwi = sqrt(navg*norm(noise_arr(:))^2/length(noise_arr(:)))/sqrt(2*n); +noise = mean(noise_arr); +SNR = mean(dwi_arr(1,:))/sigmadwi; \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv new file mode 100755 index 00000000..39c751c6 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv @@ -0,0 +1,15 @@ +% Example usage script for MSK Medical Physics Dave Lab QAMPER IVIM +% Please replace the variable names below with path values for +% img_nii: multi-b value DWI (4-D NIfTI) +% bval_file: b-value information (txt file) +% roi_nii: single-volume mask ROI +qamper_path = 'path:\to\MRI-QAMPER_IVIM'; +addpath(genpath(qamper_path)); + +img_nii = 'dwi.nii'; +bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); +roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); + +save_folder = fullfile(qamper_path,'test_data'); + +batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m new file mode 100755 index 00000000..bb7f7c60 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m @@ -0,0 +1,24 @@ +% Authors: Eve LoCastro (locastre@mskcc.org), Dr. Ramesh Paudyal (paudyalr@mskcc.org), Dr. Amita Shukla-Dave (davea@mskcc.org) +% Institution: Memorial Sloan Kettering Cancer Center +% Address: 321 E 61st St, New York, NY 10022 + +% This is an example usage script for MSK Medical Physics Dave Lab QAMPER IVIM +% Please replace the variable names below with path values for +% qamper_path: path to MRI-QAMPER_IVIM folder +% img_nii: multi-b value DWI (4-D NIfTI) +% bval_file: b-value information (txt file) +% roi_nii: single-volume mask ROI image (NIfTI) + + + + +qamper_path = 'path:\to\MRI-QAMPER_IVIM'; +addpath(genpath(qamper_path)); + +img_nii = 'dwi.nii'; +bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); +roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); + +save_folder = fullfile(qamper_path,'test_data'); + +batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m new file mode 100755 index 00000000..3da2cf42 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m @@ -0,0 +1,8 @@ +function [img_nii,bval_file,roi_nii,save_folder] = getTestData + +[d,~,~] = fileparts(which('getTestData')); + +img_nii = fullfile(d,'test_data','702-HN401D-D2019_10_08.nii.gz'); +bval_file = fullfile(d,'test_data','702-HN401D-D2019_10_08.bval'); +roi_nii = fullfile(d,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); +save_folder = fullfile(d,'test_data'); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m new file mode 100755 index 00000000..2460b512 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m @@ -0,0 +1 @@ +function B=inpaint_nans(A,method) % INPAINT_NANS: in-paints over nans in an array % usage: B=INPAINT_NANS(A) % default method % usage: B=INPAINT_NANS(A,method) % specify method used % % Solves approximation to one of several pdes to % interpolate and extrapolate holes in an array % % arguments (input): % A - nxm array with some NaNs to be filled in % % method - (OPTIONAL) scalar numeric flag - specifies % which approach (or physical metaphor to use % for the interpolation.) All methods are capable % of extrapolation, some are better than others. % There are also speed differences, as well as % accuracy differences for smooth surfaces. % % methods {0,1,2} use a simple plate metaphor. % method 3 uses a better plate equation, % but may be much slower and uses % more memory. % method 4 uses a spring metaphor. % method 5 is an 8 neighbor average, with no % rationale behind it compared to the % other methods. I do not recommend % its use. % % method == 0 --> (DEFAULT) see method 1, but % this method does not build as large of a % linear system in the case of only a few % NaNs in a large array. % Extrapolation behavior is linear. % % method == 1 --> simple approach, applies del^2 % over the entire array, then drops those parts % of the array which do not have any contact with % NaNs. Uses a least squares approach, but it % does not modify known values. % In the case of small arrays, this method is % quite fast as it does very little extra work. % Extrapolation behavior is linear. % % method == 2 --> uses del^2, but solving a direct % linear system of equations for nan elements. % This method will be the fastest possible for % large systems since it uses the sparsest % possible system of equations. Not a least % squares approach, so it may be least robust % to noise on the boundaries of any holes. % This method will also be least able to % interpolate accurately for smooth surfaces. % Extrapolation behavior is linear. % % Note: method 2 has problems in 1-d, so this % method is disabled for vector inputs. % % method == 3 --+ See method 0, but uses del^4 for % the interpolating operator. This may result % in more accurate interpolations, at some cost % in speed. % % method == 4 --+ Uses a spring metaphor. Assumes % springs (with a nominal length of zero) % connect each node with every neighbor % (horizontally, vertically and diagonally) % Since each node tries to be like its neighbors, % extrapolation is as a constant function where % this is consistent with the neighboring nodes. % % method == 5 --+ See method 2, but use an average % of the 8 nearest neighbors to any element. % This method is NOT recommended for use. % % % arguments (output): % B - nxm array with NaNs replaced % % % Example: % [x,y] = meshgrid(0:.01:1); % z0 = exp(x+y); % znan = z0; % znan(20:50,40:70) = NaN; % znan(30:90,5:10) = NaN; % znan(70:75,40:90) = NaN; % % z = inpaint_nans(znan); % % % See also: griddata, interp1 % % Author: John D'Errico % e-mail address: woodchips@rochester.rr.com % Release: 2 % Release date: 4/15/06 % I always need to know which elements are NaN, % and what size the array is for any method [n,m]=size(A); A=A(:); nm=n*m; k=isnan(A(:)); % list the nodes which are known, and which will % be interpolated nan_list=find(k); known_list=find(~k); % how many nans overall nan_count=length(nan_list); % convert NaN indices to (r,c) form % nan_list==find(k) are the unrolled (linear) indices % (row,column) form [nr,nc]=ind2sub([n,m],nan_list); % both forms of index in one array: % column 1 == unrolled index % column 2 == row index % column 3 == column index nan_list=[nan_list,nr,nc]; % supply default method if (nargin<2) || isempty(method) method = 0; elseif ~ismember(method,0:5) error 'If supplied, method must be one of: {0,1,2,3,4,5}.' end % for different methods switch method case 0 % The same as method == 1, except only work on those % elements which are NaN, or at least touch a NaN. % is it 1-d or 2-d? if (m == 1) || (n == 1) % really a 1-d case work_list = nan_list(:,1); work_list = unique([work_list;work_list - 1;work_list + 1]); work_list(work_list <= 1) = []; work_list(work_list >= nm) = []; nw = numel(work_list); u = (1:nw)'; fda = sparse(repmat(u,1,3),bsxfun(@plus,work_list,-1:1), ... repmat([1 -2 1],nw,1),nw,nm); else % a 2-d case % horizontal and vertical neighbors only talks_to = [-1 0;0 -1;1 0;0 1]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with second partials on row % variable for each element in either list, but only % for those nodes which have a row index > 1 or < n L = find((all_list(:,2) > 1) & (all_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % 2nd partials on column index L = find((all_list(:,3) > 1) & (all_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 1 % least squares approach with del^2. Build system % for every array element as an unknown, and then % eliminate those which are knowns. % Build sparse matrix approximating del^2 for % every element in A. % is it 1-d or 2-d? if (m == 1) || (n == 1) % a 1-d case u = (1:(nm-2))'; fda = sparse(repmat(u,1,3),bsxfun(@plus,u,0:2), ... repmat([1 -2 1],nm-2,1),nm-2,nm); else % a 2-d case % Compute finite difference for second partials % on row variable first [i,j]=ndgrid(2:(n-1),1:m); ind=i(:)+(j(:)-1)*n; np=(n-2)*m; fda=sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... repmat([1 -2 1],np,1),n*m,n*m); % now second partials on column variable [i,j]=ndgrid(1:n,2:(m-1)); ind=i(:)+(j(:)-1)*n; np=n*(m-2); fda=fda+sparse(repmat(ind,1,3),[ind-n,ind,ind+n], ... repmat([1 -2 1],np,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 2 % Direct solve for del^2 BVP across holes % generate sparse array with second partials on row % variable for each nan element, only for those nodes % which have a row index > 1 or < n % is it 1-d or 2-d? if (m == 1) || (n == 1) % really just a 1-d case error('Method 2 has problems for vector input. Please use another method.') else % a 2-d case L = find((nan_list(:,2) > 1) & (nan_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); else fda=spalloc(n*m,n*m,size(nan_list,1)*5); end % 2nd partials on column index L = find((nan_list(:,3) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); end % fix boundary conditions at extreme corners % of the array in case there were nans there if ismember(1,nan_list(:,1)) fda(1,[1 2 n+1])=[-2 1 1]; end if ismember(n,nan_list(:,1)) fda(n,[n, n-1,n+n])=[-2 1 1]; end if ismember(nm-n+1,nan_list(:,1)) fda(nm-n+1,[nm-n+1,nm-n+2,nm-n])=[-2 1 1]; end if ismember(nm,nan_list(:,1)) fda(nm,[nm,nm-1,nm-n])=[-2 1 1]; end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end case 3 % The same as method == 0, except uses del^4 as the % interpolating operator. % del^4 template of neighbors talks_to = [-2 0;-1 -1;-1 0;-1 1;0 -2;0 -1; ... 0 1;0 2;1 -1;1 0;1 1;2 0]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with del^4, but only % for those nodes which have a row & column index % >= 3 or <= n-2 L = find( (all_list(:,2) >= 3) & ... (all_list(:,2) <= (n-2)) & ... (all_list(:,3) >= 3) & ... (all_list(:,3) <= (m-2))); nl=length(L); if nl>0 % do the entire template at once fda=sparse(repmat(all_list(L,1),1,13), ... repmat(all_list(L,1),1,13) + ... repmat([-2*n,-n-1,-n,-n+1,-2,-1,0,1,2,n-1,n,n+1,2*n],nl,1), ... repmat([1 2 -8 2 1 -8 20 -8 1 2 -8 2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % on the boundaries, reduce the order around the edges L = find((((all_list(:,2) == 2) | ... (all_list(:,2) == (n-1))) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))) | ... (((all_list(:,3) == 2) | ... (all_list(:,3) == (m-1))) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1)))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,5), ... repmat(all_list(L,1),1,5) + ... repmat([-n,-1,0,+1,n],nl,1), ... repmat([1 1 -4 1 1],nl,1),nm,nm); end L = find( ((all_list(:,2) == 1) | ... (all_list(:,2) == n)) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-n,0,n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end L = find( ((all_list(:,3) == 1) | ... (all_list(:,3) == m)) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-1,0,1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 4 % Spring analogy % interpolating operator. % list of all springs between a node and a horizontal % or vertical neighbor hv_list=[-1 -1 0;1 1 0;-n 0 -1;n 0 1]; hv_springs=[]; for i=1:4 hvs=nan_list+repmat(hv_list(i,:),nan_count,1); k=(hvs(:,2)>=1) & (hvs(:,2)<=n) & (hvs(:,3)>=1) & (hvs(:,3)<=m); hv_springs=[hv_springs;[nan_list(k,1),hvs(k,1)]]; end % delete replicate springs hv_springs=unique(sort(hv_springs,2),'rows'); % build sparse matrix of connections, springs % connecting diagonal neighbors are weaker than % the horizontal and vertical springs nhv=size(hv_springs,1); springs=sparse(repmat((1:nhv)',1,2),hv_springs, ... repmat([1 -1],nhv,1),nhv,nm); % eliminate knowns rhs=-springs(:,known_list)*A(known_list); % and solve... B=A; B(nan_list(:,1))=springs(:,nan_list(:,1))\rhs; case 5 % Average of 8 nearest neighbors % generate sparse array to average 8 nearest neighbors % for each nan element, be careful around edges fda=spalloc(n*m,n*m,size(nan_list,1)*9); % -1,-1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,-1 L = find(nan_list(:,3) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,-1 L = find((nan_list(:,2) < n) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,0 L = find(nan_list(:,2) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,0 L = find(nan_list(:,2) < n); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,+1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,+1 L = find(nan_list(:,3) < m); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,+1 L = find((nan_list(:,2) < n) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end % all done, make sure that B is the same shape as % A was when we came in. B=reshape(B,n,m); % ==================================================== % end of main function % ==================================================== % ==================================================== % begin subfunctions % ==================================================== function neighbors_list=identify_neighbors(n,m,nan_list,talks_to) % identify_neighbors: identifies all the neighbors of % those nodes in nan_list, not including the nans % themselves % % arguments (input): % n,m - scalar - [n,m]=size(A), where A is the % array to be interpolated % nan_list - array - list of every nan element in A % nan_list(i,1) == linear index of i'th nan element % nan_list(i,2) == row index of i'th nan element % nan_list(i,3) == column index of i'th nan element % talks_to - px2 array - defines which nodes communicate % with each other, i.e., which nodes are neighbors. % % talks_to(i,1) - defines the offset in the row % dimension of a neighbor % talks_to(i,2) - defines the offset in the column % dimension of a neighbor % % For example, talks_to = [-1 0;0 -1;1 0;0 1] % means that each node talks only to its immediate % neighbors horizontally and vertically. % % arguments(output): % neighbors_list - array - list of all neighbors of % all the nodes in nan_list if ~isempty(nan_list) % use the definition of a neighbor in talks_to nan_count=size(nan_list,1); talk_count=size(talks_to,1); nn=zeros(nan_count*talk_count,2); j=[1,nan_count]; for i=1:talk_count nn(j(1):j(2),:)=nan_list(:,2:3) + ... repmat(talks_to(i,:),nan_count,1); j=j+nan_count; end % drop those nodes which fall outside the bounds of the % original array L = (nn(:,1)<1)|(nn(:,1)>n)|(nn(:,2)<1)|(nn(:,2)>m); nn(L,:)=[]; % form the same format 3 column array as nan_list neighbors_list=[sub2ind([n,m],nn(:,1),nn(:,2)),nn]; % delete replicates in the neighbors list neighbors_list=unique(neighbors_list,'rows'); % and delete those which are also in the list of NaNs. neighbors_list=setdiff(neighbors_list,nan_list,'rows'); else neighbors_list=[]; end \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m new file mode 100755 index 00000000..6ea04c4e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m @@ -0,0 +1,17 @@ +function s = ivim_modeling_noise(x,b,sigma) +%IVIM analysis +%s = s0*((1-f)exp(-b.D) + f.exp(- b(D + Dstar))) +%x = [f,D,Dstar]; + + +f = x(1); +D = x(2); +Dstar = x(3); +s0 = x(4); + +s = s0*((1-f)*exp(-b*D) + f*exp(-b*Dstar)); +% n = 8; +% navg = 4; +% s = sqrt(s.^2 + 2*n*sigma.^2/navg); + +s = sqrt(s.^2 + sigma.^2); diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m new file mode 100755 index 00000000..51290eaa --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m @@ -0,0 +1,57 @@ +function current_results_out = outputHNMPAResults(current_results,csv_outdir,modality,roiType,resultsCell) + + +current_results_out = current_results; + +struct_string = [modality '_' roiType]; + +% current_results = outputHNMPAResults(current_results,'IVIM',roiType,{bval_arr,dwi_arr,[f_arr;D_arr;Dx_arr;s0_arr],fitted_dwi_arr,RSS,rms_val,chi,AIC,BIC}); +% current_results = outputHNMPAResults(current_results,'IVIM',roiType,{bval_arr,dwi_arr,[f_arr;D_arr;Dx_arr;s0_arr],fitted_dwi_arr,RSS,rms_val,chi,AIC,BIC,SNR,sigma,LB_IVIM,UB_IVIM,ROI}); +eval(['current_results_out.' struct_string '_b_value_fit = resultsCell{1};']); bval_arr = resultsCell{1}; +eval(['current_results_out.' struct_string '_dwi_data = resultsCell{2};']); + +params_out = resultsCell{3}; + +eval(['current_results_out.' struct_string '_params_f = params_out(1,:);']); f = params_out(1,:); +eval(['current_results_out.' struct_string '_features_f = returnFeatures(params_out(1,:));']); + +eval(['current_results_out.' struct_string '_params_D = params_out(2,:);']); D = params_out(2,:); +eval(['current_results_out.' struct_string '_features_D = returnFeatures(params_out(2,:));']); + +eval(['current_results_out.' struct_string '_params_Dx = params_out(3,:);']); Dx = params_out(3,:); +eval(['current_results_out.' struct_string '_features_Dx = returnFeatures(params_out(3,:));']); + +eval(['current_results_out.' struct_string '_params_s0 = params_out(4,:);']); +eval(['current_results_out.' struct_string '_features_s0 = returnFeatures(params_out(4,:));']); + +eval(['current_results_out.' struct_string '_fitted_dwi_data1 = resultsCell{4};']); +%Fit quality +eval(['current_results_out.' struct_string '_quality_RSS = resultsCell{5};']); RSS = resultsCell{5}; +eval(['current_results_out.' struct_string '_quality_rms_val = resultsCell{6};']); +eval(['current_results_out.' struct_string '_quality_chi = resultsCell{7};']); +eval(['current_results_out.' struct_string '_quality_AIC = resultsCell{8};']); +eval(['current_results_out.' struct_string '_quality_BIC = resultsCell{9};']); +eval(['current_results_out.' struct_string '_quality_SNR = resultsCell{10};']); SNR = resultsCell{10}; +eval(['current_results_out.' struct_string '_quality_sigma = resultsCell{11};']); sigmadwi = resultsCell{11}; +eval(['current_results_out.' struct_string '_LB_IVIM = resultsCell{12};']); LB_IVIM = resultsCell{12}; +eval(['current_results_out.' struct_string '_UB_IVIM = resultsCell{13};']); UB_IVIM = resultsCell{13}; +eval(['current_results_out.' struct_string '_quality_Rsq = resultsCell{end-1};']); R_sq = resultsCell{end-1}; +eval(['current_results_out.' struct_string '_ROI_map = resultsCell{end};']); ROI = resultsCell{end}; + +outstringheader='pat_ID,analysis_batch,tissue,D_Mean,D_Median,D_StDev,D_Skewness,D_Avg_Signal,D*_Mean,D*_Median,D*_StDev,D*_Skewness,D*_Avg_Signal,f_Mean,f_Median,f_StDev,f_Skewness,f_Avg_Signal,volume,voxels,RSS,b-values,SNR,sigma,R_sq,D_Kurtosis,D*_Kurtosis,f_Kurtosis'; + +outcsv = [csv_outdir filesep 'dwi_IVIM.csv']; voxdim = current_results.dwinii.hdr.dime.pixdim(2) * current_results.dwinii.hdr.dime.pixdim(3) * current_results.dwinii.hdr.dime.pixdim(4); +voxnum = numel(find(ROI)); +outstring = [current_results.pat_id ',' num2str(current_results.batch_id) ',' roiType ',' num2str(mean(D(1:end-1))) ',' num2str(median(D(1:end-1))) ',' num2str(std(D(1:end-1))) ',' num2str(skewness(D(1:end-1))) ',' num2str(D(end)) ',' ... + num2str(mean(Dx(1:end-1))) ',' num2str(median(Dx(1:end-1))) ',' num2str(std(Dx(1:end-1))) ',' num2str(skewness(Dx(1:end-1))) ',' num2str(Dx(end)) ',' ... + num2str(mean(f(1:end-1))) ',' num2str(median(f(1:end-1))) ',' num2str(std(f(1:end-1))) ',' num2str(skewness(f(1:end-1))) ',' num2str(f(end)) ',' ... + num2str(voxdim * voxnum) ',' num2str(voxnum) ',' num2str(mean(RSS)) ',' num2str(numel(bval_arr)) ',' num2str(SNR) ',' num2str(sigmadwi) ',' num2str(R_sq) ',' num2str(kurtosis(D(1:end-1))) ',' num2str(kurtosis(Dx(1:end-1))) ',' num2str(kurtosis(f(1:end-1)))]; +if ~exist(outcsv,'file') + fid = fopen(outcsv,'w'); + fprintf(fid,'%s\n',outstringheader); +else + fid = fopen(outcsv,'a'); +end +fprintf(fid,'%s\n',outstring); +fclose(fid); +eval(['current_results_out.' struct_string '_CSV = outstring ;']); diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m new file mode 100755 index 00000000..80827f15 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m @@ -0,0 +1,31 @@ +function [RSS,rms_val,chi,AIC,BIC,R_sq_mean] = qualityFit(Nb,Np,observed_arr,predicted_arr) + +numVoxels = size(observed_arr,2); +RSS = zeros(numVoxels,1); +rms_val = zeros(numVoxels,1); +chi = zeros(numVoxels,1); +AIC = zeros(numVoxels,1); +BIC = zeros(numVoxels,1); + +for i = 1:numVoxels + observed_signal = observed_arr(:,i); + predicted_signal = predicted_arr(:,i); + + signal_diff = observed_signal - predicted_signal; + + obs_mean = mean(observed_signal); + SST(i) = sum((observed_signal - obs_mean).^2); + + RSS(i) = sum(signal_diff.^2); %SSE + + R_sq(i) = 1 - (RSS(i)/SST(i)); + + rms_val(i) = sqrt(RSS(i) / Nb); + chi(i) = (sum(signal_diff.^2 ./ predicted_signal)) / Nb; + AIC(i) = 2 * Np + Nb * log(RSS(i) / Nb) + (2 * Np * (Np + 1)) / (Nb - Np - 1); % AICc + BIC(i) = Nb * log(RSS(i)/Nb) + Np * log(Nb); +end + +disp(['mean Rsq ' num2str(mean(R_sq(find(R_sq > 0))))]); + +R_sq_mean = mean(R_sq(find(R_sq > 0))); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log new file mode 100755 index 00000000..e69de29b diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m new file mode 100755 index 00000000..6a0f13ef --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m @@ -0,0 +1,10 @@ +function arrFeatures = returnFeatures(param_arr) + +measure_set = {'Mean','Median','StDev','Skewness','Avg_Signal'}; + +arrFeatures = zeros(1,5); + +arrFeatures(1) = mean(param_arr); +arrFeatures(2) = median(param_arr); +arrFeatures(3) = std(param_arr); +arrFeatures(4) = skewness(param_arr); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m new file mode 100755 index 00000000..9908cbd0 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m @@ -0,0 +1,24 @@ +function write_param_txt(param_map,ROI,txtfilename,transformscale) +% +% author EML +% write parameter values in txt file for use in COMSOL +if nargin < 4 || isempty(transformscale) + transformscale = 1; +end + +idx = find(ROI); + +if size(param_map,3) > 1 + [Vx,Vy,Vz] = ind2sub(size(param_map),idx); +% formatspec = '%5.1f \t %5.1f \t %5.1f \t %8.8f \n'; + formatspec = '%5.1f \t %5.1f \t %5.1f \t %e \n'; + interparr = [transformscale*Vx transformscale*Vy transformscale*Vz param_map(idx)]'; +else + [Vx,Vy] = ind2sub(size(param_map),idx); + formatspec = '%5.1f \t %5.1f \t %8.8f \n'; + interparr = [transformscale*Vx transformscale*Vy param_map(idx)]'; +end + +fid = fopen(txtfilename,'w'); +fprintf(fid,formatspec,interparr); +fclose(fid); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m new file mode 100755 index 00000000..3e4f5631 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m @@ -0,0 +1,181 @@ +function runQAMPERBatch(batchStruct) %,csv_outdir) + +batch_id = batchStruct.batch_id; +DWIstruct = batchStruct.DWIstruct; + +% userCachePath = setUserCache; +% batchPath = [userCachePath filesep num2str(batch_id)]; +batchPath = fullfile(batchStruct.currentNiiPath,num2str(batch_id)); +csv_outdir = batchPath; + +if ~exist(batchPath,'dir') + mkdir(batchPath); +end + +save(fullfile(batchPath,'batchStruct.mat'),'batchStruct'); + + +roipath = [batchPath filesep 'roi']; +if ~exist(roipath) + mkdir(roipath); +end +% disp(['Saving batch struct to ' batchStruct.currentNiiPath '...']); +% save(fullfile(batchStruct.currentNiiPath,['batchStruct_' num2str(batch_id) '.mat']),'batchStruct'); + + +if DWIstruct.runDWI + + current_results = []; + current_results.batch_id = batch_id; + + + if ~isfield(DWIstruct,'Parallel') + DWIstruct.Parallel = 1; + end + + dwipath = [batchPath filesep 'dwi_ivim']; + + if ~exist(dwipath,'dir') + mkdir(dwipath); + end + + bval_arr = DWIstruct.bval_arr; + bval_tf = DWIstruct.bval_tf; + channels = DWIstruct.channels; + N_avgs = DWIstruct.N_avgs; + + num_bvals = numel(find(bval_tf)); + + bval_omit = find(~bval_tf); + + dwi_img = []; + + if ~iscell(DWIstruct.files{1}) + dwifile = deb(char(DWIstruct.files{1}(1,:))); + copyfile(dwifile,dwipath); + dwinii = nii_load(dwifile); + dwi_img = dwinii.img; + else + for i = 1:numel(DWIstruct.files{1}) + dwifile = deb(DWIstruct.files{1}{i}); + copyfile(dwifile,dwipath); + dwinii = nii_load(dwifile,1,DWIstruct.vols{i}); + dwi_img = cat(4,dwi_img,dwinii.img); + end + end + + current_results.dwinii = dwinii; current_results.pat_id = dwinii.fileprefix; + current_results.dwinii.img = []; + + nanopt = DWIstruct.nanopt; + smoothopt = DWIstruct.smoothopt; + + previewMode = DWIstruct.previewMode; + previewAxes = DWIstruct.previewAxes; + parallelFlag = DWIstruct.Parallel; + + if numel(bval_arr) ~= size(dwi_img,4) + disp('Error, mismatched images and bvals'); + end + + %remove entries if unchecked + if ~isempty(bval_omit) + dwi_img(:,:,:,bval_omit) = []; + bval_arr(bval_omit) = []; + end + + if numel(bval_arr) ~= size(dwi_img,4) + disp('Error, mismatched images and bvals'); + end + + dwi_series_num = '' ; %getSeriesNum(dwifile); + + roifile = DWIstruct.files{2}; copyfile(roifile,dwipath); + roinii = nii_load(roifile); + ROI = roinii.img; + + roi_series_num = ''; %getSeriesNum(roifile); + + roiType = DWIstruct.roiType; %getROIType(roifile); + [~,fname,~] = fileparts(dwifile); [~,fname,~] = fileparts(fname); + save_prefix = [dwipath filesep fname]; +% save_prefix = [dwipath filesep dwi_series_num '-' roi_series_num '-' roiType]; +% +% if bval_arr(end) == 0 +% [bval_arr,dwi_img] = bvalZeroOrderFix(bval_arr,dwi_img); +% end + + [bval_arr,dwi_img] = bvalOrderFix(bval_arr,dwi_img); + + if isfield(DWIstruct,'avgRepeatBvals') + if DWIstruct.avgRepeatBvals + [bval_arr,dwi_img] = avgRepeatBvalVols(bval_arr,dwi_img); + num_bvals = numel(bval_arr); + end + end +% + % optional smoothing + if smoothopt + dwi_smooth = zeros(size(dwi_img)); + smoothsize = 2*floor(DWIstruct.kernelsize/2)+1; + for i = 1:num_bvals + dwi_smooth(:,:,:,i) = smooth3(dwi_img(:,:,:,i),'gaussian',repmat(smoothsize,[1,3])); + end + dwi_img = dwi_smooth; + end + + [dwi_arr, bval_arr, sigmadwi, SNR, noise] = dataPrepDWI(dwi_img,bval_arr,ROI,channels,N_avgs); + + testarr = dwi_arr - sigmadwi; + numel(testarr < 0) + + poorSNRFlag = 0; + +% if SNR < 100 +% disp(['SNR is too low to continue (' num2str(SNR) ')']); +% % H = warndlg('SNR in series is very low. Proceeding without proper noise handling'); +% sigmadwi = 0; +% noise = 0; +% poorSNRFlag = 1; +% end + + %%%%% Matrix methods for ADC and Kurtosis + + + if DWIstruct.IVIM + disp('Running IVIM'); + LB_IVIM = DWIstruct.LB_IVIM; + UB_IVIM = DWIstruct.UB_IVIM; + x0_IVIM = DWIstruct.x0_IVIM; + + [f_arr, D_arr, Dx_arr, s0_arr, fitted_dwi_arr, RSS, rms_val, chi, AIC, BIC, R_sq] = IVIM_standard_bcin(dwi_arr,bval_arr,sigmadwi,LB_IVIM,UB_IVIM,x0_IVIM,parallelFlag,previewMode,previewAxes); + + f_map = arr2map2(f_arr(:,1:end-1),ROI); + nii = roinii; + nii.img = f_map; + f_filename = [save_prefix '_IVIM_f.nii.gz']; + nii_save(nii,f_filename); + + D_map = arr2map2(D_arr(:,1:end-1),ROI); + nii = roinii; + nii.img = D_map; + D_filename = [save_prefix '_IVIM_D.nii.gz']; + nii_save(nii,D_filename); + + Dx_map = arr2map2(Dx_arr(:,1:end-1),ROI); + nii = roinii; + nii.img = Dx_map; + Dx_filename = [save_prefix '_IVIM_Dx.nii.gz']; + nii_save(nii,Dx_filename); + + s0_map = arr2map2(s0_arr(:,1:end-1),ROI); + nii = roinii; + nii.img = s0_map; + s0_filename = [save_prefix '_IVIM_s0.nii.gz']; + nii_save(nii,s0_filename); + + current_results = outputHNMPAResults(current_results,csv_outdir,'IVIM',roiType,{bval_arr,dwi_arr,[f_arr;D_arr;Dx_arr;s0_arr],fitted_dwi_arr,RSS,rms_val,chi,AIC,BIC,SNR,sigmadwi,LB_IVIM,UB_IVIM,R_sq,ROI}); +% updatePreviewAxes(previewAxes,bval_arr,dwi_arr(:,end),fitted_dwi_arr(:,end),{'b-value (s/mm^2)','signal (a.u.)','Mean fitted signal, IVIM'}); + end +end + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m new file mode 100755 index 00000000..191ef975 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m @@ -0,0 +1,109 @@ +function batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder,optionS) + +% function batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder,optionS) +% Inputs: image_nii (char): path to 4-D NIfTI DWI volume with all b-values +% bval_file (char): path to .bval text file with b-value for each volume +% roi_nii (char): path to NIfTI ROI with segmentation mask for analysis region +% save_folder (char): path to parent folder for results. Routine will create a subfolder under this location named 'dwi_maps' +% optionS (struct): additional processing options for QAMPER +% 'roiType' (char, default = 'node') +% 'avgRepeatBvals' (int, default = 1 (True)) +% 'channels' (int, default = 16) the number of receiver channels/elements present in the coil (depending on organ of choice) for the DW-MRI data acquisition. +% This factor is considered in noise correction calculation, please consult the details for MRI coil procured from the MRI Vendor to get this number. +% 'N_avgs' (int, default = 2) the number averages (DICOM TAG: 0018,0083) used for DW-MRI data acquisition (Navg = 2 or 4, etc., for each b-value). +% +% Dependencies: nii_toolbox (https://matlab.mathworks.com/open/fileexchange/v1?id=8797), included +% +% + +if nargin < 5 + batchStruct = []; +else + batchStruct = optionS; +end + +batchStruct.batch_id = num2str(now); + +DWIstruct.runDWI = 1; +% [~,fbase,ext] = fileparts(img_nii); + +batchStruct.currentNiiPath = fullfile(save_folder,'dwi_maps'); +batchResultsFolder = fullfile(save_folder,'dwi_maps',batchStruct.batch_id); + +% if strcmp(ext,'.gz') +% [~,fbase,~] = fileparts(fbase); +% end +% +% bval_mat = fullfile(droot,[fbase '.mat']); +bval_arr = load(bval_file); +% + +DWIstruct.bval_arr = bval_arr; + +img = nii_load(img_nii,1); +vol_num = size(img.img,4); + +DWIstruct.files{1} = img_nii; DWIstruct.vols = vol_num; +DWIstruct.files{2} = roi_nii; + + +DWIstruct.ADC = 0; +DWIstruct.NGIVIM=0; +DWIstruct.Kurtosis = 0; + +DWIstruct.IVIM = 1; + +if ~isfield(DWIstruct,'roiType') + DWIstruct.roiType = 'node'; +end + +if ~isfield(DWIstruct,'avgRepeatBvals') + DWIstruct.avgRepeatBvals = 1; +end + +if ~isfield(DWIstruct,'channels') + DWIstruct.channels = 16; % +end + +if ~isfield(DWIstruct, 'N_avgs') + DWIstruct.N_avgs = 2; % +end + +if ~isfield(DWIstruct,'LB_IVIM') + IVIMBoundsTable = [{0} {0} {0} {0}; {0.5} {0.005} {0.5} {1.0}; {0.05} {0.0005} {0.01} {0.5}]; + + DWIstruct.LB_IVIM = cell2mat(IVIMBoundsTable(1,:)); + DWIstruct.UB_IVIM = cell2mat(IVIMBoundsTable(2,:)); + DWIstruct.x0_IVIM = cell2mat(IVIMBoundsTable(3,:)); +end +% end + +if ~isfield(DWIstruct,'Parallel') + DWIstruct.Parallel = 1; +end + +DWIstruct.bval_tf = ones(size(DWIstruct.bval_arr)); +DWIstruct.nanopt = 1; + +if ~isfield(DWIstruct,'kernelsize') + DWIstruct.kernelsize = 1; +end +if ~isfield(DWIstruct,'smoothopt') + DWIstruct.smoothopt = 1; +end + +DWIstruct.previewMode = 0; +DWIstruct.previewAxes = 0; + +batchStruct.DWIstruct = DWIstruct; +batchStruct.T1struct.runT1 = 0; +batchStruct.DCEstruct.runDCE = 0; +batchStruct.T2struct.runT2 = 0; + +% save the batchfile for the job in folder with input image and in +% output folder +save(fullfile(save_folder,['batchStruct_' batchStruct.batch_id '.mat']),'batchStruct'); + +% run the batch +runQAMPERBatch(batchStruct); +disp('Batch complete.'); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m new file mode 100755 index 00000000..fc6ce345 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m @@ -0,0 +1,60 @@ +function [arr_names,sz]=Dir2Arr(srcpth,extns,omit,silentFlag) +%Author: Eve LoCastro, Weill Cornell Medical College, Department of Radiology, IDEAL Imaging Lab, November 2011 +%Dir2Arr sends dir search filenames to matlab array variable (like its pre- +%decessor, DirToArr), but with extended functionality. srcpth, extns and +%omit can be all be (cell) arrays of text. Returns text array with +%spaced-padding at end + + +if nargin < 4 + silentFlag = 0; +end + + +MSL=500; +% +if nargin > 2 && ~isempty(omit) + omit_arr=Dir2Arr(srcpth,omit,'',silentFlag); + if isempty(omit_arr) + omit_list={}; + else + O=size(omit_arr,1); + for o=1:O + [~,f,e]=fileparts(omit_arr(o,:)); + omit_arr(o,:)=[f e blanks(MSL-length([f e]))]; + end + omit_list=cellstr(omit_arr); + end +else + omit_list={}; +end + +arr_names=[]; + +if ~iscellstr(srcpth), srcpth=cellstr(srcpth); end +if ~iscellstr(extns), extns=cellstr(extns); end + +dupe_prevention={}; + +for p=1:length(srcpth) + read_dir=srcpth{p}; + for e=1:length(extns) + G=dir([read_dir filesep extns{e}]); + for g=1:length(G) + if ~ismember(G(g).name,omit_list) && ~strcmp(G(g).name(1),'.') && ~ismember(G(g).name,dupe_prevention) + fname=[read_dir filesep G(g).name]; + dupe_prevention{end+1}=G(g).name; + arr_names=[arr_names; fname blanks(MSL-length(fname))]; + end + end + end + %disp(p); +end + + +sz=size(arr_names,1); + +if ~silentFlag + disp(['Dir2Arr: ' int2str(sz) ' entries.']); +end +end \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m new file mode 100755 index 00000000..a0824898 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m @@ -0,0 +1,55 @@ +function map = arr2map2(arr,ROI) + +dim = size(ROI); +idx = find(ROI); + +%assume arr is a 3d map in 1xN form; if 4d timeseries with M timepoints, +%arr will be MXN form +%dim should have 3 dimensions + +if size(arr,1) == 1 || size(arr,2) == 1 + if size(arr,1) < size(arr,2) + arr = arr'; + end +end + +% for some reason DWI/DCE had 1 more element in OPM so opted to remove last one +if size(arr, 1) > size(idx, 1) + arr = arr(:,1:end-1); +end +if size(idx, 1) > size(arr, 1) + idx = idx(1:end-1); +end + +if numel(dim) < 3 + dim(3) = 1; +end + +roisz = numel(find(ROI)); + +if size(arr,1) == roisz + N = 2; + tpts = size(arr,2); +else + N = 1; + tpts = size(arr,1); +end + +% try +map = zeros(dim(1),dim(2),dim(3),size(arr,N)); +% catch +% arr = arr'; +% map = zeros(dim(1),dim(2),dim(3),size(arr,1)); +% end + +mapi = zeros(1,dim(1)*dim(2)*dim(3)); + +for i = 1:tpts + if N == 1 + mapi(idx) = double(arr(i,:)); + elseif N == 2 + mapi(idx) = double(arr(:,i))'; + end + map(:,:,:,i) = reshape(mapi,dim); + +end \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m new file mode 100755 index 00000000..4fdf0af4 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m @@ -0,0 +1,3 @@ +function db = deb(longstring) +% @EveLoCastro +db = deblank(longstring); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m new file mode 100755 index 00000000..2e983f31 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m @@ -0,0 +1,13 @@ +function LRflip = flipCheck(nii1,nii2) + +% uses OtsuThreshold +% +% nii1 = nii_load(img1,1); +% nii2 = nii_load(img2,1); + +if nii1.hdr.dime.pixdim(1) == -nii2.hdr.dime.pixdim(1) + LRflip = 1; +else + LRflip = 0; +end + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m new file mode 100755 index 00000000..95fb426e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m @@ -0,0 +1,28 @@ +function arr = map2arr(map,roi) +%Author: Eve LoCastro, Weill Cornell Medical College, Department of Radiology, IDEAL Imaging Lab, November 2011 +%assume map is a 3d map in LXMxNxT form if 4d timeseries with T timepoints, +%arr will be Tx(L*M*N) form + +dim = size(map); +% if length(dim) < 4 +% idxlen = dim(1)*dim(2); +% else +% maplen = dim(1)*dim(2)*dim(3); +% end + +if length(dim) < 4 + tpts = 1; +else + tpts = dim(4); +end + +% dotmap = map .* repmat(roi,1,1,1,tpts); + +idx = find(roi); + +arr = zeros(tpts,length(idx)); + +for i = 1:tpts + tmpmap = map(:,:,:,i); + arr(i,:) = tmpmap(idx)'; +end \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf new file mode 100755 index 0000000000000000000000000000000000000000..b04456b9250a92b9413811fb42a826167d41281e GIT binary patch literal 206445 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 Ozzr{O1O)M~!2bg?TfVda literal 0 HcmV?d00001 diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf new file mode 100755 index 0000000000000000000000000000000000000000..592c807bbf13ed0544ca599f4828b62ef930f565 GIT binary patch literal 61950 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 z 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 V?JYi9A1?dDXm-Fy(f+5={|h4amr(!! literal 0 HcmV?d00001 diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m new file mode 100755 index 00000000..c494c63e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m @@ -0,0 +1,554 @@ +% Using 2D or 3D affine matrix to rotate, translate, scale, reflect and +% shear a 2D image or 3D volume. 2D image is represented by a 2D matrix, +% 3D volume is represented by a 3D matrix, and data type can be real +% integer or floating-point. +% +% You may notice that MATLAB has a function called 'imtransform.m' for +% 2D spatial transformation. However, keep in mind that 'imtransform.m' +% assumes y for the 1st dimension, and x for the 2nd dimension. They are +% equivalent otherwise. +% +% In addition, if you adjust the 'new_elem_size' parameter, this 'affine.m' +% is equivalent to 'interp2.m' for 2D image, and equivalent to 'interp3.m' +% for 3D volume. +% +% Usage: [new_img new_M] = ... +% affine(old_img, old_M, [new_elem_size], [verbose], [bg], [method]); +% +% old_img - original 2D image or 3D volume. We assume x for the 1st +% dimension, y for the 2nd dimension, and z for the 3rd +% dimension. +% +% old_M - a 3x3 2D affine matrix for 2D image, or a 4x4 3D affine +% matrix for 3D volume. We assume x for the 1st dimension, +% y for the 2nd dimension, and z for the 3rd dimension. +% +% new_elem_size (optional) - size of voxel along x y z direction for +% a transformed 3D volume, or size of pixel along x y for +% a transformed 2D image. We assume x for the 1st dimension +% y for the 2nd dimension, and z for the 3rd dimension. +% 'new_elem_size' is 1 if it is default or empty. +% +% You can increase its value to decrease the resampling rate, +% and make the 2D image or 3D volume more coarse. It works +% just like 'interp3'. +% +% verbose (optional) - 1, 0 +% 1: show transforming progress in percentage +% 2: progress will not be displayed +% 'verbose' is 1 if it is default or empty. +% +% bg (optional) - background voxel intensity in any extra corner that +% is caused by the interpolation. 0 in most cases. If it is +% default or empty, 'bg' will be the average of two corner +% voxel intensities in original data. +% +% method (optional) - 1, 2, or 3 +% 1: for Trilinear interpolation +% 2: for Nearest Neighbor interpolation +% 3: for Fischer's Bresenham interpolation +% 'method' is 1 if it is default or empty. +% +% new_img - transformed 2D image or 3D volume +% +% new_M - transformed affine matrix +% +% Example 1 (3D rotation): +% load mri.mat; old_img = double(squeeze(D)); +% old_M = [0.88 0.5 3 -90; -0.5 0.88 3 -126; 0 0 2 -72; 0 0 0 1]; +% new_img = affine(old_img, old_M, 2); +% [x y z] = meshgrid(1:128,1:128,1:27); +% sz = size(new_img); +% [x1 y1 z1] = meshgrid(1:sz(2),1:sz(1),1:sz(3)); +% figure; slice(x, y, z, old_img, 64, 64, 13.5); +% shading flat; colormap(map); view(-66, 66); +% figure; slice(x1, y1, z1, new_img, sz(1)/2, sz(2)/2, sz(3)/2); +% shading flat; colormap(map); view(-66, 66); +% +% Example 2 (2D interpolation): +% load mri.mat; old_img=D(:,:,1,13)'; +% old_M = [1 0 0; 0 1 0; 0 0 1]; +% new_img = affine(old_img, old_M, [.2 .4]); +% figure; image(old_img); colormap(map); +% figure; image(new_img); colormap(map); +% +% This program is inspired by: +% SPM5 Software from Wellcome Trust Centre for Neuroimaging +% http://www.fil.ion.ucl.ac.uk/spm/software +% Fischer, J., A. del Rio (2004). A Fast Method for Applying Rigid +% Transformations to Volume Data, WSCG2004 Conference. +% http://wscg.zcu.cz/wscg2004/Papers_2004_Short/M19.pdf +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [new_img, new_M] = affine(old_img, old_M, new_elem_size, verbose, bg, method) + + if ~exist('old_img','var') | ~exist('old_M','var') + error('Usage: [new_img new_M] = affine(old_img, old_M, [new_elem_size], [verbose], [bg], [method]);'); + end + + if ndims(old_img) == 3 + if ~isequal(size(old_M),[4 4]) + error('old_M should be a 4x4 affine matrix for 3D volume.'); + end + elseif ndims(old_img) == 2 + if ~isequal(size(old_M),[3 3]) + error('old_M should be a 3x3 affine matrix for 2D image.'); + end + else + error('old_img should be either 2D image or 3D volume.'); + end + + if ~exist('new_elem_size','var') | isempty(new_elem_size) + new_elem_size = [1 1 1]; + elseif length(new_elem_size) < 2 + new_elem_size = new_elem_size(1)*ones(1,3); + elseif length(new_elem_size) < 3 + new_elem_size = [new_elem_size(:); 1]'; + end + + if ~exist('method','var') | isempty(method) + method = 1; + elseif ~exist('bresenham_line3d.m','file') & method == 3 + error([char(10) char(10) 'Please download 3D Bresenham''s line generation program from:' char(10) char(10) 'http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=21057' char(10) char(10) 'to test Fischer''s Bresenham interpolation method.' char(10) char(10)]); + end + + % Make compatible to MATLAB earlier than version 7 (R14), which + % can only perform arithmetic on double data type + % + old_img = double(old_img); + old_dim = size(old_img); + + if ~exist('bg','var') | isempty(bg) + bg = mean([old_img(1) old_img(end)]); + end + + if ~exist('verbose','var') | isempty(verbose) + verbose = 1; + end + + if ndims(old_img) == 2 + old_dim(3) = 1; + old_M = old_M(:, [1 2 3 3]); + old_M = old_M([1 2 3 3], :); + old_M(3,:) = [0 0 1 0]; + old_M(:,3) = [0 0 1 0]'; + end + + % Vertices of img in voxel + % + XYZvox = [ 1 1 1 + 1 1 old_dim(3) + 1 old_dim(2) 1 + 1 old_dim(2) old_dim(3) + old_dim(1) 1 1 + old_dim(1) 1 old_dim(3) + old_dim(1) old_dim(2) 1 + old_dim(1) old_dim(2) old_dim(3) ]'; + + old_R = old_M(1:3,1:3); + old_T = old_M(1:3,4); + + % Vertices of img in millimeter + % + XYZmm = old_R*(XYZvox-1) + repmat(old_T, [1, 8]); + + % Make scale of new_M according to new_elem_size + % + new_M = diag([new_elem_size 1]); + + % Make translation so minimum vertex is moved to [1,1,1] + % + new_M(1:3,4) = round( min(XYZmm,[],2) ); + + % New dimensions will be the maximum vertices in XYZ direction (dim_vox) + % i.e. compute dim_vox via dim_mm = R*(dim_vox-1)+T + % where, dim_mm = round(max(XYZmm,[],2)); + % + new_dim = ceil(new_M(1:3,1:3) \ ( round(max(XYZmm,[],2))-new_M(1:3,4) )+1)'; + + % Initialize new_img with new_dim + % + new_img = zeros(new_dim(1:3)); + + % Mask out any changes from Z axis of transformed volume, since we + % will traverse it voxel by voxel below. We will only apply unit + % increment of mask_Z(3,4) to simulate the cursor movement + % + % i.e. we will use mask_Z * new_XYZvox to replace new_XYZvox + % + mask_Z = diag(ones(1,4)); + mask_Z(3,3) = 0; + + % It will be easier to do the interpolation if we invert the process + % by not traversing the original volume. Instead, we traverse the + % transformed volume, and backproject each voxel in the transformed + % volume back into the original volume. If the backprojected voxel + % in original volume is within its boundary, the intensity of that + % voxel can be used by the cursor location in the transformed volume. + % + % First, we traverse along Z axis of transformed volume voxel by voxel + % + for z = 1:new_dim(3) + + if verbose & ~mod(z,10) + fprintf('%.2f percent is done.\n', 100*z/new_dim(3)); + end + + % We need to find out the mapping from voxel in the transformed + % volume (new_XYZvox) to voxel in the original volume (old_XYZvox) + % + % The following equation works, because they all equal to XYZmm: + % new_R*(new_XYZvox-1) + new_T == old_R*(old_XYZvox-1) + old_T + % + % We can use modified new_M1 & old_M1 to substitute new_M & old_M + % new_M1 * new_XYZvox == old_M1 * old_XYZvox + % + % where: M1 = M; M1(:,4) = M(:,4) - sum(M(:,1:3),2); + % and: M(:,4) == [T; 1] == sum(M1,2) + % + % Therefore: old_XYZvox = old_M1 \ new_M1 * new_XYZvox; + % + % Since we are traverse Z axis, and new_XYZvox is replaced + % by mask_Z * new_XYZvox, the above formula can be rewritten + % as: old_XYZvox = old_M1 \ new_M1 * mask_Z * new_XYZvox; + % + % i.e. we find the mapping from new_XYZvox to old_XYZvox: + % M = old_M1 \ new_M1 * mask_Z; + % + % First, compute modified old_M1 & new_M1 + % + old_M1 = old_M; old_M1(:,4) = old_M(:,4) - sum(old_M(:,1:3),2); + new_M1 = new_M; new_M1(:,4) = new_M(:,4) - sum(new_M(:,1:3),2); + + % Then, apply unit increment of mask_Z(3,4) to simulate the + % cursor movement + % + mask_Z(3,4) = z; + + % Here is the mapping from new_XYZvox to old_XYZvox + % + M = old_M1 \ new_M1 * mask_Z; + + switch method + case 1 + new_img(:,:,z) = trilinear(old_img, new_dim, old_dim, M, bg); + case 2 + new_img(:,:,z) = nearest_neighbor(old_img, new_dim, old_dim, M, bg); + case 3 + new_img(:,:,z) = bresenham(old_img, new_dim, old_dim, M, bg); + end + + end; % for z + + if ndims(old_img) == 2 + new_M(3,:) = []; + new_M(:,3) = []; + end + + return; % affine + + +%-------------------------------------------------------------------- +function img_slice = trilinear(img, dim1, dim2, M, bg) + + img_slice = zeros(dim1(1:2)); + TINY = 5e-2; % tolerance + + % Dimension of transformed 3D volume + % + xdim1 = dim1(1); + ydim1 = dim1(2); + + % Dimension of original 3D volume + % + xdim2 = dim2(1); + ydim2 = dim2(2); + zdim2 = dim2(3); + + % initialize new_Y accumulation + % + Y2X = 0; + Y2Y = 0; + Y2Z = 0; + + for y = 1:ydim1 + + % increment of new_Y accumulation + % + Y2X = Y2X + M(1,2); % new_Y to old_X + Y2Y = Y2Y + M(2,2); % new_Y to old_Y + Y2Z = Y2Z + M(3,2); % new_Y to old_Z + + % backproject new_Y accumulation and translation to old_XYZ + % + old_X = Y2X + M(1,4); + old_Y = Y2Y + M(2,4); + old_Z = Y2Z + M(3,4); + + for x = 1:xdim1 + + % accumulate the increment of new_X, and apply it + % to the backprojected old_XYZ + % + old_X = M(1,1) + old_X ; + old_Y = M(2,1) + old_Y ; + old_Z = M(3,1) + old_Z ; + + % within boundary of original image + % + if ( old_X > 1-TINY & old_X < xdim2+TINY & ... + old_Y > 1-TINY & old_Y < ydim2+TINY & ... + old_Z > 1-TINY & old_Z < zdim2+TINY ) + + % Calculate distance of old_XYZ to its neighbors for + % weighted intensity average + % + dx = old_X - floor(old_X); + dy = old_Y - floor(old_Y); + dz = old_Z - floor(old_Z); + + x000 = floor(old_X); + x100 = x000 + 1; + + if floor(old_X) < 1 + x000 = 1; + x100 = x000; + elseif floor(old_X) > xdim2-1 + x000 = xdim2; + x100 = x000; + end + + x010 = x000; + x001 = x000; + x011 = x000; + + x110 = x100; + x101 = x100; + x111 = x100; + + y000 = floor(old_Y); + y010 = y000 + 1; + + if floor(old_Y) < 1 + y000 = 1; + y100 = y000; + elseif floor(old_Y) > ydim2-1 + y000 = ydim2; + y010 = y000; + end + + y100 = y000; + y001 = y000; + y101 = y000; + + y110 = y010; + y011 = y010; + y111 = y010; + + z000 = floor(old_Z); + z001 = z000 + 1; + + if floor(old_Z) < 1 + z000 = 1; + z001 = z000; + elseif floor(old_Z) > zdim2-1 + z000 = zdim2; + z001 = z000; + end + + z100 = z000; + z010 = z000; + z110 = z000; + + z101 = z001; + z011 = z001; + z111 = z001; + + x010 = x000; + x001 = x000; + x011 = x000; + + x110 = x100; + x101 = x100; + x111 = x100; + + v000 = double(img(x000, y000, z000)); + v010 = double(img(x010, y010, z010)); + v001 = double(img(x001, y001, z001)); + v011 = double(img(x011, y011, z011)); + + v100 = double(img(x100, y100, z100)); + v110 = double(img(x110, y110, z110)); + v101 = double(img(x101, y101, z101)); + v111 = double(img(x111, y111, z111)); + + img_slice(x,y) = v000*(1-dx)*(1-dy)*(1-dz) + ... + v010*(1-dx)*dy*(1-dz) + ... + v001*(1-dx)*(1-dy)*dz + ... + v011*(1-dx)*dy*dz + ... + v100*dx*(1-dy)*(1-dz) + ... + v110*dx*dy*(1-dz) + ... + v101*dx*(1-dy)*dz + ... + v111*dx*dy*dz; + + else + img_slice(x,y) = bg; + + end % if boundary + + end % for x + end % for y + + return; % trilinear + + +%-------------------------------------------------------------------- +function img_slice = nearest_neighbor(img, dim1, dim2, M, bg) + + img_slice = zeros(dim1(1:2)); + + % Dimension of transformed 3D volume + % + xdim1 = dim1(1); + ydim1 = dim1(2); + + % Dimension of original 3D volume + % + xdim2 = dim2(1); + ydim2 = dim2(2); + zdim2 = dim2(3); + + % initialize new_Y accumulation + % + Y2X = 0; + Y2Y = 0; + Y2Z = 0; + + for y = 1:ydim1 + + % increment of new_Y accumulation + % + Y2X = Y2X + M(1,2); % new_Y to old_X + Y2Y = Y2Y + M(2,2); % new_Y to old_Y + Y2Z = Y2Z + M(3,2); % new_Y to old_Z + + % backproject new_Y accumulation and translation to old_XYZ + % + old_X = Y2X + M(1,4); + old_Y = Y2Y + M(2,4); + old_Z = Y2Z + M(3,4); + + for x = 1:xdim1 + + % accumulate the increment of new_X and apply it + % to the backprojected old_XYZ + % + old_X = M(1,1) + old_X ; + old_Y = M(2,1) + old_Y ; + old_Z = M(3,1) + old_Z ; + + xi = round(old_X); + yi = round(old_Y); + zi = round(old_Z); + + % within boundary of original image + % + if ( xi >= 1 & xi <= xdim2 & ... + yi >= 1 & yi <= ydim2 & ... + zi >= 1 & zi <= zdim2 ) + + img_slice(x,y) = img(xi,yi,zi); + + else + img_slice(x,y) = bg; + + end % if boundary + + end % for x + end % for y + + return; % nearest_neighbor + + +%-------------------------------------------------------------------- +function img_slice = bresenham(img, dim1, dim2, M, bg) + + img_slice = zeros(dim1(1:2)); + + % Dimension of transformed 3D volume + % + xdim1 = dim1(1); + ydim1 = dim1(2); + + % Dimension of original 3D volume + % + xdim2 = dim2(1); + ydim2 = dim2(2); + zdim2 = dim2(3); + + for y = 1:ydim1 + + start_old_XYZ = round(M*[0 y 0 1]'); + end_old_XYZ = round(M*[xdim1 y 0 1]'); + + [X Y Z] = bresenham_line3d(start_old_XYZ, end_old_XYZ); + + % line error correction + % +% del = end_old_XYZ - start_old_XYZ; + % del_dom = max(del); + % idx_dom = find(del==del_dom); + % idx_dom = idx_dom(1); + % idx_other = [1 2 3]; + % idx_other(idx_dom) = []; + %del_x1 = del(idx_other(1)); +% del_x2 = del(idx_other(2)); + % line_slope = sqrt((del_x1/del_dom)^2 + (del_x2/del_dom)^2 + 1); + % line_error = line_slope - 1; +% line error correction removed because it is too slow + + for x = 1:xdim1 + + % rescale ratio + % + i = round(x * length(X) / xdim1); + + if i < 1 + i = 1; + elseif i > length(X) + i = length(X); + end + + xi = X(i); + yi = Y(i); + zi = Z(i); + + % within boundary of the old XYZ space + % + if ( xi >= 1 & xi <= xdim2 & ... + yi >= 1 & yi <= ydim2 & ... + zi >= 1 & zi <= zdim2 ) + + img_slice(x,y) = img(xi,yi,zi); + +% if line_error > 1 + % x = x + 1; + +% if x <= xdim1 + % img_slice(x,y) = img(xi,yi,zi); + % line_error = line_slope - 1; + % end + % end % if line_error +% line error correction removed because it is too slow + + else + img_slice(x,y) = bg; + + end % if boundary + + end % for x + end % for y + + return; % bresenham + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m new file mode 100755 index 00000000..02cd30b6 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m @@ -0,0 +1,94 @@ +%BIPOLAR returns an M-by-3 matrix containing a blue-red colormap, in +% in which red stands for positive, blue stands for negative, +% and white stands for 0. +% +% Usage: cmap = bipolar(M, lo, hi, contrast); or cmap = bipolar; +% +% cmap: output M-by-3 matrix for BIPOLAR colormap. +% M: number of shades in the colormap. By default, it is the +% same length as the current colormap. +% lo: the lowest value to represent. +% hi: the highest value to represent. +% +% Inspired from the LORETA PASCAL program: +% http://www.unizh.ch/keyinst/NewLORETA +% +% jimmy@rotman-baycrest.on.ca +% +%---------------------------------------------------------------- +function cmap = bipolar(M, lo, hi, contrast) + + if ~exist('contrast','var') + contrast = 128; + end + + if ~exist('lo','var') + lo = -1; + end + + if ~exist('hi','var') + hi = 1; + end + + if ~exist('M','var') + cmap = colormap; + M = size(cmap,1); + end + + steepness = 10 ^ (1 - (contrast-1)/127); + pos_infs = 1e-99; + neg_infs = -1e-99; + + doubleredc = []; + doublebluec = []; + + if lo >= 0 % all positive + + if lo == 0 + lo = pos_infs; + end + + for i=linspace(hi/M, hi, M) + t = exp(log(i/hi)*steepness); + doubleredc = [doubleredc; [(1-t)+t,(1-t)+0,(1-t)+0]]; + end + + cmap = doubleredc; + + elseif hi <= 0 % all negative + + if hi == 0 + hi = neg_infs; + end + + for i=linspace(abs(lo)/M, abs(lo), M) + t = exp(log(i/abs(lo))*steepness); + doublebluec = [doublebluec; [(1-t)+0,(1-t)+0,(1-t)+t]]; + end + + cmap = flipud(doublebluec); + + else + + if hi > abs(lo) + maxc = hi; + else + maxc = abs(lo); + end + + for i=linspace(maxc/M, hi, round(M*hi/(hi-lo))) + t = exp(log(i/maxc)*steepness); + doubleredc = [doubleredc; [(1-t)+t,(1-t)+0,(1-t)+0]]; + end + + for i=linspace(maxc/M, abs(lo), round(M*abs(lo)/(hi-lo))) + t = exp(log(i/maxc)*steepness); + doublebluec = [doublebluec; [(1-t)+0,(1-t)+0,(1-t)+t]]; + end + + cmap = [flipud(doublebluec); doubleredc]; + + end + + return; % bipolar + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m new file mode 100755 index 00000000..e1e9dc70 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m @@ -0,0 +1,189 @@ +% Generate X Y Z coordinates of a 3D Bresenham's line between +% two given points. +% +% A very useful application of this algorithm can be found in the +% implementation of Fischer's Bresenham interpolation method in my +% another program that can rotate three dimensional image volume +% with an affine matrix: +% http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=21080 +% +% Usage: [X Y Z] = bresenham_line3d(P1, P2, [precision]); +% +% P1 - vector for Point1, where P1 = [x1 y1 z1] +% +% P2 - vector for Point2, where P2 = [x2 y2 z2] +% +% precision (optional) - Although according to Bresenham's line +% algorithm, point coordinates x1 y1 z1 and x2 y2 z2 should +% be integer numbers, this program extends its limit to all +% real numbers. If any of them are floating numbers, you +% should specify how many digits of decimal that you would +% like to preserve. Be aware that the length of output X Y +% Z coordinates will increase in 10 times for each decimal +% digit that you want to preserve. By default, the precision +% is 0, which means that they will be rounded to the nearest +% integer. +% +% X - a set of x coordinates on Bresenham's line +% +% Y - a set of y coordinates on Bresenham's line +% +% Z - a set of z coordinates on Bresenham's line +% +% Therefore, all points in XYZ set (i.e. P(i) = [X(i) Y(i) Z(i)]) +% will constitute the Bresenham's line between P1 and P1. +% +% Example: +% P1 = [12 37 6]; P2 = [46 3 35]; +% [X Y Z] = bresenham_line3d(P1, P2); +% figure; plot3(X,Y,Z,'s','markerface','b'); +% +% This program is ported to MATLAB from: +% +% B.Pendleton. line3d - 3D Bresenham's (a 3D line drawing algorithm) +% ftp://ftp.isc.org/pub/usenet/comp.sources.unix/volume26/line3d, 1992 +% +% Which is also referenced by: +% +% Fischer, J., A. del Rio (2004). A Fast Method for Applying Rigid +% Transformations to Volume Data, WSCG2004 Conference. +% http://wscg.zcu.cz/wscg2004/Papers_2004_Short/M19.pdf +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [X,Y,Z] = bresenham_line3d(P1, P2, precision) + + if ~exist('precision','var') | isempty(precision) | round(precision) == 0 + precision = 0; + P1 = round(P1); + P2 = round(P2); + else + precision = round(precision); + P1 = round(P1*(10^precision)); + P2 = round(P2*(10^precision)); + end + + d = max(abs(P2-P1)+1); + X = zeros(1, d); + Y = zeros(1, d); + Z = zeros(1, d); + + x1 = P1(1); + y1 = P1(2); + z1 = P1(3); + + x2 = P2(1); + y2 = P2(2); + z2 = P2(3); + + dx = x2 - x1; + dy = y2 - y1; + dz = z2 - z1; + + ax = abs(dx)*2; + ay = abs(dy)*2; + az = abs(dz)*2; + + sx = sign(dx); + sy = sign(dy); + sz = sign(dz); + + x = x1; + y = y1; + z = z1; + idx = 1; + + if(ax>=max(ay,az)) % x dominant + yd = ay - ax/2; + zd = az - ax/2; + + while(1) + X(idx) = x; + Y(idx) = y; + Z(idx) = z; + idx = idx + 1; + + if(x == x2) % end + break; + end + + if(yd >= 0) % move along y + y = y + sy; + yd = yd - ax; + end + + if(zd >= 0) % move along z + z = z + sz; + zd = zd - ax; + end + + x = x + sx; % move along x + yd = yd + ay; + zd = zd + az; + end + elseif(ay>=max(ax,az)) % y dominant + xd = ax - ay/2; + zd = az - ay/2; + + while(1) + X(idx) = x; + Y(idx) = y; + Z(idx) = z; + idx = idx + 1; + + if(y == y2) % end + break; + end + + if(xd >= 0) % move along x + x = x + sx; + xd = xd - ay; + end + + if(zd >= 0) % move along z + z = z + sz; + zd = zd - ay; + end + + y = y + sy; % move along y + xd = xd + ax; + zd = zd + az; + end + elseif(az>=max(ax,ay)) % z dominant + xd = ax - az/2; + yd = ay - az/2; + + while(1) + X(idx) = x; + Y(idx) = y; + Z(idx) = z; + idx = idx + 1; + + if(z == z2) % end + break; + end + + if(xd >= 0) % move along x + x = x + sx; + xd = xd - az; + end + + if(yd >= 0) % move along y + y = y + sy; + yd = yd - az; + end + + z = z + sz; % move along z + xd = xd + ax; + yd = yd + ay; + end + end + + if precision ~= 0 + X = X/(10^precision); + Y = Y/(10^precision); + Z = Z/(10^precision); + end + + return; % bresenham_line3d + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m new file mode 100755 index 00000000..126524ee --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m @@ -0,0 +1,115 @@ +% CLIP_NII: Clip the NIfTI volume from any of the 6 sides +% +% Usage: nii = clip_nii(nii, [option]) +% +% Inputs: +% +% nii - NIfTI volume. +% +% option - struct instructing how many voxel to be cut from which side. +% +% option.cut_from_L = ( number of voxel ) +% option.cut_from_R = ( number of voxel ) +% option.cut_from_P = ( number of voxel ) +% option.cut_from_A = ( number of voxel ) +% option.cut_from_I = ( number of voxel ) +% option.cut_from_S = ( number of voxel ) +% +% Options description in detail: +% ============================== +% +% cut_from_L: Number of voxels from Left side will be clipped. +% +% cut_from_R: Number of voxels from Right side will be clipped. +% +% cut_from_P: Number of voxels from Posterior side will be clipped. +% +% cut_from_A: Number of voxels from Anterior side will be clipped. +% +% cut_from_I: Number of voxels from Inferior side will be clipped. +% +% cut_from_S: Number of voxels from Superior side will be clipped. +% +% NIfTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = clip_nii(nii, opt) + + dims = abs(nii.hdr.dime.dim(2:4)); + origin = abs(nii.hdr.hist.originator(1:3)); + + if isempty(origin) | all(origin == 0) % according to SPM + origin = round((dims+1)/2); + end + + cut_from_L = 0; + cut_from_R = 0; + cut_from_P = 0; + cut_from_A = 0; + cut_from_I = 0; + cut_from_S = 0; + + if nargin > 1 & ~isempty(opt) + if ~isstruct(opt) + error('option argument should be a struct'); + end + + if isfield(opt,'cut_from_L') + cut_from_L = round(opt.cut_from_L); + + if cut_from_L >= origin(1) | cut_from_L < 0 + error('cut_from_L cannot be negative or cut beyond originator'); + end + end + + if isfield(opt,'cut_from_P') + cut_from_P = round(opt.cut_from_P); + + if cut_from_P >= origin(2) | cut_from_P < 0 + error('cut_from_P cannot be negative or cut beyond originator'); + end + end + + if isfield(opt,'cut_from_I') + cut_from_I = round(opt.cut_from_I); + + if cut_from_I >= origin(3) | cut_from_I < 0 + error('cut_from_I cannot be negative or cut beyond originator'); + end + end + + if isfield(opt,'cut_from_R') + cut_from_R = round(opt.cut_from_R); + + if cut_from_R > dims(1)-origin(1) | cut_from_R < 0 + error('cut_from_R cannot be negative or cut beyond originator'); + end + end + + if isfield(opt,'cut_from_A') + cut_from_A = round(opt.cut_from_A); + + if cut_from_A > dims(2)-origin(2) | cut_from_A < 0 + error('cut_from_A cannot be negative or cut beyond originator'); + end + end + + if isfield(opt,'cut_from_S') + cut_from_S = round(opt.cut_from_S); + + if cut_from_S > dims(3)-origin(3) | cut_from_S < 0 + error('cut_from_S cannot be negative or cut beyond originator'); + end + end + end + + nii = make_nii(nii.img( (cut_from_L+1) : (dims(1)-cut_from_R), ... + (cut_from_P+1) : (dims(2)-cut_from_A), ... + (cut_from_I+1) : (dims(3)-cut_from_S), ... + :,:,:,:,:), nii.hdr.dime.pixdim(2:4), ... + [origin(1)-cut_from_L origin(2)-cut_from_P origin(3)-cut_from_I], ... + nii.hdr.dime.datatype, nii.hdr.hist.descrip); + + return; + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m new file mode 100755 index 00000000..14f1bc6e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m @@ -0,0 +1,260 @@ +% Collapse multiple single-scan NIFTI files into a multiple-scan NIFTI file +% +% Usage: collapse_nii_scan(scan_file_pattern, [collapsed_fileprefix], [scan_file_folder]) +% +% Here, scan_file_pattern should look like: 'myscan_0*.img' +% If collapsed_fileprefix is omit, 'multi_scan' will be used +% If scan_file_folder is omit, current file folder will be used +% +% The order of volumes in the collapsed file will be the order of +% corresponding filenames for those selected scan files. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function collapse_nii_scan(scan_pattern, fileprefix, scan_path) + + if ~exist('fileprefix','var') + fileprefix = 'multi_scan'; + else + [tmp fileprefix] = fileparts(fileprefix); + end + + if ~exist('scan_path','var'), scan_path = pwd; end + pnfn = fullfile(scan_path, scan_pattern); + + file_lst = dir(pnfn); + flist = {file_lst.name}; + flist = flist(:); + filename = flist{1}; + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + end + else + if ~strcmp(filename(end-3:end), '.img') & ... + ~strcmp(filename(end-3:end), '.hdr') & ... + ~strcmp(filename(end-3:end), '.nii') + + error('Please check filename.'); + end + end + + nii = load_untouch_nii(fullfile(scan_path,filename)); + nii.hdr.dime.dim(5) = length(flist); + + if nii.hdr.dime.dim(1) < 4 + nii.hdr.dime.dim(1) = 4; + end + + hdr = nii.hdr; + filetype = nii.filetype; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + elseif filetype == 1 + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch0_nii_hdr(hdr, fid); + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if skip_bytes + fwrite(fid, zeros(1,skip_bytes), 'uint8'); + end + + glmax = -inf; + glmin = inf; + + for i = 1:length(flist) + nii = load_untouch_nii(fullfile(scan_path,flist{i})); + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if nii.hdr.dime.glmax > glmax + glmax = nii.hdr.dime.glmax; + end + + if nii.hdr.dime.glmin < glmin + glmin = nii.hdr.dime.glmin; + end + + fwrite(fid, nii.img, precision); + end + + hdr.dime.glmax = round(glmax); + hdr.dime.glmin = round(glmin); + + if filetype == 2 + fseek(fid, 140, 'bof'); + fwrite(fid, hdr.dime.glmax, 'int32'); + fwrite(fid, hdr.dime.glmin, 'int32'); + elseif filetype == 1 + fid2 = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid2 < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch_nii_hdr(hdr, fid2); + + if ~isempty(ext) + save_nii_ext(ext, fid2); + end + + fclose(fid2); + else + fid2 = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid2 < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch0_nii_hdr(hdr, fid2); + + fclose(fid2); + end + + fclose(fid); + + % gzip output file if requested + % + if exist('gzFile', 'var') + if filetype == 1 + gzip([fileprefix, '.img']); + delete([fileprefix, '.img']); + gzip([fileprefix, '.hdr']); + delete([fileprefix, '.hdr']); + elseif filetype == 2 + gzip([fileprefix, '.nii']); + delete([fileprefix, '.nii']); + end; + end; + + return; % collapse_nii_scan + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m new file mode 100755 index 00000000..f95a3e96 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m @@ -0,0 +1,260 @@ +% Collapse multiple single-scan NIFTI files into a multiple-scan NIFTI file +% +% Usage: collapse_nii_scan(scan_file_pattern, [collapsed_fileprefix], [scan_file_folder]) +% +% Here, scan_file_pattern should look like: 'myscan_0*.img' +% If collapsed_fileprefix is omit, 'multi_scan' will be used +% If scan_file_folder is omit, current file folder will be used +% +% The order of volumes in the collapsed file will be the order of +% corresponding filenames for those selected scan files. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function collapse_nii_scan2(flist, fileprefix, scan_path) +% +% if ~exist('fileprefix','var') +% fileprefix = 'multi_scan'; +% else +% [tmp fileprefix] = fileparts(fileprefix); +% end + +% if ~exist('scan_path','var'), scan_path = pwd; end +% pnfn = fullfile(scan_path, scan_pattern); + +% file_lst = dir(pnfn); +% flist = {file_lst.name}; +% flist = flist(:); + filename = flist{1}; + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + end + else + if ~strcmp(filename(end-3:end), '.img') & ... + ~strcmp(filename(end-3:end), '.hdr') & ... + ~strcmp(filename(end-3:end), '.nii') + + error('Please check filename.'); + end + end + + nii = load_untouch_nii(fullfile(scan_path,filename)); + nii.hdr.dime.dim(5) = length(flist); + + if nii.hdr.dime.dim(1) < 4 + nii.hdr.dime.dim(1) = 4; + end + + hdr = nii.hdr; + filetype = nii.filetype; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + elseif filetype == 1 + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch0_nii_hdr(hdr, fid); + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if skip_bytes + fwrite(fid, zeros(1,skip_bytes), 'uint8'); + end + + glmax = -inf; + glmin = inf; + + for i = 1:length(flist) + nii = load_untouch_nii(fullfile(scan_path,flist{i})); + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if nii.hdr.dime.glmax > glmax + glmax = nii.hdr.dime.glmax; + end + + if nii.hdr.dime.glmin < glmin + glmin = nii.hdr.dime.glmin; + end + + fwrite(fid, nii.img, precision); + end + + hdr.dime.glmax = round(glmax); + hdr.dime.glmin = round(glmin); + + if filetype == 2 + fseek(fid, 140, 'bof'); + fwrite(fid, hdr.dime.glmax, 'int32'); + fwrite(fid, hdr.dime.glmin, 'int32'); + elseif filetype == 1 + fid2 = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid2 < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch_nii_hdr(hdr, fid2); + + if ~isempty(ext) + save_nii_ext(ext, fid2); + end + + fclose(fid2); + else + fid2 = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid2 < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch0_nii_hdr(hdr, fid2); + + fclose(fid2); + end + + fclose(fid); + + % gzip output file if requested + % +% if exist('gzFile', 'var') +% if filetype == 1 +% gzip([fileprefix, '.img']); +% delete([fileprefix, '.img']); +% gzip([fileprefix, '.hdr']); +% delete([fileprefix, '.hdr']); +% elseif filetype == 2 +% gzip([fileprefix, '.nii']); +% delete([fileprefix, '.nii']); +% end; +% end; + + return; % collapse_nii_scan + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt new file mode 100755 index 00000000..1111650d --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt @@ -0,0 +1,130 @@ + +- Examples to load, make and save a nii struct: + + To load Analyze data or NIFTI data to a structure: + + nii = load_nii(NIFTI_file_name, [img_idx], [old_RGB24]); + + img_idx is a numerical array of image indices along the temporal + axis, which is only available in NIFTI data. After you specify + img_idx, only those images indexed by img_idx will be loaded. If + there is no img_idx or img_idx is empty, all available images + will be loaded. + + For RGB image, most people use RGB triple sequentially for each + voxel, like [R1 G1 B1 R2 G2 B2 ...]. However, some program like + Analyze 6.0 developed by AnalyzeDirect uses old RGB24, in a way + like [R1 R2 ... G1 G2 ... B1 B2 ...] for each slices. In this + case, you can set old_RGB24 flag to 1 and load data correctly: + + nii = load_nii(NIFTI_file_name, [], 1); + + To get a total number of images along the temporal axis: + + num_scan = get_nii_frame(NIFTI_file_name); + + You can also load the header extension if it exists: + + nii.ext = load_nii_ext(NIFTI_file_name); + + You can just load the Analyze or NIFTI header: + (header contains: hk, dime, and hist) + + hdr = load_nii_hdr(NIFTI_file_name); + + You can also save the structure to a new file: + (header extension will be saved if there is nii.ext structure) + + save_nii(nii, NIFTI_file_name); + + To make the structure from any 3D (or 4D) data: + + img = rand(91,109,91); or + img = rand(64,64,21,18); + nii = make_nii(img [, voxel_size, origin, datatype] ); + + Use "help load_nii", "help save_nii", "help make_nii" etc. + to get more detail information. + + +- Examples to plot a nii struct: + (More detail descriptions are available on top of "view_nii.m") + + Simple way to plot a nii struct: + + view_nii(nii); + + The default colormap will use the Gray if all data values are + non-negative; otherwise, the default colormap will use BiPolar. + You can choose other colormap, including customized colormap + from panel. + + To imbed the plot into your existing figure: + + h = gcf; + opt.command = 'init'; + opt.setarea = [0.3 0.1 0.6 0.8]; + view_nii(h, nii, opt); + + To add a colorbar: + + opt.usecolorbar = 1; + view_nii(gcf, opt); + + Here, opt.command is implicitly set to 'update'. + + To display in real aspect ratio: + + opt.usestretch = 0; + view_nii(gcf, opt); + + If you want the data value to be directly used as the index + of colormap, instead of scale to the whole colormap: + + opt.useimagesc = 0; + view_nii(gcf, opt); + + If you modified the data value without changing the dimension, + voxel_size, and origin, you can update the display by: + + opt.command = 'updateimg'; + view_nii(gcf, nii.img, opt); + + If the data is completely different, display can be updated by: + + opt.command = 'updatenii'; + view_nii(gcf, nii, opt); + + This is an example to plot EEG source imaging on top of T1 background: + 1. download overlay.zip and unzip it from: + http://www.rotman-baycrest.on.ca/~jimmy/NIFTI/overlay.zip + 2. T1 = load_nii('T1.nii'); + 3. EEG = load_nii('EEG.nii'); + 4. option.setvalue.idx = find(EEG.img); + 5. option.setvalue.val = EEG.img(option.setvalue.idx); + 6. option.useinterp = 1; + 7. option.setviewpoint = [62 48 46]; + 8. view_nii(T1, option); + + +- Contrast and Brightness are available under Gray and Bipolar colormap: + + Increase contrast in Gray colormap will make high end values + more distinguishable by sacrificing the low end values; The + minimum contrast (default) will display the whole range. + + Increase or decrease contrast in BiPolar colormap will shift + the distinguishable position for both positive and negative + values. + + Increase or decrease brightness in Gray colormap will shift + the distinguishable position. + + Increase or decrease brightness in BiPolar colormap will make + both positive and negative values more distinguishable. + + +- Required files: + + All files in this package. + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m new file mode 100755 index 00000000..c2e40630 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m @@ -0,0 +1,48 @@ +% Expand a multiple-scan NIFTI file into multiple single-scan NIFTI files +% +% Usage: expand_nii_scan(multi_scan_filename, [img_idx], [path_to_save]) +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function expand_nii_scan(filename, img_idx, newpath) + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + end + end + + if ~exist('newpath','var') | isempty(newpath), newpath = pwd; end + if ~exist('img_idx','var') | isempty(img_idx), img_idx = 1:get_nii_frame(filename); end + + for i=img_idx + nii_i = load_untouch_nii(filename, i); + + fn = [nii_i.fileprefix '_' sprintf('%04d',i)]; + pnfn = fullfile(newpath, fn); + + if exist('gzFile', 'var') + pnfn = [pnfn '.nii.gz']; + end + + save_untouch_nii(nii_i, pnfn); + end + + return; % expand_nii_scan + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m new file mode 100755 index 00000000..a2b5291d --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m @@ -0,0 +1,255 @@ +% Decode extra NIFTI header information into hdr.extra +% +% Usage: hdr = extra_nii_hdr(hdr) +% +% hdr can be obtained from load_nii_hdr +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function hdr = extra_nii_hdr(hdr) + + switch hdr.dime.datatype + case 1 + extra.NIFTI_DATATYPES = 'DT_BINARY'; + case 2 + extra.NIFTI_DATATYPES = 'DT_UINT8'; + case 4 + extra.NIFTI_DATATYPES = 'DT_INT16'; + case 8 + extra.NIFTI_DATATYPES = 'DT_INT32'; + case 16 + extra.NIFTI_DATATYPES = 'DT_FLOAT32'; + case 32 + extra.NIFTI_DATATYPES = 'DT_COMPLEX64'; + case 64 + extra.NIFTI_DATATYPES = 'DT_FLOAT64'; + case 128 + extra.NIFTI_DATATYPES = 'DT_RGB24'; + case 256 + extra.NIFTI_DATATYPES = 'DT_INT8'; + case 512 + extra.NIFTI_DATATYPES = 'DT_UINT16'; + case 768 + extra.NIFTI_DATATYPES = 'DT_UINT32'; + case 1024 + extra.NIFTI_DATATYPES = 'DT_INT64'; + case 1280 + extra.NIFTI_DATATYPES = 'DT_UINT64'; + case 1536 + extra.NIFTI_DATATYPES = 'DT_FLOAT128'; + case 1792 + extra.NIFTI_DATATYPES = 'DT_COMPLEX128'; + case 2048 + extra.NIFTI_DATATYPES = 'DT_COMPLEX256'; + otherwise + extra.NIFTI_DATATYPES = 'DT_UNKNOWN'; + end + + switch hdr.dime.intent_code + case 2 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_CORREL'; + case 3 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_TTEST'; + case 4 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_FTEST'; + case 5 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_ZSCORE'; + case 6 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_CHISQ'; + case 7 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_BETA'; + case 8 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_BINOM'; + case 9 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_GAMMA'; + case 10 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_POISSON'; + case 11 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_NORMAL'; + case 12 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_FTEST_NONC'; + case 13 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_CHISQ_NONC'; + case 14 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_LOGISTIC'; + case 15 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_LAPLACE'; + case 16 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_UNIFORM'; + case 17 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_TTEST_NONC'; + case 18 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_WEIBULL'; + case 19 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_CHI'; + case 20 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_INVGAUSS'; + case 21 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_EXTVAL'; + case 22 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_PVAL'; + case 23 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_LOGPVAL'; + case 24 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_LOG10PVAL'; + case 1001 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_ESTIMATE'; + case 1002 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_LABEL'; + case 1003 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_NEURONAME'; + case 1004 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_GENMATRIX'; + case 1005 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_SYMMATRIX'; + case 1006 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_DISPVECT'; + case 1007 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_VECTOR'; + case 1008 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_POINTSET'; + case 1009 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_TRIANGLE'; + case 1010 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_QUATERNION'; + case 1011 + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_DIMLESS'; + otherwise + extra.NIFTI_INTENT_CODES = 'NIFTI_INTENT_NONE'; + end + + extra.NIFTI_INTENT_NAMES = hdr.hist.intent_name; + + if hdr.hist.sform_code > 0 + switch hdr.hist.sform_code + case 1 + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_SCANNER_ANAT'; + case 2 + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_ALIGNED_ANAT'; + case 3 + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_TALAIRACH'; + case 4 + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_MNI_152'; + otherwise + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + end + + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + elseif hdr.hist.qform_code > 0 + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + + switch hdr.hist.qform_code + case 1 + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_SCANNER_ANAT'; + case 2 + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_ALIGNED_ANAT'; + case 3 + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_TALAIRACH'; + case 4 + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_MNI_152'; + otherwise + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + end + else + extra.NIFTI_SFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + extra.NIFTI_QFORM_CODES = 'NIFTI_XFORM_UNKNOWN'; + end + + switch bitand(hdr.dime.xyzt_units, 7) % mask with 0x07 + case 1 + extra.NIFTI_SPACE_UNIT = 'NIFTI_UNITS_METER'; + case 2 + extra.NIFTI_SPACE_UNIT = 'NIFTI_UNITS_MM'; % millimeter + case 3 + extra.NIFTI_SPACE_UNIT = 'NIFTI_UNITS_MICRO'; + otherwise + extra.NIFTI_SPACE_UNIT = 'NIFTI_UNITS_UNKNOWN'; + end + + switch bitand(hdr.dime.xyzt_units, 56) % mask with 0x38 + case 8 + extra.NIFTI_TIME_UNIT = 'NIFTI_UNITS_SEC'; + case 16 + extra.NIFTI_TIME_UNIT = 'NIFTI_UNITS_MSEC'; + case 24 + extra.NIFTI_TIME_UNIT = 'NIFTI_UNITS_USEC'; % microsecond + otherwise + extra.NIFTI_TIME_UNIT = 'NIFTI_UNITS_UNKNOWN'; + end + + switch hdr.dime.xyzt_units + case 32 + extra.NIFTI_SPECTRAL_UNIT = 'NIFTI_UNITS_HZ'; + case 40 + extra.NIFTI_SPECTRAL_UNIT = 'NIFTI_UNITS_PPM'; % part per million + case 48 + extra.NIFTI_SPECTRAL_UNIT = 'NIFTI_UNITS_RADS'; % radians per second + otherwise + extra.NIFTI_SPECTRAL_UNIT = 'NIFTI_UNITS_UNKNOWN'; + end + + % MRI-specific spatial and temporal information + % + dim_info = hdr.hk.dim_info; + extra.NIFTI_FREQ_DIM = bitand(dim_info, 3); + extra.NIFTI_PHASE_DIM = bitand(bitshift(dim_info, -2), 3); + extra.NIFTI_SLICE_DIM = bitand(bitshift(dim_info, -4), 3); + + % Check slice code + % + switch hdr.dime.slice_code + case 1 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_SEQ_INC'; % sequential increasing + case 2 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_SEQ_DEC'; % sequential decreasing + case 3 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_ALT_INC'; % alternating increasing + case 4 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_ALT_DEC'; % alternating decreasing + case 5 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_ALT_INC2'; % ALT_INC # 2 + case 6 + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_ALT_DEC2'; % ALT_DEC # 2 + otherwise + extra.NIFTI_SLICE_ORDER = 'NIFTI_SLICE_UNKNOWN'; + end + + % Check NIFTI version + % + if ~isempty(hdr.hist.magic) & strcmp(hdr.hist.magic(1),'n') & ... + ( strcmp(hdr.hist.magic(2),'i') | strcmp(hdr.hist.magic(2),'+') ) & ... + str2num(hdr.hist.magic(3)) >= 1 & str2num(hdr.hist.magic(3)) <= 9 + + extra.NIFTI_VERSION = str2num(hdr.hist.magic(3)); + else + extra.NIFTI_VERSION = 0; + end + + % Check if data stored in the same file (*.nii) or separate + % files (*.hdr/*.img) + % + if isempty(hdr.hist.magic) + extra.NIFTI_ONEFILE = 0; + else + extra.NIFTI_ONEFILE = strcmp(hdr.hist.magic(2), '+'); + end + + % Swap has been taken care of by checking whether sizeof_hdr is + % 348 (machine is 'ieee-le' or 'ieee-be' etc) + % + % extra.NIFTI_NEEDS_SWAP = (hdr.dime.dim(1) < 0 | hdr.dime.dim(1) > 7); + + % Check NIFTI header struct contains a 5th (vector) dimension + % + if hdr.dime.dim(1) > 4 & hdr.dime.dim(6) > 1 + extra.NIFTI_5TH_DIM = hdr.dime.dim(6); + else + extra.NIFTI_5TH_DIM = 0; + end + + hdr.extra = extra; + + return; % extra_nii_hdr + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m new file mode 100755 index 00000000..fc78e78c --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m @@ -0,0 +1,84 @@ +% When you load any ANALYZE or NIfTI file with 'load_nii.m', and view +% it with 'view_nii.m', you may find that the image is L-R flipped. +% This is because of the confusion of radiological and neurological +% convention in the medical image before NIfTI format is adopted. You +% can find more details from: +% +% http://www.rotman-baycrest.on.ca/~jimmy/UseANALYZE.htm +% +% Sometime, people even want to convert RAS (standard orientation) back +% to LAS orientation to satisfy the legend programs or processes. This +% program is only written for those purpose. So PLEASE BE VERY CAUTIOUS +% WHEN USING THIS 'FLIP_LR.M' PROGRAM. +% +% With 'flip_lr.m', you can convert any ANALYZE or NIfTI (no matter +% 3D or 4D) file to a flipped NIfTI file. This is implemented simply +% by flipping the affine matrix in the NIfTI header. Since the L-R +% orientation is determined there, so the image will be flipped. +% +% Usage: flip_lr(original_fn, flipped_fn, [old_RGB],[tolerance],[preferredForm]) +% +% original_fn - filename of the original ANALYZE or NIfTI (3D or 4D) file +% +% flipped_fn - filename of the L-R flipped NIfTI file +% +% old_RGB (optional) - a scale number to tell difference of new RGB24 +% from old RGB24. New RGB24 uses RGB triple sequentially for each +% voxel, like [R1 G1 B1 R2 G2 B2 ...]. Analyze 6.0 from AnalyzeDirect +% uses old RGB24, in a way like [R1 R2 ... G1 G2 ... B1 B2 ...] for +% each slices. If the image that you view is garbled, try to set +% old_RGB variable to 1 and try again, because it could be in +% old RGB24. It will be set to 0, if it is default or empty. +% +% tolerance (optional) - distortion allowed for non-orthogonal rotation +% or shearing in NIfTI affine matrix. It will be set to 0.1 (10%), +% if it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% Example: flip_lr('avg152T1_LR_nifti.nii', 'flipped_lr.nii'); +% flip_lr('avg152T1_RL_nifti.nii', 'flipped_rl.nii'); +% +% You will find that 'avg152T1_LR_nifti.nii' and 'avg152T1_RL_nifti.nii' +% are the same, and 'flipped_lr.nii' and 'flipped_rl.nii' are also the +% the same, but they are L-R flipped from 'avg152T1_*'. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function flip_lr(original_fn, flipped_fn, old_RGB, tolerance, preferredForm) + + if ~exist('original_fn','var') | ~exist('flipped_fn','var') + error('Usage: flip_lr(original_fn, flipped_fn, [old_RGB],[tolerance])'); + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + nii = load_nii(original_fn, [], [], [], [], old_RGB, tolerance, preferredForm); + M = diag(nii.hdr.dime.pixdim(2:5)); + M(1:3,4) = -M(1:3,1:3)*(nii.hdr.hist.originator(1:3)-1)'; + M(1,:) = -1*M(1,:); + nii.hdr.hist.sform_code = 1; + nii.hdr.hist.srow_x = M(1,:); + nii.hdr.hist.srow_y = M(2,:); + nii.hdr.hist.srow_z = M(3,:); + save_nii(nii, flipped_fn); + + return; % flip_lr + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m new file mode 100755 index 00000000..9ebdc822 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m @@ -0,0 +1,164 @@ +% Return time frame of a NIFTI dataset. Support both *.nii and +% *.hdr/*.img file extension. If file extension is not provided, +% *.hdr/*.img will be used as default. +% +% It is a lightweighted "load_nii_hdr", and is equivalent to +% hdr.dime.dim(5) +% +% Usage: [ total_scan ] = get_nii_frame(filename) +% +% filename - NIFTI file name. +% +% Returned values: +% +% total_scan - total number of image scans for the time frame +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [ total_scan ] = get_nii_frame(filename) + + if ~exist('filename','var'), + error('Usage: [ total_scan ] = get_nii_frame(filename)'); + end + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + fileprefix = filename; + machine = 'ieee-le'; + new_ext = 0; + + if findstr('.nii',fileprefix) & strcmp(fileprefix(end-3:end), '.nii') + new_ext = 1; + fileprefix(end-3:end)=''; + end + + if findstr('.hdr',fileprefix) & strcmp(fileprefix(end-3:end), '.hdr') + fileprefix(end-3:end)=''; + end + + if findstr('.img',fileprefix) & strcmp(fileprefix(end-3:end), '.img') + fileprefix(end-3:end)=''; + end + + if new_ext + fn = sprintf('%s.nii',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', fileprefix); + error(msg); + end + else + fn = sprintf('%s.hdr',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', fileprefix); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + hdr = read_header(fid); + fclose(fid); + end + + if hdr.sizeof_hdr ~= 348 + % first try reading the opposite endian to 'machine' + switch machine, + case 'ieee-le', machine = 'ieee-be'; + case 'ieee-be', machine = 'ieee-le'; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + hdr = read_header(fid); + fclose(fid); + end + end + + if hdr.sizeof_hdr ~= 348 + % Now throw an error + msg = sprintf('File "%s" is corrupted.',fn); + error(msg); + end + + total_scan = hdr.dim(5); + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + rmdir(tmpDir,'s'); + end + + return; % get_nii_frame + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + fseek(fid,0,'bof'); + dsr.sizeof_hdr = fread(fid,1,'int32')'; % should be 348! + + fseek(fid,40,'bof'); + dsr.dim = fread(fid,8,'int16')'; + + return; % read_header + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt new file mode 100755 index 00000000..5a2d7f87 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt @@ -0,0 +1,24 @@ +Copyright (c) 2014, Jimmy Shen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m new file mode 100755 index 00000000..d8515d48 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m @@ -0,0 +1,198 @@ +% Load NIFTI or ANALYZE dataset. Support both *.nii and *.hdr/*.img +% file extension. If file extension is not provided, *.hdr/*.img will +% be used as default. +% +% A subset of NIFTI transform is included. For non-orthogonal rotation, +% shearing etc., please use 'reslice_nii.m' to reslice the NIFTI file. +% It will not cause negative effect, as long as you remember not to do +% slice time correction after reslicing the NIFTI file. Output variable +% nii will be in RAS orientation, i.e. X axis from Left to Right, +% Y axis from Posterior to Anterior, and Z axis from Inferior to +% Superior. +% +% Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], ... +% [dim7_idx], [old_RGB], [tolerance], [preferredForm]) +% +% filename - NIFTI or ANALYZE file name. +% +% img_idx (optional) - a numerical array of 4th dimension indices, +% which is the indices of image scan volume. The number of images +% scan volumes can be obtained from get_nii_frame.m, or simply +% hdr.dime.dim(5). Only the specified volumes will be loaded. +% All available image volumes will be loaded, if it is default or +% empty. +% +% dim5_idx (optional) - a numerical array of 5th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim6_idx (optional) - a numerical array of 6th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim7_idx (optional) - a numerical array of 7th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% old_RGB (optional) - a scale number to tell difference of new RGB24 +% from old RGB24. New RGB24 uses RGB triple sequentially for each +% voxel, like [R1 G1 B1 R2 G2 B2 ...]. Analyze 6.0 from AnalyzeDirect +% uses old RGB24, in a way like [R1 R2 ... G1 G2 ... B1 B2 ...] for +% each slices. If the image that you view is garbled, try to set +% old_RGB variable to 1 and try again, because it could be in +% old RGB24. It will be set to 0, if it is default or empty. +% +% tolerance (optional) - distortion allowed in the loaded image for any +% non-orthogonal rotation or shearing of NIfTI affine matrix. If +% you set 'tolerance' to 0, it means that you do not allow any +% distortion. If you set 'tolerance' to 1, it means that you do +% not care any distortion. The image will fail to be loaded if it +% can not be tolerated. The tolerance will be set to 0.1 (10%), if +% it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% Returned values: +% +% nii structure: +% +% hdr - struct with NIFTI header fields. +% +% filetype - Analyze format .hdr/.img (0); +% NIFTI .hdr/.img (1); +% NIFTI .nii (2) +% +% fileprefix - NIFTI filename without extension. +% +% machine - machine string variable. +% +% img - 3D (or 4D) matrix of NIFTI data. +% +% original - the original header before any affine transform. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = load_nii(filename, img_idx, dim5_idx, dim6_idx, dim7_idx, ... + old_RGB, tolerance, preferredForm) + + if ~exist('filename','var') + error('Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], [dim7_idx], [old_RGB], [tolerance], [preferredForm])'); + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; % 10 percent + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + % Read the header extension + % +% nii.ext = load_nii_ext(filename); + + % Read the dataset body + % + [nii.img,nii.hdr] = load_nii_img(nii.hdr,nii.filetype,nii.fileprefix, ... + nii.machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + % Perform some of sform/qform transform + % + nii = xform_nii(nii, tolerance, preferredForm); + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + + % fix fileprefix so it doesn't point to temp location + % + nii.fileprefix = gzFileName(1:end-7); + rmdir(tmpDir,'s'); + end + + return % load_nii + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m new file mode 100755 index 00000000..193ee113 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m @@ -0,0 +1,207 @@ +% Load NIFTI header extension after its header is loaded using load_nii_hdr. +% +% Usage: ext = load_nii_ext(filename) +% +% filename - NIFTI file name. +% +% Returned values: +% +% ext - Structure of NIFTI header extension, which includes num_ext, +% and all the extended header sections in the header extension. +% Each extended header section will have its esize, ecode, and +% edata, where edata can be plain text, xml, or any raw data +% that was saved in the extended header section. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function ext = load_nii_ext(filename) + + if ~exist('filename','var'), + error('Usage: ext = load_nii_ext(filename)'); + end + + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + machine = 'ieee-le'; + new_ext = 0; + + if findstr('.nii',filename) & strcmp(filename(end-3:end), '.nii') + new_ext = 1; + filename(end-3:end)=''; + end + + if findstr('.hdr',filename) & strcmp(filename(end-3:end), '.hdr') + filename(end-3:end)=''; + end + + if findstr('.img',filename) & strcmp(filename(end-3:end), '.img') + filename(end-3:end)=''; + end + + if new_ext + fn = sprintf('%s.nii',filename); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', filename); + error(msg); + end + else + fn = sprintf('%s.hdr',filename); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', filename); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + vox_offset = 0; + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') == 348 + if new_ext + fseek(fid,108,'bof'); + vox_offset = fread(fid,1,'float32'); + end + + ext = read_extension(fid, vox_offset); + fclose(fid); + else + fclose(fid); + + % first try reading the opposite endian to 'machine' + % + switch machine, + case 'ieee-le', machine = 'ieee-be'; + case 'ieee-be', machine = 'ieee-le'; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') ~= 348 + + % Now throw an error + % + msg = sprintf('File "%s" is corrupted.',fn); + error(msg); + end + + if new_ext + fseek(fid,108,'bof'); + vox_offset = fread(fid,1,'float32'); + end + + ext = read_extension(fid, vox_offset); + fclose(fid); + end + end + end + + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + rmdir(tmpDir,'s'); + end + + + return % load_nii_ext + + +%--------------------------------------------------------------------- +function ext = read_extension(fid, vox_offset) + + ext = []; + + if vox_offset + end_of_ext = vox_offset; + else + fseek(fid, 0, 'eof'); + end_of_ext = ftell(fid); + end + + if end_of_ext > 352 + fseek(fid, 348, 'bof'); + ext.extension = fread(fid,4)'; + end + + if isempty(ext) | ext.extension(1) == 0 + ext = []; + return; + end + + i = 1; + + while(ftell(fid) < end_of_ext) + ext.section(i).esize = fread(fid,1,'int32'); + ext.section(i).ecode = fread(fid,1,'int32'); + ext.section(i).edata = char(fread(fid,ext.section(i).esize-8)'); + i = i + 1; + end + + ext.num_ext = length(ext.section); + + return % read_extension + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m new file mode 100755 index 00000000..fcfd479e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m @@ -0,0 +1,280 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function [hdr, filetype, fileprefix, machine] = load_nii_hdr(fileprefix) + + if ~exist('fileprefix','var'), + error('Usage: [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename)'); + end + + machine = 'ieee-le'; + new_ext = 0; + + if findstr('.nii',fileprefix) & strcmp(fileprefix(end-3:end), '.nii') + new_ext = 1; + fileprefix(end-3:end)=''; + end + + if findstr('.hdr',fileprefix) & strcmp(fileprefix(end-3:end), '.hdr') + fileprefix(end-3:end)=''; + end + + if findstr('.img',fileprefix) & strcmp(fileprefix(end-3:end), '.img') + fileprefix(end-3:end)=''; + end + + if new_ext + fn = sprintf('%s.nii',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', fileprefix); + error(msg); + end + else + fn = sprintf('%s.hdr',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', fileprefix); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') == 348 + hdr = read_header(fid); + fclose(fid); + else + fclose(fid); + + % first try reading the opposite endian to 'machine' + % + switch machine, + case 'ieee-le', machine = 'ieee-be'; + case 'ieee-be', machine = 'ieee-le'; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') ~= 348 + + % Now throw an error + % + msg = sprintf('File "%s" is corrupted.',fn); + error(msg); + end + + hdr = read_header(fid); + fclose(fid); + end + end + end + + if strcmp(hdr.hist.magic, 'n+1') + filetype = 2; + elseif strcmp(hdr.hist.magic, 'ni1') + filetype = 1; + else + filetype = 0; + end + + return % load_nii_hdr + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + % Original header structures + % struct dsr + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + dsr.hk = header_key(fid); + dsr.dime = image_dimension(fid); + dsr.hist = data_history(fid); + + % For Analyze data format + % + if ~strcmp(dsr.hist.magic, 'n+1') & ~strcmp(dsr.hist.magic, 'ni1') + dsr.hist.qform_code = 0; + dsr.hist.sform_code = 0; + end + + return % read_header + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + % + % int sizeof_header Should be 348. + % char regular Must be 'r' to indicate that all images and + % volumes are the same size. + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! + hk.data_type = deblank(fread(fid,10,directchar)'); + hk.db_name = deblank(fread(fid,18,directchar)'); + hk.extents = fread(fid, 1,'int32')'; + hk.session_error = fread(fid, 1,'int16')'; + hk.regular = fread(fid, 1,directchar)'; + hk.dim_info = fread(fid, 1,'uchar')'; + + return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % /* + % dim[0] Number of dimensions in database; usually 4. + % dim[1] Image X dimension; number of *pixels* in an image row. + % dim[2] Image Y dimension; number of *pixel rows* in slice. + % dim[3] Volume Z dimension; number of *slices* in a volume. + % dim[4] Time points; number of volumes in database + % */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width, mm + % pixdim[2] - voxel height, mm + % pixdim[3] - slice thickness, mm + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + dime.dim = fread(fid,8,'int16')'; + dime.intent_p1 = fread(fid,1,'float32')'; + dime.intent_p2 = fread(fid,1,'float32')'; + dime.intent_p3 = fread(fid,1,'float32')'; + dime.intent_code = fread(fid,1,'int16')'; + dime.datatype = fread(fid,1,'int16')'; + dime.bitpix = fread(fid,1,'int16')'; + dime.slice_start = fread(fid,1,'int16')'; + dime.pixdim = fread(fid,8,'float32')'; + dime.vox_offset = fread(fid,1,'float32')'; + dime.scl_slope = fread(fid,1,'float32')'; + dime.scl_inter = fread(fid,1,'float32')'; + dime.slice_end = fread(fid,1,'int16')'; + dime.slice_code = fread(fid,1,'uchar')'; + dime.xyzt_units = fread(fid,1,'uchar')'; + dime.cal_max = fread(fid,1,'float32')'; + dime.cal_min = fread(fid,1,'float32')'; + dime.slice_duration = fread(fid,1,'float32')'; + dime.toffset = fread(fid,1,'float32')'; + dime.glmax = fread(fid,1,'int32')'; + dime.glmin = fread(fid,1,'int32')'; + + return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + + % Original header structures + % struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hist.descrip = deblank(fread(fid,80,directchar)'); + hist.aux_file = deblank(fread(fid,24,directchar)'); + hist.qform_code = fread(fid,1,'int16')'; + hist.sform_code = fread(fid,1,'int16')'; + hist.quatern_b = fread(fid,1,'float32')'; + hist.quatern_c = fread(fid,1,'float32')'; + hist.quatern_d = fread(fid,1,'float32')'; + hist.qoffset_x = fread(fid,1,'float32')'; + hist.qoffset_y = fread(fid,1,'float32')'; + hist.qoffset_z = fread(fid,1,'float32')'; + hist.srow_x = fread(fid,4,'float32')'; + hist.srow_y = fread(fid,4,'float32')'; + hist.srow_z = fread(fid,4,'float32')'; + hist.intent_name = deblank(fread(fid,16,directchar)'); + hist.magic = deblank(fread(fid,4,directchar)'); + + fseek(fid,253,'bof'); + hist.originator = fread(fid, 5,'int16')'; + + return % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m new file mode 100755 index 00000000..f6e7b5d5 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m @@ -0,0 +1,392 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + if ~exist('hdr','var') | ~exist('filetype','var') | ~exist('fileprefix','var') | ~exist('machine','var') + error('Usage: [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,[img_idx],[dim5_idx],[dim6_idx],[dim7_idx],[old_RGB]);'); + end + + if ~exist('img_idx','var') | isempty(img_idx) | hdr.dime.dim(5)<1 + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) | hdr.dime.dim(6)<1 + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) | hdr.dime.dim(7)<1 + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) | hdr.dime.dim(8)<1 + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + % check img_idx + % + if ~isempty(img_idx) & ~isnumeric(img_idx) + error('"img_idx" should be a numerical array.'); + end + + if length(unique(img_idx)) ~= length(img_idx) + error('Duplicate image index in "img_idx"'); + end + + if ~isempty(img_idx) & (min(img_idx) < 1 | max(img_idx) > hdr.dime.dim(5)) + max_range = hdr.dime.dim(5); + + if max_range == 1 + error(['"img_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"img_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim5_idx + % + if ~isempty(dim5_idx) & ~isnumeric(dim5_idx) + error('"dim5_idx" should be a numerical array.'); + end + + if length(unique(dim5_idx)) ~= length(dim5_idx) + error('Duplicate index in "dim5_idx"'); + end + + if ~isempty(dim5_idx) & (min(dim5_idx) < 1 | max(dim5_idx) > hdr.dime.dim(6)) + max_range = hdr.dime.dim(6); + + if max_range == 1 + error(['"dim5_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim5_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim6_idx + % + if ~isempty(dim6_idx) & ~isnumeric(dim6_idx) + error('"dim6_idx" should be a numerical array.'); + end + + if length(unique(dim6_idx)) ~= length(dim6_idx) + error('Duplicate index in "dim6_idx"'); + end + + if ~isempty(dim6_idx) & (min(dim6_idx) < 1 | max(dim6_idx) > hdr.dime.dim(7)) + max_range = hdr.dime.dim(7); + + if max_range == 1 + error(['"dim6_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim6_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim7_idx + % + if ~isempty(dim7_idx) & ~isnumeric(dim7_idx) + error('"dim7_idx" should be a numerical array.'); + end + + if length(unique(dim7_idx)) ~= length(dim7_idx) + error('Duplicate index in "dim7_idx"'); + end + + if ~isempty(dim7_idx) & (min(dim7_idx) < 1 | max(dim7_idx) > hdr.dime.dim(8)) + max_range = hdr.dime.dim(8); + + if max_range == 1 + error(['"dim7_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim7_idx" should be an integer within the range of [' range '].']); + end + end + + [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + return % load_nii_img + + +%--------------------------------------------------------------------- +function [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + switch filetype + case {0, 1} + fn = [fileprefix '.img']; + case 2 + fn = [fileprefix '.nii']; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 1, + hdr.dime.bitpix = 1; precision = 'ubit1'; + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 32, + hdr.dime.bitpix = 64; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + case 1792, + hdr.dime.bitpix = 128; precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.dim(find(hdr.dime.dim < 1)) = 1; + + % move pointer to the start of image block + % + switch filetype + case {0, 1} + fseek(fid, 0, 'bof'); + case 2 + fseek(fid, hdr.dime.vox_offset, 'bof'); + end + + % Load whole image block for old Analyze format or binary image; + % otherwise, load images that are specified in img_idx, dim5_idx, + % dim6_idx, and dim7_idx + % + % For binary image, we have to read all because pos can not be + % seeked in bit and can not be calculated the way below. + % + if hdr.dime.datatype == 1 | isequal(hdr.dime.dim(5:8),ones(1,4)) | ... + (isempty(img_idx) & isempty(dim5_idx) & isempty(dim6_idx) & isempty(dim7_idx)) + + % For each frame, precision of value will be read + % in img_siz times, where img_siz is only the + % dimension size of an image, not the byte storage + % size of an image. + % + img_siz = prod(hdr.dime.dim(2:8)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + img = fread(fid, img_siz, sprintf('*%s',precision)); + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + else + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + + % compute size of one image + % + img_siz = prod(hdr.dime.dim(2:4)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + % preallocate img + img = zeros(img_siz, length(img_idx)*length(dim5_idx)*length(dim6_idx)*length(dim7_idx) ); + currentIndex = 1; + + for i7=1:length(dim7_idx) + for i6=1:length(dim6_idx) + for i5=1:length(dim5_idx) + for t=1:length(img_idx) + + % Position is seeked in bytes. To convert dimension size + % to byte storage size, hdr.dime.bitpix/8 will be + % applied. + % + pos = sub2ind([d1 d2 d3 d4 d5 d6 d7], 1, 1, 1, ... + img_idx(t), dim5_idx(i5),dim6_idx(i6),dim7_idx(i7)) -1; + pos = pos * hdr.dime.bitpix/8; + + if filetype == 2 + fseek(fid, pos + hdr.dime.vox_offset, 'bof'); + else + fseek(fid, pos, 'bof'); + end + + % For each frame, fread will read precision of value + % in img_siz times + % + img(:,currentIndex) = fread(fid, img_siz, sprintf('*%s',precision)); + currentIndex = currentIndex +1; + + end + end + end + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img = reshape(img, [2, length(img)/2]); + img = complex(img(1,:)', img(2,:)'); + end + + fclose(fid); + + % Update the global min and max values + % + hdr.dime.glmax = double(max(img(:))); + hdr.dime.glmin = double(min(img(:))); + + % old_RGB treat RGB slice by slice, now it is treated voxel by voxel + % + if old_RGB & hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:3) 3 hdr.dime.dim(4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [1 2 4 3 5 6 7 8]); + elseif hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + elseif hdr.dime.datatype == 511 & hdr.dime.bitpix == 96 + img = double(img(:)); + img = single((img - min(img))/(max(img) - min(img))); + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + else + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + end + + if ~isempty(img_idx) + hdr.dime.dim(5) = length(img_idx); + end + + if ~isempty(dim5_idx) + hdr.dime.dim(6) = length(dim5_idx); + end + + if ~isempty(dim6_idx) + hdr.dime.dim(7) = length(dim6_idx); + end + + if ~isempty(dim7_idx) + hdr.dime.dim(8) = length(dim7_idx); + end + + return % read_image + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m new file mode 100755 index 00000000..20335cbf --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m @@ -0,0 +1,200 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function hdr = load_nii_hdr(fileprefix, machine) + + fn = sprintf('%s.hdr',fileprefix); + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + hdr = read_header(fid); + fclose(fid); + end + + return % load_nii_hdr + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + % Original header structures + % struct dsr + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + dsr.hk = header_key(fid); + dsr.dime = image_dimension(fid); + dsr.hist = data_history(fid); + + return % read_header + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + % + % int sizeof_header Should be 348. + % char regular Must be 'r' to indicate that all images and + % volumes are the same size. + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! + hk.data_type = deblank(fread(fid,10,directchar)'); + hk.db_name = deblank(fread(fid,18,directchar)'); + hk.extents = fread(fid, 1,'int32')'; + hk.session_error = fread(fid, 1,'int16')'; + hk.regular = fread(fid, 1,directchar)'; + hk.hkey_un0 = fread(fid, 1,directchar)'; + + return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + + %struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % /* + % dim[0] Number of dimensions in database; usually 4. + % dim[1] Image X dimension; number of *pixels* in an image row. + % dim[2] Image Y dimension; number of *pixel rows* in slice. + % dim[3] Volume Z dimension; number of *slices* in a volume. + % dim[4] Time points; number of volumes in database + % */ + % char vox_units[4]; /* 16 + 4 */ + % char cal_units[8]; /* 20 + 8 */ + % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width, mm + % pixdim[2] - voxel height, mm + % pixdim[3] - slice thickness, mm + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float roi_scale; /* 72 + 4 */ + % float funused1; /* 76 + 4 */ + % float funused2; /* 80 + 4 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % int compressed; /* 92 + 4 */ + % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + dime.dim = fread(fid,8,'int16')'; + dime.vox_units = deblank(fread(fid,4,directchar)'); + dime.cal_units = deblank(fread(fid,8,directchar)'); + dime.unused1 = fread(fid,1,'int16')'; + dime.datatype = fread(fid,1,'int16')'; + dime.bitpix = fread(fid,1,'int16')'; + dime.dim_un0 = fread(fid,1,'int16')'; + dime.pixdim = fread(fid,8,'float32')'; + dime.vox_offset = fread(fid,1,'float32')'; + dime.roi_scale = fread(fid,1,'float32')'; + dime.funused1 = fread(fid,1,'float32')'; + dime.funused2 = fread(fid,1,'float32')'; + dime.cal_max = fread(fid,1,'float32')'; + dime.cal_min = fread(fid,1,'float32')'; + dime.compressed = fread(fid,1,'int32')'; + dime.verified = fread(fid,1,'int32')'; + dime.glmax = fread(fid,1,'int32')'; + dime.glmin = fread(fid,1,'int32')'; + + return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % char orient; /* 104 + 1 */ + % char originator[10]; /* 105 + 10 */ + % char generated[10]; /* 115 + 10 */ + % char scannum[10]; /* 125 + 10 */ + % char patient_id[10]; /* 135 + 10 */ + % char exp_date[10]; /* 145 + 10 */ + % char exp_time[10]; /* 155 + 10 */ + % char hist_un0[3]; /* 165 + 3 */ + % int views /* 168 + 4 */ + % int vols_added; /* 172 + 4 */ + % int start_field; /* 176 + 4 */ + % int field_skip; /* 180 + 4 */ + % int omax; /* 184 + 4 */ + % int omin; /* 188 + 4 */ + % int smax; /* 192 + 4 */ + % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hist.descrip = deblank(fread(fid,80,directchar)'); + hist.aux_file = deblank(fread(fid,24,directchar)'); + hist.orient = fread(fid, 1,'char')'; + hist.originator = fread(fid, 5,'int16')'; + hist.generated = deblank(fread(fid,10,directchar)'); + hist.scannum = deblank(fread(fid,10,directchar)'); + hist.patient_id = deblank(fread(fid,10,directchar)'); + hist.exp_date = deblank(fread(fid,10,directchar)'); + hist.exp_time = deblank(fread(fid,10,directchar)'); + hist.hist_un0 = deblank(fread(fid, 3,directchar)'); + hist.views = fread(fid, 1,'int32')'; + hist.vols_added = fread(fid, 1,'int32')'; + hist.start_field = fread(fid, 1,'int32')'; + hist.field_skip = fread(fid, 1,'int32')'; + hist.omax = fread(fid, 1,'int32')'; + hist.omin = fread(fid, 1,'int32')'; + hist.smax = fread(fid, 1,'int32')'; + hist.smin = fread(fid, 1,'int32')'; + + return % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m new file mode 100755 index 00000000..3231db01 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m @@ -0,0 +1,187 @@ +% Load NIfTI / Analyze header without applying any appropriate affine +% geometric transform or voxel intensity scaling. It is equivalent to +% hdr field when using load_untouch_nii to load dataset. Support both +% *.nii and *.hdr file extension. If file extension is not provided, +% *.hdr will be used as default. +% +% Usage: [header, ext, filetype, machine] = load_untouch_header_only(filename) +% +% filename - NIfTI / Analyze file name. +% +% Returned values: +% +% header - struct with NIfTI / Analyze header fields. +% +% ext - NIfTI extension if it is not empty. +% +% filetype - 0 for Analyze format (*.hdr/*.img); +% 1 for NIFTI format in 2 files (*.hdr/*.img); +% 2 for NIFTI format in 1 file (*.nii). +% +% machine - a string, see below for details. The default here is 'ieee-le'. +% +% 'native' or 'n' - local machine format - the default +% 'ieee-le' or 'l' - IEEE floating point with little-endian +% byte ordering +% 'ieee-be' or 'b' - IEEE floating point with big-endian +% byte ordering +% 'vaxd' or 'd' - VAX D floating point and VAX ordering +% 'vaxg' or 'g' - VAX G floating point and VAX ordering +% 'cray' or 'c' - Cray floating point with big-endian +% byte ordering +% 'ieee-le.l64' or 'a' - IEEE floating point with little-endian +% byte ordering and 64 bit long data type +% 'ieee-be.l64' or 's' - IEEE floating point with big-endian byte +% ordering and 64 bit long data type. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [hdr, ext, filetype, machine] = load_untouch_header_only(filename) + + if ~exist('filename','var') + error('Usage: [header, ext, filetype, machine] = load_untouch_header_only(filename)'); + end + + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + % Read the dataset header + % + [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename); + + if filetype == 0 + hdr = load_untouch0_nii_hdr(fileprefix, machine); + ext = []; + else + hdr = load_untouch_nii_hdr(fileprefix, machine, filetype); + + % Read the header extension + % + ext = load_nii_ext(filename); + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 1, + hdr.dime.bitpix = 1; precision = 'ubit1'; + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 32, + hdr.dime.bitpix = 64; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + case 1792, + hdr.dime.bitpix = 128; precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + tmp = hdr.dime.dim(2:end); + tmp(find(tmp < 1)) = 1; + hdr.dime.dim(2:end) = tmp; + + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + rmdir(tmpDir,'s'); + end + + + return % load_untouch_header_only + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m new file mode 100755 index 00000000..42fa763c --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m @@ -0,0 +1,191 @@ +% Load NIFTI or ANALYZE dataset, but not applying any appropriate affine +% geometric transform or voxel intensity scaling. +% +% Although according to NIFTI website, all those header information are +% supposed to be applied to the loaded NIFTI image, there are some +% situations that people do want to leave the original NIFTI header and +% data untouched. They will probably just use MATLAB to do certain image +% processing regardless of image orientation, and to save data back with +% the same NIfTI header. +% +% Since this program is only served for those situations, please use it +% together with "save_untouch_nii.m", and do not use "save_nii.m" or +% "view_nii.m" for the data that is loaded by "load_untouch_nii.m". For +% normal situation, you should use "load_nii.m" instead. +% +% Usage: nii = load_untouch_nii(filename, [img_idx], [dim5_idx], [dim6_idx], ... +% [dim7_idx], [old_RGB], [slice_idx]) +% +% filename - NIFTI or ANALYZE file name. +% +% img_idx (optional) - a numerical array of image volume indices. +% Only the specified volumes will be loaded. All available image +% volumes will be loaded, if it is default or empty. +% +% The number of images scans can be obtained from get_nii_frame.m, +% or simply: hdr.dime.dim(5). +% +% dim5_idx (optional) - a numerical array of 5th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim6_idx (optional) - a numerical array of 6th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim7_idx (optional) - a numerical array of 7th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% old_RGB (optional) - a scale number to tell difference of new RGB24 +% from old RGB24. New RGB24 uses RGB triple sequentially for each +% voxel, like [R1 G1 B1 R2 G2 B2 ...]. Analyze 6.0 from AnalyzeDirect +% uses old RGB24, in a way like [R1 R2 ... G1 G2 ... B1 B2 ...] for +% each slices. If the image that you view is garbled, try to set +% old_RGB variable to 1 and try again, because it could be in +% old RGB24. It will be set to 0, if it is default or empty. +% +% slice_idx (optional) - a numerical array of image slice indices. +% Only the specified slices will be loaded. All available image +% slices will be loaded, if it is default or empty. +% +% Returned values: +% +% nii structure: +% +% hdr - struct with NIFTI header fields. +% +% filetype - Analyze format .hdr/.img (0); +% NIFTI .hdr/.img (1); +% NIFTI .nii (2) +% +% fileprefix - NIFTI filename without extension. +% +% machine - machine string variable. +% +% img - 3D (or 4D) matrix of NIFTI data. +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = load_untouch_nii(filename, img_idx, dim5_idx, dim6_idx, dim7_idx, ... + old_RGB, slice_idx) + + if ~exist('filename','var') + error('Usage: nii = load_untouch_nii(filename, [img_idx], [dim5_idx], [dim6_idx], [dim7_idx], [old_RGB], [slice_idx])'); + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('slice_idx','var') | isempty(slice_idx) + slice_idx = []; + end + + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + if nii.filetype == 0 + nii.hdr = load_untouch0_nii_hdr(nii.fileprefix,nii.machine); + nii.ext = []; + else + nii.hdr = load_untouch_nii_hdr(nii.fileprefix,nii.machine,nii.filetype); + + % Read the header extension + % + nii.ext = load_nii_ext(filename); + end + + % Read the dataset body + % + [nii.img,nii.hdr] = load_untouch_nii_img(nii.hdr,nii.filetype,nii.fileprefix, ... + nii.machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB,slice_idx); + + % Perform some of sform/qform transform + % +% nii = xform_nii(nii, tolerance, preferredForm); + + nii.untouch = 1; + + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + + % fix fileprefix so it doesn't point to temp location + % + nii.fileprefix = gzFileName(1:end-7); + rmdir(tmpDir,'s'); + end + + + return % load_untouch_nii + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m new file mode 100755 index 00000000..ee91cafd --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m @@ -0,0 +1,217 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function hdr = load_nii_hdr(fileprefix, machine, filetype) + + if filetype == 2 + fn = sprintf('%s.nii',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', fileprefix); + error(msg); + end + else + fn = sprintf('%s.hdr',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', fileprefix); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + hdr = read_header(fid); + fclose(fid); + end + + return % load_nii_hdr + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + % Original header structures + % struct dsr + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + dsr.hk = header_key(fid); + dsr.dime = image_dimension(fid); + dsr.hist = data_history(fid); + + % For Analyze data format + % + if ~strcmp(dsr.hist.magic, 'n+1') & ~strcmp(dsr.hist.magic, 'ni1') + dsr.hist.qform_code = 0; + dsr.hist.sform_code = 0; + end + + return % read_header + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + % + % int sizeof_header Should be 348. + % char regular Must be 'r' to indicate that all images and + % volumes are the same size. + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! + hk.data_type = deblank(fread(fid,10,directchar)'); + hk.db_name = deblank(fread(fid,18,directchar)'); + hk.extents = fread(fid, 1,'int32')'; + hk.session_error = fread(fid, 1,'int16')'; + hk.regular = fread(fid, 1,directchar)'; + hk.dim_info = fread(fid, 1,'uchar')'; + + return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % /* + % dim[0] Number of dimensions in database; usually 4. + % dim[1] Image X dimension; number of *pixels* in an image row. + % dim[2] Image Y dimension; number of *pixel rows* in slice. + % dim[3] Volume Z dimension; number of *slices* in a volume. + % dim[4] Time points; number of volumes in database + % */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width, mm + % pixdim[2] - voxel height, mm + % pixdim[3] - slice thickness, mm + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + dime.dim = fread(fid,8,'int16')'; + dime.intent_p1 = fread(fid,1,'float32')'; + dime.intent_p2 = fread(fid,1,'float32')'; + dime.intent_p3 = fread(fid,1,'float32')'; + dime.intent_code = fread(fid,1,'int16')'; + dime.datatype = fread(fid,1,'int16')'; + dime.bitpix = fread(fid,1,'int16')'; + dime.slice_start = fread(fid,1,'int16')'; + dime.pixdim = fread(fid,8,'float32')'; + dime.vox_offset = fread(fid,1,'float32')'; + dime.scl_slope = fread(fid,1,'float32')'; + dime.scl_inter = fread(fid,1,'float32')'; + dime.slice_end = fread(fid,1,'int16')'; + dime.slice_code = fread(fid,1,'uchar')'; + dime.xyzt_units = fread(fid,1,'uchar')'; + dime.cal_max = fread(fid,1,'float32')'; + dime.cal_min = fread(fid,1,'float32')'; + dime.slice_duration = fread(fid,1,'float32')'; + dime.toffset = fread(fid,1,'float32')'; + dime.glmax = fread(fid,1,'int32')'; + dime.glmin = fread(fid,1,'int32')'; + + return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + + % Original header structures + % struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hist.descrip = deblank(fread(fid,80,directchar)'); + hist.aux_file = deblank(fread(fid,24,directchar)'); + hist.qform_code = fread(fid,1,'int16')'; + hist.sform_code = fread(fid,1,'int16')'; + hist.quatern_b = fread(fid,1,'float32')'; + hist.quatern_c = fread(fid,1,'float32')'; + hist.quatern_d = fread(fid,1,'float32')'; + hist.qoffset_x = fread(fid,1,'float32')'; + hist.qoffset_y = fread(fid,1,'float32')'; + hist.qoffset_z = fread(fid,1,'float32')'; + hist.srow_x = fread(fid,4,'float32')'; + hist.srow_y = fread(fid,4,'float32')'; + hist.srow_z = fread(fid,4,'float32')'; + hist.intent_name = deblank(fread(fid,16,directchar)'); + hist.magic = deblank(fread(fid,4,directchar)'); + + return % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m new file mode 100755 index 00000000..68901444 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m @@ -0,0 +1,468 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function [img,hdr] = load_untouch_nii_img(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB,slice_idx) + + if ~exist('hdr','var') | ~exist('filetype','var') | ~exist('fileprefix','var') | ~exist('machine','var') + error('Usage: [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,[img_idx],[dim5_idx],[dim6_idx],[dim7_idx],[old_RGB],[slice_idx]);'); + end + + if ~exist('img_idx','var') | isempty(img_idx) | hdr.dime.dim(5)<1 + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) | hdr.dime.dim(6)<1 + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) | hdr.dime.dim(7)<1 + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) | hdr.dime.dim(8)<1 + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('slice_idx','var') | isempty(slice_idx) | hdr.dime.dim(4)<1 + slice_idx = []; + end + + % check img_idx + % + if ~isempty(img_idx) & ~isnumeric(img_idx) + error('"img_idx" should be a numerical array.'); + end + + if length(unique(img_idx)) ~= length(img_idx) + error('Duplicate image index in "img_idx"'); + end + + if ~isempty(img_idx) & (min(img_idx) < 1 | max(img_idx) > hdr.dime.dim(5)) + max_range = hdr.dime.dim(5); + + if max_range == 1 + error(['"img_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"img_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim5_idx + % + if ~isempty(dim5_idx) & ~isnumeric(dim5_idx) + error('"dim5_idx" should be a numerical array.'); + end + + if length(unique(dim5_idx)) ~= length(dim5_idx) + error('Duplicate index in "dim5_idx"'); + end + + if ~isempty(dim5_idx) & (min(dim5_idx) < 1 | max(dim5_idx) > hdr.dime.dim(6)) + max_range = hdr.dime.dim(6); + + if max_range == 1 + error(['"dim5_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim5_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim6_idx + % + if ~isempty(dim6_idx) & ~isnumeric(dim6_idx) + error('"dim6_idx" should be a numerical array.'); + end + + if length(unique(dim6_idx)) ~= length(dim6_idx) + error('Duplicate index in "dim6_idx"'); + end + + if ~isempty(dim6_idx) & (min(dim6_idx) < 1 | max(dim6_idx) > hdr.dime.dim(7)) + max_range = hdr.dime.dim(7); + + if max_range == 1 + error(['"dim6_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim6_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim7_idx + % + if ~isempty(dim7_idx) & ~isnumeric(dim7_idx) + error('"dim7_idx" should be a numerical array.'); + end + + if length(unique(dim7_idx)) ~= length(dim7_idx) + error('Duplicate index in "dim7_idx"'); + end + + if ~isempty(dim7_idx) & (min(dim7_idx) < 1 | max(dim7_idx) > hdr.dime.dim(8)) + max_range = hdr.dime.dim(8); + + if max_range == 1 + error(['"dim7_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim7_idx" should be an integer within the range of [' range '].']); + end + end + + % check slice_idx + % + if ~isempty(slice_idx) & ~isnumeric(slice_idx) + error('"slice_idx" should be a numerical array.'); + end + + if length(unique(slice_idx)) ~= length(slice_idx) + error('Duplicate index in "slice_idx"'); + end + + if ~isempty(slice_idx) & (min(slice_idx) < 1 | max(slice_idx) > hdr.dime.dim(4)) + max_range = hdr.dime.dim(4); + + if max_range == 1 + error(['"slice_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"slice_idx" should be an integer within the range of [' range '].']); + end + end + + [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB,slice_idx); + + return % load_nii_img + + +%--------------------------------------------------------------------- +function [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB,slice_idx) + + switch filetype + case {0, 1} + fn = [fileprefix '.img']; + case 2 + fn = [fileprefix '.nii']; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 1, + hdr.dime.bitpix = 1; precision = 'ubit1'; + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 32, + hdr.dime.bitpix = 64; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + case 1792, + hdr.dime.bitpix = 128; precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + tmp = hdr.dime.dim(2:end); + tmp(find(tmp < 1)) = 1; + hdr.dime.dim(2:end) = tmp; + + % move pointer to the start of image block + % + switch filetype + case {0, 1} + fseek(fid, 0, 'bof'); + case 2 + fseek(fid, hdr.dime.vox_offset, 'bof'); + end + + % Load whole image block for old Analyze format or binary image; + % otherwise, load images that are specified in img_idx, dim5_idx, + % dim6_idx, and dim7_idx + % + % For binary image, we have to read all because pos can not be + % seeked in bit and can not be calculated the way below. + % + if hdr.dime.datatype == 1 | isequal(hdr.dime.dim(4:8),ones(1,5)) | ... + (isempty(img_idx) & isempty(dim5_idx) & isempty(dim6_idx) & isempty(dim7_idx) & isempty(slice_idx)) + + % For each frame, precision of value will be read + % in img_siz times, where img_siz is only the + % dimension size of an image, not the byte storage + % size of an image. + % + img_siz = prod(hdr.dime.dim(2:8)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + img = fread(fid, img_siz, sprintf('*%s',precision)); + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(slice_idx) + slice_idx = 1:d3; + end + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + else + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(slice_idx) + slice_idx = 1:d3; + end + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + + %ROMAN: begin + roman = 1; + if(roman) + + % compute size of one slice + % + img_siz = prod(hdr.dime.dim(2:3)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + % preallocate img + img = zeros(img_siz, length(slice_idx)*length(img_idx)*length(dim5_idx)*length(dim6_idx)*length(dim7_idx) ); + currentIndex = 1; + else + img = []; + end; %if(roman) + % ROMAN: end + + for i7=1:length(dim7_idx) + for i6=1:length(dim6_idx) + for i5=1:length(dim5_idx) + for t=1:length(img_idx) + for s=1:length(slice_idx) + + % Position is seeked in bytes. To convert dimension size + % to byte storage size, hdr.dime.bitpix/8 will be + % applied. + % + pos = sub2ind([d1 d2 d3 d4 d5 d6 d7], 1, 1, slice_idx(s), ... + img_idx(t), dim5_idx(i5),dim6_idx(i6),dim7_idx(i7)) -1; + pos = pos * hdr.dime.bitpix/8; + + % ROMAN: begin + if(roman) + % do nothing + else + img_siz = prod(hdr.dime.dim(2:3)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + end; % if (roman) + % ROMAN: end + + if filetype == 2 + fseek(fid, pos + hdr.dime.vox_offset, 'bof'); + else + fseek(fid, pos, 'bof'); + end + + % For each frame, fread will read precision of value + % in img_siz times + % + % ROMAN: begin + if(roman) + img(:,currentIndex) = fread(fid, img_siz, sprintf('*%s',precision)); + currentIndex = currentIndex +1; + else + img = [img fread(fid, img_siz, sprintf('*%s',precision))]; + end; %if(roman) + % ROMAN: end + + end + end + end + end + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img = reshape(img, [2, length(img)/2]); + img = complex(img(1,:)', img(2,:)'); + end + + fclose(fid); + + % Update the global min and max values + % + hdr.dime.glmax = double(max(img(:))); + hdr.dime.glmin = double(min(img(:))); + + % old_RGB treat RGB slice by slice, now it is treated voxel by voxel + % + if old_RGB & hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:3) 3 length(slice_idx) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [1 2 4 3 5 6 7 8]); + elseif hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:3) length(slice_idx) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + elseif hdr.dime.datatype == 511 & hdr.dime.bitpix == 96 + img = double(img(:)); + img = single((img - min(img))/(max(img) - min(img))); + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:3) length(slice_idx) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + else + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:3) length(slice_idx) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + end + + if ~isempty(slice_idx) + hdr.dime.dim(4) = length(slice_idx); + end + + if ~isempty(img_idx) + hdr.dime.dim(5) = length(img_idx); + end + + if ~isempty(dim5_idx) + hdr.dime.dim(6) = length(dim5_idx); + end + + if ~isempty(dim6_idx) + hdr.dime.dim(7) = length(dim6_idx); + end + + if ~isempty(dim7_idx) + hdr.dime.dim(8) = length(dim7_idx); + end + + return % read_image + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m new file mode 100755 index 00000000..3febf85b --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m @@ -0,0 +1,210 @@ +% Make ANALYZE 7.5 data structure specified by a 3D or 4D matrix. +% Optional parameters can also be included, such as: voxel_size, +% origin, datatype, and description. +% +% Once the ANALYZE structure is made, it can be saved into ANALYZE 7.5 +% format data file using "save_untouch_nii" command (for more detail, +% type: help save_untouch_nii). +% +% Usage: ana = make_ana(img, [voxel_size], [origin], [datatype], [description]) +% +% Where: +% +% img: a 3D matrix [x y z], or a 4D matrix with time +% series [x y z t]. When image is in RGB format, +% make sure that the size of 4th dimension is +% always 3 (i.e. [R G B]). In that case, make +% sure that you must specify RGB datatype to 128. +% +% voxel_size (optional): Voxel size in millimeter for each +% dimension. Default is [1 1 1]. +% +% origin (optional): The AC origin. Default is [0 0 0]. +% +% datatype (optional): Storage data type: +% 2 - uint8, 4 - int16, 8 - int32, 16 - float32, +% 64 - float64, 128 - RGB24 +% Default will use the data type of 'img' matrix +% For RGB image, you must specify it to 128. +% +% description (optional): Description of data. Default is ''. +% +% e.g.: +% origin = [33 44 13]; datatype = 64; +% ana = make_ana(img, [], origin, datatype); % default voxel_size +% +% ANALYZE 7.5 format: http://www.rotman-baycrest.on.ca/~jimmy/ANALYZE75.pdf +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function ana = make_ana(varargin) + + ana.img = varargin{1}; + dims = size(ana.img); + dims = [4 dims ones(1,8)]; + dims = dims(1:8); + + voxel_size = [0 ones(1,3) zeros(1,4)]; + origin = zeros(1,5); + descrip = ''; + + switch class(ana.img) + case 'uint8' + datatype = 2; + case 'int16' + datatype = 4; + case 'int32' + datatype = 8; + case 'single' + datatype = 16; + case 'double' + datatype = 64; + otherwise + error('Datatype is not supported by make_ana.'); + end + + if nargin > 1 & ~isempty(varargin{2}) + voxel_size(2:4) = double(varargin{2}); + end + + if nargin > 2 & ~isempty(varargin{3}) + origin(1:3) = double(varargin{3}); + end + + if nargin > 3 & ~isempty(varargin{4}) + datatype = double(varargin{4}); + + if datatype == 128 | datatype == 511 + dims(5) = []; + dims = [dims 1]; + end + end + + if nargin > 4 & ~isempty(varargin{5}) + descrip = varargin{5}; + end + + if ndims(ana.img) > 4 + error('NIfTI only allows a maximum of 4 Dimension matrix.'); + end + + maxval = round(double(max(ana.img(:)))); + minval = round(double(min(ana.img(:)))); + + ana.hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval); + ana.filetype = 0; + ana.ext = []; + ana.untouch = 1; + + switch ana.hdr.dime.datatype + case 2 + ana.img = uint8(ana.img); + case 4 + ana.img = int16(ana.img); + case 8 + ana.img = int32(ana.img); + case 16 + ana.img = single(ana.img); + case 64 + ana.img = double(ana.img); + case 128 + ana.img = uint8(ana.img); + otherwise + error('Datatype is not supported by make_ana.'); + end + + return; % make_ana + + +%--------------------------------------------------------------------- +function hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval) + + hdr.hk = header_key; + hdr.dime = image_dimension(dims, voxel_size, datatype, maxval, minval); + hdr.hist = data_history(origin, descrip); + + return; % make_header + + +%--------------------------------------------------------------------- +function hk = header_key + + hk.sizeof_hdr = 348; % must be 348! + hk.data_type = ''; + hk.db_name = ''; + hk.extents = 0; + hk.session_error = 0; + hk.regular = 'r'; + hk.hkey_un0 = '0'; + + return; % header_key + + +%--------------------------------------------------------------------- +function dime = image_dimension(dims, voxel_size, datatype, maxval, minval) + + dime.dim = dims; + dime.vox_units = 'mm'; + dime.cal_units = ''; + dime.unused1 = 0; + dime.datatype = datatype; + + switch dime.datatype + case 2, + dime.bitpix = 8; precision = 'uint8'; + case 4, + dime.bitpix = 16; precision = 'int16'; + case 8, + dime.bitpix = 32; precision = 'int32'; + case 16, + dime.bitpix = 32; precision = 'float32'; + case 64, + dime.bitpix = 64; precision = 'float64'; + case 128 + dime.bitpix = 24; precision = 'uint8'; + otherwise + error('Datatype is not supported by make_ana.'); + end + + dime.dim_un0 = 0; + dime.pixdim = voxel_size; + dime.vox_offset = 0; + dime.roi_scale = 1; + dime.funused1 = 0; + dime.funused2 = 0; + dime.cal_max = 0; + dime.cal_min = 0; + dime.compressed = 0; + dime.verified = 0; + dime.glmax = maxval; + dime.glmin = minval; + + return; % image_dimension + + +%--------------------------------------------------------------------- +function hist = data_history(origin, descrip) + + hist.descrip = descrip; + hist.aux_file = 'none'; + hist.orient = 0; + hist.originator = origin; + hist.generated = ''; + hist.scannum = ''; + hist.patient_id = ''; + hist.exp_date = ''; + hist.exp_time = ''; + hist.hist_un0 = ''; + hist.views = 0; + hist.vols_added = 0; + hist.start_field = 0; + hist.field_skip = 0; + hist.omax = 0; + hist.omin = 0; + hist.smax = 0; + hist.smin = 0; + + return; % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m new file mode 100755 index 00000000..af319520 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m @@ -0,0 +1,256 @@ +% Make NIfTI structure specified by an N-D matrix. Usually, N is 3 for +% 3D matrix [x y z], or 4 for 4D matrix with time series [x y z t]. +% Optional parameters can also be included, such as: voxel_size, +% origin, datatype, and description. +% +% Once the NIfTI structure is made, it can be saved into NIfTI file +% using "save_nii" command (for more detail, type: help save_nii). +% +% Usage: nii = make_nii(img, [voxel_size], [origin], [datatype], [description]) +% +% Where: +% +% img: Usually, img is a 3D matrix [x y z], or a 4D +% matrix with time series [x y z t]. However, +% NIfTI allows a maximum of 7D matrix. When the +% image is in RGB format, make sure that the size +% of 4th dimension is always 3 (i.e. [R G B]). In +% that case, make sure that you must specify RGB +% datatype, which is either 128 or 511. +% +% voxel_size (optional): Voxel size in millimeter for each +% dimension. Default is [1 1 1]. +% +% origin (optional): The AC origin. Default is [0 0 0]. +% +% datatype (optional): Storage data type: +% 2 - uint8, 4 - int16, 8 - int32, 16 - float32, +% 32 - complex64, 64 - float64, 128 - RGB24, +% 256 - int8, 511 - RGB96, 512 - uint16, +% 768 - uint32, 1792 - complex128 +% Default will use the data type of 'img' matrix +% For RGB image, you must specify it to either 128 +% or 511. +% +% description (optional): Description of data. Default is ''. +% +% e.g.: +% origin = [33 44 13]; datatype = 64; +% nii = make_nii(img, [], origin, datatype); % default voxel_size +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = make_nii(varargin) + + nii.img = varargin{1}; + dims = size(nii.img); + dims = [length(dims) dims ones(1,8)]; + dims = dims(1:8); + + voxel_size = [0 ones(1,7)]; + origin = zeros(1,5); + descrip = ''; + + switch class(nii.img) + case 'uint8' + datatype = 2; + case 'int16' + datatype = 4; + case 'int32' + datatype = 8; + case 'single' + if isreal(nii.img) + datatype = 16; + else + datatype = 32; + end + case 'double' + if isreal(nii.img) + datatype = 64; + else + datatype = 1792; + end + case 'int8' + datatype = 256; + case 'uint16' + datatype = 512; + case 'uint32' + datatype = 768; + otherwise + error('Datatype is not supported by make_nii.'); + end + + if nargin > 1 & ~isempty(varargin{2}) + voxel_size(2:4) = double(varargin{2}); + end + + if nargin > 2 & ~isempty(varargin{3}) + origin(1:3) = double(varargin{3}); + end + + if nargin > 3 & ~isempty(varargin{4}) + datatype = double(varargin{4}); + + if datatype == 128 | datatype == 511 + dims(5) = []; + dims(1) = dims(1) - 1; + dims = [dims 1]; + end + end + + if nargin > 4 & ~isempty(varargin{5}) + descrip = varargin{5}; + end + + if ndims(nii.img) > 7 + error('NIfTI only allows a maximum of 7 Dimension matrix.'); + end + + maxval = round(double(max(nii.img(:)))); + minval = round(double(min(nii.img(:)))); + + nii.hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval); + + switch nii.hdr.dime.datatype + case 2 + nii.img = uint8(nii.img); + case 4 + nii.img = int16(nii.img); + case 8 + nii.img = int32(nii.img); + case 16 + nii.img = single(nii.img); + case 32 + nii.img = single(nii.img); + case 64 + nii.img = double(nii.img); + case 128 + nii.img = uint8(nii.img); + case 256 + nii.img = int8(nii.img); + case 511 + img = double(nii.img(:)); + img = single((img - min(img))/(max(img) - min(img))); + nii.img = reshape(img, size(nii.img)); + nii.hdr.dime.glmax = double(max(img)); + nii.hdr.dime.glmin = double(min(img)); + case 512 + nii.img = uint16(nii.img); + case 768 + nii.img = uint32(nii.img); + case 1792 + nii.img = double(nii.img); + otherwise + error('Datatype is not supported by make_nii.'); + end + + return; % make_nii + + +%--------------------------------------------------------------------- +function hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval) + + hdr.hk = header_key; + hdr.dime = image_dimension(dims, voxel_size, datatype, maxval, minval); + hdr.hist = data_history(origin, descrip); + + return; % make_header + + +%--------------------------------------------------------------------- +function hk = header_key + + hk.sizeof_hdr = 348; % must be 348! + hk.data_type = ''; + hk.db_name = ''; + hk.extents = 0; + hk.session_error = 0; + hk.regular = 'r'; + hk.dim_info = 0; + + return; % header_key + + +%--------------------------------------------------------------------- +function dime = image_dimension(dims, voxel_size, datatype, maxval, minval) + + dime.dim = dims; + dime.intent_p1 = 0; + dime.intent_p2 = 0; + dime.intent_p3 = 0; + dime.intent_code = 0; + dime.datatype = datatype; + + switch dime.datatype + case 2, + dime.bitpix = 8; precision = 'uint8'; + case 4, + dime.bitpix = 16; precision = 'int16'; + case 8, + dime.bitpix = 32; precision = 'int32'; + case 16, + dime.bitpix = 32; precision = 'float32'; + case 32, + dime.bitpix = 64; precision = 'float32'; + case 64, + dime.bitpix = 64; precision = 'float64'; + case 128 + dime.bitpix = 24; precision = 'uint8'; + case 256 + dime.bitpix = 8; precision = 'int8'; + case 511 + dime.bitpix = 96; precision = 'float32'; + case 512 + dime.bitpix = 16; precision = 'uint16'; + case 768 + dime.bitpix = 32; precision = 'uint32'; + case 1792, + dime.bitpix = 128; precision = 'float64'; + otherwise + error('Datatype is not supported by make_nii.'); + end + + dime.slice_start = 0; + dime.pixdim = voxel_size; + dime.vox_offset = 0; + dime.scl_slope = 0; + dime.scl_inter = 0; + dime.slice_end = 0; + dime.slice_code = 0; + dime.xyzt_units = 0; + dime.cal_max = 0; + dime.cal_min = 0; + dime.slice_duration = 0; + dime.toffset = 0; + dime.glmax = maxval; + dime.glmin = minval; + + return; % image_dimension + + +%--------------------------------------------------------------------- +function hist = data_history(origin, descrip) + + hist.descrip = descrip; + hist.aux_file = 'none'; + hist.qform_code = 0; + hist.sform_code = 0; + hist.quatern_b = 0; + hist.quatern_c = 0; + hist.quatern_d = 0; + hist.qoffset_x = 0; + hist.qoffset_y = 0; + hist.qoffset_z = 0; + hist.srow_x = zeros(1,4); + hist.srow_y = zeros(1,4); + hist.srow_z = zeros(1,4); + hist.intent_name = ''; + hist.magic = ''; + hist.originator = origin; + + return; % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m new file mode 100755 index 00000000..1584306d --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m @@ -0,0 +1,83 @@ +%MAT_INTO_HDR The old versions of SPM (any version before SPM5) store +% an affine matrix of the SPM Reoriented image into a matlab file +% (.mat extension). The file name of this SPM matlab file is the +% same as the SPM Reoriented image file (.img/.hdr extension). +% +% This program will convert the ANALYZE 7.5 SPM Reoriented image +% file into NIfTI format, and integrate the affine matrix in the +% SPM matlab file into its header file (.hdr extension). +% +% WARNING: Before you run this program, please save the header +% file (.hdr extension) into another file name or into another +% folder location, because all header files (.hdr extension) +% will be overwritten after they are converted into NIfTI +% format. +% +% Usage: mat_into_hdr(filename); +% +% filename: file name(s) with .hdr or .mat file extension, like: +% '*.hdr', or '*.mat', or a single .hdr or .mat file. +% e.g. mat_into_hdr('T1.hdr') +% mat_into_hdr('*.mat') +% + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +%------------------------------------------------------------------------- +function mat_into_hdr(files) + + pn = fileparts(files); + file_lst = dir(files); + file_lst = {file_lst.name}; + file1 = file_lst{1}; + [p n e]= fileparts(file1); + + for i=1:length(file_lst) + [p n e]= fileparts(file_lst{i}); + disp(['working on file ', num2str(i) ,' of ', num2str(length(file_lst)), ': ', n,e]); + process=1; + + if isequal(e,'.hdr') + mat=fullfile(pn, [n,'.mat']); + hdr=fullfile(pn, file_lst{i}); + + if ~exist(mat,'file') + warning(['Cannot find file "',mat , '". File "', n, e, '" will not be processed.']); + process=0; + end + elseif isequal(e,'.mat') + hdr=fullfile(pn, [n,'.hdr']); + mat=fullfile(pn, file_lst{i}); + + if ~exist(hdr,'file') + warning(['Can not find file "',hdr , '". File "', n, e, '" will not be processed.']); + process=0; + end + else + warning(['Input file must have .mat or .hdr extension. File "', n, e, '" will not be processed.']); + process=0; + end + + if process + load(mat); + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + + [h filetype fileprefix machine]=load_nii_hdr(hdr); + h.hist.qform_code=0; + h.hist.sform_code=1; + h.hist.srow_x=M(1,:); + h.hist.srow_y=M(2,:); + h.hist.srow_z=M(3,:); + h.hist.magic='ni1'; + + fid = fopen(hdr,'w',machine); + save_nii_hdr(h,fid); + fclose(fid); + end + end + + return; % mat_into_hdr + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m new file mode 100755 index 00000000..e6284f1c --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m @@ -0,0 +1,142 @@ +% PAD_NII: Pad the NIfTI volume from any of the 6 sides +% +% Usage: nii = pad_nii(nii, [option]) +% +% Inputs: +% +% nii - NIfTI volume. +% +% option - struct instructing how many voxel to be padded from which side. +% +% option.pad_from_L = ( number of voxel ) +% option.pad_from_R = ( number of voxel ) +% option.pad_from_P = ( number of voxel ) +% option.pad_from_A = ( number of voxel ) +% option.pad_from_I = ( number of voxel ) +% option.pad_from_S = ( number of voxel ) +% option.bg = [0] +% +% Options description in detail: +% ============================== +% +% pad_from_L: Number of voxels from Left side will be padded. +% +% pad_from_R: Number of voxels from Right side will be padded. +% +% pad_from_P: Number of voxels from Posterior side will be padded. +% +% pad_from_A: Number of voxels from Anterior side will be padded. +% +% pad_from_I: Number of voxels from Inferior side will be padded. +% +% pad_from_S: Number of voxels from Superior side will be padded. +% +% bg: Background intensity, which is 0 by default. +% +% NIfTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jshen@research.baycrest.org) +% +function nii = pad_nii(nii, opt) + + dims = abs(nii.hdr.dime.dim(2:4)); + origin = abs(nii.hdr.hist.originator(1:3)); + + if isempty(origin) | all(origin == 0) % according to SPM + origin = round((dims+1)/2); + end + + pad_from_L = 0; + pad_from_R = 0; + pad_from_P = 0; + pad_from_A = 0; + pad_from_I = 0; + pad_from_S = 0; + bg = 0; + + if nargin > 1 & ~isempty(opt) + if ~isstruct(opt) + error('option argument should be a struct'); + end + + if isfield(opt,'pad_from_L') + pad_from_L = round(opt.pad_from_L); + + if pad_from_L >= origin(1) | pad_from_L < 0 + error('pad_from_L cannot be negative'); + end + end + + if isfield(opt,'pad_from_P') + pad_from_P = round(opt.pad_from_P); + + if pad_from_P >= origin(2) | pad_from_P < 0 + error('pad_from_P cannot be negative'); + end + end + + if isfield(opt,'pad_from_I') + pad_from_I = round(opt.pad_from_I); + + if pad_from_I >= origin(3) | pad_from_I < 0 + error('pad_from_I cannot be negative'); + end + end + + if isfield(opt,'pad_from_R') + pad_from_R = round(opt.pad_from_R); + + if pad_from_R > dims(1)-origin(1) | pad_from_R < 0 + error('pad_from_R cannot be negative'); + end + end + + if isfield(opt,'pad_from_A') + pad_from_A = round(opt.pad_from_A); + + if pad_from_A > dims(2)-origin(2) | pad_from_A < 0 + error('pad_from_A cannot be negative'); + end + end + + if isfield(opt,'pad_from_S') + pad_from_S = round(opt.pad_from_S); + + if pad_from_S > dims(3)-origin(3) | pad_from_S < 0 + error('pad_from_S cannot be negative'); + end + end + + if isfield(opt,'bg') + bg = opt.bg; + end + end + + blk = bg * ones( pad_from_L, dims(2), dims(3) ); + nii.img = cat(1, blk, nii.img); + + blk = bg * ones( pad_from_R, dims(2), dims(3) ); + nii.img = cat(1, nii.img, blk); + + dims = size(nii.img); + + blk = bg * ones( dims(1), pad_from_P, dims(3) ); + nii.img = cat(2, blk, nii.img); + + blk = bg * ones( dims(1), pad_from_A, dims(3) ); + nii.img = cat(2, nii.img, blk); + + dims = size(nii.img); + + blk = bg * ones( dims(1), dims(2), pad_from_I ); + nii.img = cat(3, blk, nii.img); + + blk = bg * ones( dims(1), dims(2), pad_from_S ); + nii.img = cat(3, nii.img, blk); + + nii = make_nii(nii.img, nii.hdr.dime.pixdim(2:4), ... + [origin(1)+pad_from_L origin(2)+pad_from_P origin(3)+pad_from_I], ... + nii.hdr.dime.datatype, nii.hdr.hist.descrip); + + return; + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m new file mode 100755 index 00000000..a764d1ae --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m @@ -0,0 +1,321 @@ +% The basic application of the 'reslice_nii.m' program is to perform +% any 3D affine transform defined by a NIfTI format image. +% +% In addition, the 'reslice_nii.m' program can also be applied to +% generate an isotropic image from either a NIfTI format image or +% an ANALYZE format image. +% +% The resliced NIfTI file will always be in RAS orientation. +% +% This program only supports real integer or floating-point data type. +% For other data type, the program will exit with an error message +% "Transform of this NIFTI data is not supported by the program". +% +% Usage: reslice_nii(old_fn, new_fn, [voxel_size], [verbose], [bg], ... +% [method], [img_idx], [preferredForm]); +% +% old_fn - filename for original NIfTI file +% +% new_fn - filename for resliced NIfTI file +% +% voxel_size (optional) - size of a voxel in millimeter along x y z +% direction for resliced NIfTI file. 'voxel_size' will use +% the minimum voxel_size in original NIfTI header, +% if it is default or empty. +% +% verbose (optional) - 1, 0 +% 1: show transforming progress in percentage +% 2: progress will not be displayed +% 'verbose' is 1 if it is default or empty. +% +% bg (optional) - background voxel intensity in any extra corner that +% is caused by 3D interpolation. 0 in most cases. 'bg' +% will be the average of two corner voxel intensities +% in original image volume, if it is default or empty. +% +% method (optional) - 1, 2, or 3 +% 1: for Trilinear interpolation +% 2: for Nearest Neighbor interpolation +% 3: for Fischer's Bresenham interpolation +% 'method' is 1 if it is default or empty. +% +% img_idx (optional) - a numerical array of image volume indices. Only +% the specified volumes will be loaded. All available image +% volumes will be loaded, if it is default or empty. +% +% The number of images scans can be obtained from get_nii_frame.m, +% or simply: hdr.dime.dim(5). +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jshen@research.baycrest.org) +% +function reslice_nii(old_fn, new_fn, voxel_size, verbose, bg, method, img_idx, preferredForm) + + if ~exist('old_fn','var') | ~exist('new_fn','var') + error('Usage: reslice_nii(old_fn, new_fn, [voxel_size], [verbose], [bg], [method], [img_idx])'); + end + + if ~exist('method','var') | isempty(method) + method = 1; + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + end + + if ~exist('verbose','var') | isempty(verbose) + verbose = 1; + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + nii = load_nii_no_xform(old_fn, img_idx, 0, preferredForm); + + if ~ismember(nii.hdr.dime.datatype, [2,4,8,16,64,256,512,768]) + error('Transform of this NIFTI data is not supported by the program.'); + end + + if ~exist('voxel_size','var') | isempty(voxel_size) + voxel_size = abs(min(nii.hdr.dime.pixdim(2:4)))*ones(1,3); + elseif length(voxel_size) < 3 + voxel_size = abs(voxel_size(1))*ones(1,3); + end + + if ~exist('bg','var') | isempty(bg) + bg = mean([nii.img(1) nii.img(end)]); + end + + old_M = nii.hdr.hist.old_affine; + + if nii.hdr.dime.dim(5) > 1 + for i = 1:nii.hdr.dime.dim(5) + if verbose + fprintf('Reslicing %d of %d volumes.\n', i, nii.hdr.dime.dim(5)); + end + + [img(:,:,:,i) M] = ... + affine(nii.img(:,:,:,i), old_M, voxel_size, verbose, bg, method); + end + else + [img M] = affine(nii.img, old_M, voxel_size, verbose, bg, method); + end + + new_dim = size(img); + nii.img = img; + nii.hdr.dime.dim(2:4) = new_dim(1:3); + nii.hdr.dime.datatype = 16; + nii.hdr.dime.bitpix = 32; + nii.hdr.dime.pixdim(2:4) = voxel_size(:)'; + nii.hdr.dime.glmax = max(img(:)); + nii.hdr.dime.glmin = min(img(:)); + nii.hdr.hist.qform_code = 0; + nii.hdr.hist.sform_code = 1; + nii.hdr.hist.srow_x = M(1,:); + nii.hdr.hist.srow_y = M(2,:); + nii.hdr.hist.srow_z = M(3,:); + nii.hdr.hist.new_affine = M; + + save_nii(nii, new_fn); + + return; % reslice_nii + + +%-------------------------------------------------------------------- +function [nii] = load_nii_no_xform(filename, img_idx, old_RGB, preferredForm) + + if ~exist('filename','var'), + error('Usage: [nii] = load_nii(filename, [img_idx], [old_RGB])'); + end + + if ~exist('img_idx','var'), img_idx = []; end + if ~exist('old_RGB','var'), old_RGB = 0; end + if ~exist('preferredForm','var'), preferredForm= 's'; end % Jeff + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + % Read the header extension + % +% nii.ext = load_nii_ext(filename); + + % Read the dataset body + % + [nii.img,nii.hdr] = ... + load_nii_img(nii.hdr,nii.filetype,nii.fileprefix,nii.machine,img_idx,'','','',old_RGB); + + % Perform some of sform/qform transform + % +% nii = xform_nii(nii, preferredForm); + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + + % fix fileprefix so it doesn't point to temp location + % + nii.fileprefix = gzFileName(1:end-7); + rmdir(tmpDir,'s'); + end + + + hdr = nii.hdr; + + % NIFTI can have both sform and qform transform. This program + % will check sform_code prior to qform_code by default. + % + % If user specifys "preferredForm", user can then choose the + % priority. - Jeff + % + useForm=[]; % Jeff + + if isequal(preferredForm,'S') + if isequal(hdr.hist.sform_code,0) + error('User requires sform, sform not set in header'); + else + useForm='s'; + end + end % Jeff + + if isequal(preferredForm,'Q') + if isequal(hdr.hist.qform_code,0) + error('User requires sform, sform not set in header'); + else + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'s') + if hdr.hist.sform_code > 0 + useForm='s'; + elseif hdr.hist.qform_code > 0 + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'q') + if hdr.hist.qform_code > 0 + useForm='q'; + elseif hdr.hist.sform_code > 0 + useForm='s'; + end + end % Jeff + + if isequal(useForm,'s') + R = [hdr.hist.srow_x(1:3) + hdr.hist.srow_y(1:3) + hdr.hist.srow_z(1:3)]; + + T = [hdr.hist.srow_x(4) + hdr.hist.srow_y(4) + hdr.hist.srow_z(4)]; + + nii.hdr.hist.old_affine = [ [R;[0 0 0]] [T;1] ]; + + elseif isequal(useForm,'q') + b = hdr.hist.quatern_b; + c = hdr.hist.quatern_c; + d = hdr.hist.quatern_d; + + if 1.0-(b*b+c*c+d*d) < 0 + if abs(1.0-(b*b+c*c+d*d)) < 1e-5 + a = 0; + else + error('Incorrect quaternion values in this NIFTI data.'); + end + else + a = sqrt(1.0-(b*b+c*c+d*d)); + end + + qfac = hdr.dime.pixdim(1); + i = hdr.dime.pixdim(2); + j = hdr.dime.pixdim(3); + k = qfac * hdr.dime.pixdim(4); + + R = [a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c + 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b + 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b]; + + T = [hdr.hist.qoffset_x + hdr.hist.qoffset_y + hdr.hist.qoffset_z]; + + nii.hdr.hist.old_affine = [ [R * diag([i j k]);[0 0 0]] [T;1] ]; + + elseif nii.filetype == 0 & exist([nii.fileprefix '.mat'],'file') + load([nii.fileprefix '.mat']); % old SPM affine matrix + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + nii.hdr.hist.old_affine = M; + + else + M = diag(hdr.dime.pixdim(2:5)); + M(1:3,4) = -M(1:3,1:3)*(hdr.hist.originator(1:3)-1)'; + M(4,4) = 1; + nii.hdr.hist.old_affine = M; + end + + return % load_nii_no_xform + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m new file mode 100755 index 00000000..cf0f7019 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m @@ -0,0 +1,179 @@ +% Imbed a file menu to any figure. If file menu exist, it will append +% to the existing file menu. This file menu includes: Copy to clipboard, +% print, save, close etc. +% +% Usage: rri_file_menu(fig); +% +% rri_file_menu(fig,0) means no 'Close' menu. +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +%-------------------------------------------------------------------- + +function rri_file_menu(action, varargin) + + if isnumeric(action) + fig = action; + action = 'init'; + end + + % clear the message line, + % + h = findobj(gcf,'Tag','MessageLine'); + set(h,'String',''); + + if ~strcmp(action, 'init') + set(gcbf, 'InvertHardcopy','off'); +% set(gcbf, 'PaperPositionMode','auto'); + end + + switch action + case {'init'} + if nargin > 1 + init(fig, 1); % no 'close' menu + else + init(fig, 0); + end + case {'print_fig'} + printdlg(gcbf); + case {'copy_fig'} + copy_fig; + case {'export_fig'} + export_fig; + end + + return % rri_file_menu + + +%------------------------------------------------ +% +% Create (or append) File menu +% +function init(fig, no_close) + + % search for file menu + % + h_file = []; + menuitems = findobj(fig, 'type', 'uimenu'); + + for i=1:length(menuitems) + filelabel = get(menuitems(i),'label'); + + if strcmpi(strrep(filelabel, '&', ''), 'file') + h_file = menuitems(i); + break; + end + end + + set(fig, 'menubar', 'none'); + + if isempty(h_file) + if isempty(menuitems) + h_file = uimenu('parent', fig, 'label', 'File'); + else + h_file = uimenu('parent', fig, 'label', 'Copy Figure'); + end + + h1 = uimenu('parent', h_file, ... + 'callback','rri_file_menu(''copy_fig'');', ... + 'label','Copy to Clipboard'); + else + h1 = uimenu('parent', h_file, ... + 'callback','rri_file_menu(''copy_fig'');', ... + 'separator','on', ... + 'label','Copy to Clipboard'); + end + + h2 = uimenu(h_file, ... + 'callback','pagesetupdlg(gcbf);', ... + 'label','Page Setup...'); + + h2 = uimenu(h_file, ... + 'callback','printpreview(gcbf);', ... + 'label','Print Preview...'); + + h2 = uimenu('parent', h_file, ... + 'callback','printdlg(gcbf);', ... + 'label','Print Figure ...'); + + h2 = uimenu('parent', h_file, ... + 'callback','rri_file_menu(''export_fig'');', ... + 'label','Save Figure ...'); + + arch = computer; + if ~strcmpi(arch(1:2),'PC') + set(h1, 'enable', 'off'); + end + + if ~no_close + h1 = uimenu('parent', h_file, ... + 'callback','close(gcbf);', ... + 'separator','on', ... + 'label','Close'); + end + + return; % init + + +%------------------------------------------------ +% +% Copy to clipboard +% +function copy_fig + + arch = computer; + if(~strcmpi(arch(1:2),'PC')) + error('copy to clipboard can only be used under MS Windows'); + return; + end + + print -noui -dbitmap; + + return % copy_fig + + +%------------------------------------------------ +% +% Save as an image file +% +function export_fig + + curr = pwd; + if isempty(curr) + curr = filesep; + end + + [selected_file, selected_path] = rri_select_file(curr,'Save As'); + + if isempty(selected_file) | isempty(selected_path) + return; + end + + filename = [selected_path selected_file]; + + if(exist(filename,'file')==2) % file exist + + dlg_title = 'Confirm File Overwrite'; + msg = ['File ',filename,' exist. Are you sure you want to overwrite it?']; + response = questdlg(msg,dlg_title,'Yes','No','Yes'); + + if(strcmp(response,'No')) + return; + end + + end + + old_pointer = get(gcbf,'pointer'); + set(gcbf,'pointer','watch'); + + try + saveas(gcbf,filename); + catch + msg = 'ERROR: Cannot save file'; + set(findobj(gcf,'Tag','MessageLine'),'String',msg); + end + + set(gcbf,'pointer',old_pointer); + + return; % export_fig + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m new file mode 100755 index 00000000..b497c74a --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m @@ -0,0 +1,106 @@ +% Convert image of different orientations to standard Analyze orientation +% +% Usage: nii = rri_orient(nii); + +% Jimmy Shen (jimmy@rotman-baycrest.on.ca), 26-APR-04 +%___________________________________________________________________ + +function [nii, orient, pattern] = rri_orient(nii, varargin) + + if nargin > 1 + pattern = varargin{1}; + else + pattern = []; + end + + if(nargin > 2) + orient = varargin{2}; + if(length(find(orient>6)) || length(find(orient<1))) %value checking + orient=[1 2 3]; %set to default if bogus values set + end + else + orient = [1 2 3]; + end + + + dim = double(nii.hdr.dime.dim([2:4])); + + if ~isempty(pattern) & ~isequal(length(pattern), prod(dim)) + return; + end + + % get orient of the current image + % + if isequal(orient, [1 2 3]) + orient = rri_orient_ui; + pause(.1); + end + + % no need for conversion + % + if isequal(orient, [1 2 3]) + return; + end + + if isempty(pattern) + pattern = 1:prod(dim); + end + + pattern = reshape(pattern, dim); + img = nii.img; + + % calculate after flip orient + % + rot_orient = mod(orient + 2, 3) + 1; + + % do flip: + % + flip_orient = orient - rot_orient; + + for i = 1:3 + if flip_orient(i) + pattern = flipdim(pattern, i); + img = flipdim(img, i); + end + end + + % get index of orient (do inverse) + % + [tmp rot_orient] = sort(rot_orient); + + % do rotation: + % + pattern = permute(pattern, rot_orient); + img = permute(img, [rot_orient 4 5 6]); + + % rotate resolution, or 'dim' + % + new_dim = nii.hdr.dime.dim([2:4]); + new_dim = new_dim(rot_orient); + nii.hdr.dime.dim([2:4]) = new_dim; + + % rotate voxel_size, or 'pixdim' + % + tmp = nii.hdr.dime.pixdim([2:4]); + tmp = tmp(rot_orient); + nii.hdr.dime.pixdim([2:4]) = tmp; + + % re-calculate originator + % + tmp = nii.hdr.hist.originator([1:3]); + tmp = tmp(rot_orient); + flip_orient = flip_orient(rot_orient); + + for i = 1:3 + if flip_orient(i) & ~isequal(double(tmp(i)), 0) + tmp(i) = int16(double(new_dim(i)) - double(tmp(i)) + 1); + end + end + + nii.hdr.hist.originator([1:3]) = tmp; + + nii.img = img; + pattern = pattern(:); + + return; % rri_orient + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m new file mode 100755 index 00000000..8f0c9223 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m @@ -0,0 +1,251 @@ +% Return orientation of the current image: +% orient is orientation 1x3 matrix, in that: +% Three elements represent: [x y z] +% Element value: 1 - Left to Right; 2 - Posterior to Anterior; +% 3 - Inferior to Superior; 4 - Right to Left; +% 5 - Anterior to Posterior; 6 - Superior to Inferior; +% e.g.: +% Standard RAS Orientation: [1 2 3] +% Standard RHOS Orientation: [2 4 3] + +% Jimmy Shen (jimmy@rotman-baycrest.on.ca), 26-APR-04 +% +function orient = rri_orient_ui(varargin) + + if nargin == 0 + init; + orient_ui_fig = gcf; + uiwait; % wait for user finish + + orient = getappdata(gcf, 'orient'); + + if isempty(orient) + orient = [1 2 3]; + end + + if ishandle(orient_ui_fig) + close(gcf); + end + + return; + end + + action = varargin{1}; + + if strcmp(action, 'done') + click_done; + elseif strcmp(action, 'cancel') + uiresume; + end + + return; % rri_orient_ui + + +%---------------------------------------------------------------------- +function init + + save_setting_status = 'on'; + rri_orient_pos = []; + + try + load('pls_profile'); + catch + end + + try + load('rri_pos_profile'); + catch + end + + if ~isempty(rri_orient_pos) & strcmp(save_setting_status,'on') + + pos = rri_orient_pos; + + else + + w = 0.35; + h = 0.4; + x = (1-w)/2; + y = (1-h)/2; + + pos = [x y w h]; + + end + + handles.figure = figure('Color',[0.8 0.8 0.8], ... + 'Units','normal', ... + 'Name', 'Convert to standard RAS orientation', ... + 'NumberTitle','off', ... + 'MenuBar','none', ... + 'Position',pos, ... + 'WindowStyle', 'normal', ... + 'ToolBar','none'); + + h0 = handles.figure; + Font.FontUnits = 'point'; + Font.FontSize = 12; + + margin = .1; + line_num = 6; + line_ht = (1 - margin*2) / line_num; + + x = margin; + y = 1 - margin - line_ht; + w = 1 - margin * 2; + h = line_ht * .7; + + pos = [x y w h]; + + handles.Ttit = uicontrol('parent', h0, ... + 'style','text', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'background', [0.8 0.8 0.8], ... + 'string', 'Please input orientation of the current image:'); + + y = y - line_ht; + w = .2; + + pos = [x y w h]; + + handles.Tx_orient = uicontrol('parent', h0, ... + 'style','text', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'background', [0.8 0.8 0.8], ... + 'string', 'X Axes:'); + + y = y - line_ht; + + pos = [x y w h]; + + handles.Ty_orient = uicontrol('parent', h0, ... + 'style','text', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'background', [0.8 0.8 0.8], ... + 'string', 'Y Axes:'); + + y = y - line_ht; + + pos = [x y w h]; + + handles.Tz_orient = uicontrol('parent', h0, ... + 'style','text', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'background', [0.8 0.8 0.8], ... + 'string', 'Z Axes:'); + + choice = { 'From Left to Right', 'From Posterior to Anterior', ... + 'From Inferior to Superior', 'From Right to Left', ... + 'From Anterior to Posterior', 'From Superior to Inferior' }; + + y = 1 - margin - line_ht; + y = y - line_ht; + w = 1 - margin - x - w; + x = 1 - margin - w; + + pos = [x y w h]; + + handles.x_orient = uicontrol('parent', h0, ... + 'style','popupmenu', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'string', choice, ... + 'value', 1, ... + 'background', [1 1 1]); + + y = y - line_ht; + + pos = [x y w h]; + + handles.y_orient = uicontrol('parent', h0, ... + 'style','popupmenu', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'string', choice, ... + 'value', 2, ... + 'background', [1 1 1]); + + y = y - line_ht; + + pos = [x y w h]; + + handles.z_orient = uicontrol('parent', h0, ... + 'style','popupmenu', ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','left',... + 'string', choice, ... + 'value', 3, ... + 'background', [1 1 1]); + + x = margin; + y = y - line_ht * 1.5; + w = .3; + + pos = [x y w h]; + + handles.done = uicontrol('parent', h0, ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','center',... + 'callback', 'rri_orient_ui(''done'');', ... + 'string', 'Done'); + + x = 1 - margin - w; + + pos = [x y w h]; + + handles.cancel = uicontrol('parent', h0, ... + 'unit', 'normal', ... + Font, ... + 'Position',pos, ... + 'HorizontalAlignment','center',... + 'callback', 'rri_orient_ui(''cancel'');', ... + 'string', 'Cancel'); + + setappdata(h0, 'handles', handles); + setappdata(h0, 'orient', [1 2 3]); + + return; % init + + +%---------------------------------------------------------------------- +function click_done + + handles = getappdata(gcf, 'handles'); + + x_orient = get(handles.x_orient, 'value'); + y_orient = get(handles.y_orient, 'value'); + z_orient = get(handles.z_orient, 'value'); + + orient = [x_orient y_orient z_orient]; + test_orient = [orient, orient + 3]; + test_orient = mod(test_orient, 3); + + if length(unique(test_orient)) ~= 3 + msgbox('Please don''t choose same or opposite direction','Error','modal'); + return; + end + + setappdata(gcf, 'orient', [x_orient y_orient z_orient]); + uiresume; + + return; % click_done + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m new file mode 100755 index 00000000..7e462c47 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m @@ -0,0 +1,636 @@ +function [selected_file, selected_path] = rri_select_file(varargin) +% +% USAGE: [selected_file, selected_path] = ... +% rri_select_file(dir_name, fig_title) +% +% Allow user to select a file from a list of Matlab competible +% file format +% +% Example: +% +% [selected_file, selected_path] = ... +% rri_select_file('/usr','Select Data File'); +% +% See Also RRI_GETFILES + +% -- Created June 2001 by Wilkin Chau, Rotman Research Institute +% +% use rri_select_file to open & save Matlab recognized format +% -- Modified Dec 2002 by Jimmy Shen, Rotman Research Institute +% + + if nargin == 0 | ischar(varargin{1}) % create rri_select_file figure + + dir_name = ''; + fig_title = 'Select a File'; + + if nargin > 0 + dir_name = varargin{1}; + end + + if nargin > 1 + fig_title = varargin{2}; + end + + Init(fig_title,dir_name); + uiwait; % wait for user finish + + selected_path = getappdata(gcf,'SelectedDirectory'); + selected_file = getappdata(gcf,'SelectedFile'); + + cd (getappdata(gcf,'StartDirectory')); + close(gcf); + return; + end; + + % clear the message line, + % + h = findobj(gcf,'Tag','MessageLine'); + set(h,'String',''); + + action = varargin{1}{1}; + + % change 'File format': + % update 'Files' & 'File selection' based on file pattern + % + if strcmp(action,'EditFilter'), + EditFilter; + + % run delete_fig when figure is closing + % + elseif strcmp(action,'delete_fig'), + delete_fig; + + % select 'Directories': + % go into the selected dir + % update 'Files' & 'File selection' based on file pattern + % + elseif strcmp(action,'select_dir'), + select_dir; + + % select 'Files': + % update 'File selection' + % + elseif strcmp(action,'select_file'), + select_file; + + % change 'File selection': + % if it is a file, select that, + % if it is more than a file (*), select those, + % if it is a directory, select based on file pattern + % + elseif strcmp(action,'EditSelection'), + EditSelection; + + % clicked 'Select' + % + elseif strcmp(action,'DONE_BUTTON_PRESSED'), + h = findobj(gcf,'Tag','SelectionEdit'); + [filepath,filename,fileext] = fileparts(get(h,'String')); + + if isempty(filepath) | isempty(filename) | isempty(fileext) + setappdata(gcf,'SelectedDirectory',[]); + setappdata(gcf,'SelectedFile',[]); + else + if ~strcmp(filepath(end),filesep) % not end with filesep + filepath = [filepath filesep]; % add a filesep to filepath + end + + setappdata(gcf,'SelectedDirectory',filepath); + setappdata(gcf,'SelectedFile',[filename fileext]); + end + + if getappdata(gcf,'ready') % ready to exit + uiresume; + end + + % clicked 'cancel' + % + elseif strcmp(action,'CANCEL_BUTTON_PRESSED'), + setappdata(gcf,'SelectedDirectory',[]); + setappdata(gcf,'SelectedFile',[]); + set(findobj(gcf,'Tag','FileList'),'String',''); + uiresume; + end; + + return; + + +% -------------------------------------------------------------------- +function Init(fig_title,dir_name), + + StartDirectory = pwd; + if isempty(StartDirectory), + StartDirectory = filesep; + end; + + filter_disp = {'JPEG image (*.jpg)', ... + 'TIFF image, compressed (*.tif)', ... + 'EPS Level 1 (*.eps)', ... + 'Adobe Illustrator 88 (*.ai)', ... + 'Enhanced metafile (*.emf)', ... + 'Matlab Figure (*.fig)', ... + 'Matlab M-file (*.m)', ... + 'Portable bitmap (*.pbm)', ... + 'Paintbrush 24-bit (*.pcx)', ... + 'Portable Graymap (*.pgm)', ... + 'Portable Network Graphics (*.png)', ... + 'Portable Pixmap (*.ppm)', ... + }; + + filter_string = {'*.jpg', ... + '*.tif', ... + '*.eps', ... + '*.ai', ... + '*.emf', ... + '*.fig', ... + '*.m', ... + '*.pbm', ... + '*.pcx', ... + '*.pgm', ... + '*.png', ... + '*.ppm', ... + }; + +% filter_disp = char(filter_disp); + filter_string = char(filter_string); + + margine = 0.05; + line_height = 0.07; + char_height = line_height*0.8; + + save_setting_status = 'on'; + rri_select_file_pos = []; + + try + load('pls_profile'); + catch + end + + if ~isempty(rri_select_file_pos) & strcmp(save_setting_status,'on') + + pos = rri_select_file_pos; + + else + + w = 0.4; + h = 0.6; + x = (1-w)/2; + y = (1-h)/2; + + pos = [x y w h]; + + end + + h0 = figure('parent',0, 'Color',[0.8 0.8 0.8], ... + 'Units','normal', ... + 'Name',fig_title, ... + 'NumberTitle','off', ... + 'MenuBar','none', ... + 'Position', pos, ... + 'deleteFcn','rri_select_file({''delete_fig''});', ... + 'WindowStyle', 'modal', ... + 'Tag','GetFilesFigure', ... + 'ToolBar','none'); + + x = margine; + y = 1 - 1*line_height - margine; + w = 1-2*x; + h = char_height; + + pos = [x y w h]; + + h1 = uicontrol('Parent',h0, ... % Filter Label + 'Style','text', ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'Position', pos, ... + 'String','Choose one of the file format:', ... + 'Tag','FilterLabel'); + + y = 1 - 2*line_height - margine + line_height*0.2; + w = 1-2*x; + + pos = [x y w h]; + + h_filter = uicontrol('Parent',h0, ... % Filter list + 'Style','popupmenu', ... + 'Units','normal', ... + 'BackgroundColor',[1 1 1], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'Position', pos, ... + 'String', filter_disp, ... + 'user', filter_string, ... + 'value', 1, ... + 'Callback','rri_select_file({''EditFilter''});', ... + 'Tag','FilterEdit'); + + y = 1 - 3*line_height - margine; + w = 0.5 - x - margine/2; + + pos = [x y w h]; + + h1 = uicontrol('Parent',h0, ... % Directory Label + 'Style','text', ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'ListboxTop',0, ... + 'Position', pos, ... + 'String','Directories', ... + 'Tag','DirectoryLabel'); + + x = 0.5; + y = 1 - 3*line_height - margine; + w = 0.5 - margine; + + pos = [x y w h]; + + h1 = uicontrol('Parent',h0, ... % File Label + 'Style','text', ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'ListboxTop',0, ... + 'Position', pos, ... + 'String','Files', ... + 'Tag','FileLabel'); + + x = margine; + y = 4*line_height + margine; + w = 0.5 - x - margine/2; + h = 1 - 7*line_height - 2*margine; + + pos = [x y w h]; + + h_dir = uicontrol('Parent',h0, ... % Directory Listbox + 'Style','listbox', ... + 'Units','normal', ... + 'fontunit','normal', ... + 'FontSize',0.08, ... + 'HorizontalAlignment','left', ... + 'Interruptible', 'off', ... + 'ListboxTop',1, ... + 'Position', pos, ... + 'String', '', ... + 'Callback','rri_select_file({''select_dir''});', ... + 'Tag','DirectoryList'); + + x = 0.5; + y = 4*line_height + margine; + w = 0.5 - margine; + h = 1 - 7*line_height - 2*margine; + + pos = [x y w h]; + + h_file = uicontrol('Parent',h0, ... % File Listbox + 'Style','listbox', ... + 'Units','normal', ... + 'fontunit','normal', ... + 'FontSize',0.08, ... + 'HorizontalAlignment','left', ... + 'ListboxTop',1, ... + 'Position', pos, ... + 'String', '', ... + 'Callback','rri_select_file({''select_file''});', ... + 'Tag','FileList'); + + x = margine; + y = 3*line_height + margine - line_height*0.2; + w = 1-2*x; + h = char_height; + + pos = [x y w h]; + + h1 = uicontrol('Parent',h0, ... % Selection Label + 'Style','text', ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'Position', pos, ... + 'String','File you selected:', ... + 'Tag','SelectionLabel'); + + y = 2*line_height + margine; + w = 1-2*x; + + pos = [x y w h]; + + h_select = uicontrol('Parent',h0, ... % Selection Edit + 'Style','edit', ... + 'Units','normal', ... + 'BackgroundColor',[1 1 1], ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'HorizontalAlignment','left', ... + 'Position', pos, ... + 'String', '', ... + 'Callback','rri_select_file({''EditSelection''});', ... + 'Tag','SelectionEdit'); + + x = 2*margine; + y = line_height/2 + margine; + w = 0.2; + h = line_height; + + pos = [x y w h]; + + h_done = uicontrol('Parent',h0, ... % DONE + 'Units','normal', ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'ListboxTop',0, ... + 'Position', pos, ... + 'HorizontalAlignment','center', ... + 'String','Save', ... % 'Select', ... + 'Callback','rri_select_file({''DONE_BUTTON_PRESSED''});', ... + 'Tag','DONEButton'); + + x = 1 - x - w; + + pos = [x y w h]; + + h_cancel = uicontrol('Parent',h0, ... % CANCEL + 'Units','normal', ... + 'fontunit','normal', ... + 'FontSize',0.5, ... + 'ListboxTop',0, ... + 'Position', pos, ... + 'HorizontalAlignment','center', ... + 'String','Cancel', ... + 'Callback','rri_select_file({''CANCEL_BUTTON_PRESSED''});', ... + 'Tag','CANCELButton'); + + if isempty(dir_name) + dir_name = StartDirectory; + end + + set(h_select,'string',dir_name); + + filter_select = get(h_filter,'value'); + filter_pattern = filter_string(filter_select,:); + + setappdata(gcf,'FilterPattern',deblank(filter_pattern)); + setappdata(gcf,'filter_string',filter_string); + + setappdata(gcf,'h_filter', h_filter); + setappdata(gcf,'h_dir', h_dir); + setappdata(gcf,'h_file', h_file); + setappdata(gcf,'h_select', h_select); + setappdata(gcf,'h_done', h_done); + setappdata(gcf,'h_cancel', h_cancel); + setappdata(gcf,'StartDirectory',StartDirectory); + + EditSelection; + + h_file = getappdata(gcf,'h_file'); + if isempty(get(h_file,'string')) + setappdata(gcf,'ready',0); + else + setappdata(gcf,'ready',1); + end + + return; % Init + + +% called by all the actions, to update 'Directories' or 'Files' +% based on filter_pattern. Select first file in filelist. +% +% -------------------------------------------------------------------- + +function update_dirlist; + + filter_path = getappdata(gcf,'curr_dir'); + filter_pattern = getappdata(gcf,'FilterPattern'); + + if exist(filter_pattern) == 2 % user input specific filename + is_single_file = 1; % need manually take path out later + else + is_single_file = 0; + end + + % take the file path out from filter_pattern + % + [fpath fname fext] = fileparts(filter_pattern); + filter_pattern = [fname fext]; + + dir_struct = dir(filter_path); + if isempty(dir_struct) + msg = 'ERROR: Directory not found!'; + uiwait(msgbox(msg,'File Selection Error','modal')); + return; + end; + + old_pointer = get(gcf,'Pointer'); + set(gcf,'Pointer','watch'); + + dir_list = dir_struct(find([dir_struct.isdir] == 1)); + [sorted_dir_names,sorted_dir_index] = sortrows({dir_list.name}'); + + dir_struct = dir([filter_path filesep filter_pattern]); + if isempty(dir_struct) + sorted_file_names = []; + else + file_list = dir_struct(find([dir_struct.isdir] == 0)); + + if is_single_file % take out path + tmp = file_list.name; + [fpath fname fext] = fileparts(tmp); + file_list.name = [fname fext]; + end + + [sorted_file_names,sorted_file_index] = sortrows({file_list.name}'); + end; + + disp_dir_names = []; % if need full path, use this + % instead of sorted_dir_names + for i=1:length(sorted_dir_names) + tmp = [filter_path filesep sorted_dir_names{i}]; + disp_dir_names = [disp_dir_names {tmp}]; + end + + h = findobj(gcf,'Tag','DirectoryList'); + set(h,'String',sorted_dir_names,'Value',1); + + h = findobj(gcf,'Tag','FileList'); + set(h,'String',sorted_file_names,'value',1); + + h_select = getappdata(gcf,'h_select'); + if strcmp(filter_path(end),filesep) % filepath end with filesep + filter_path = filter_path(1:end-1); % take filesep out + end + + if isempty(sorted_file_names) + set(h_select,'string',[filter_path filesep]); + else + set(h_select,'string',[filter_path filesep sorted_file_names{1}]); + end + + set(gcf,'Pointer',old_pointer); + + return; % update_dirlist + + +% change 'File format': +% update 'Files' & 'File selection' based on file pattern +% +% -------------------------------------------------------------------- + +function EditFilter() + + filter_select = get(gcbo,'value'); + filter_string = getappdata(gcf,'filter_string'); + filter_pattern = filter_string(filter_select,:); + filter_path = getappdata(gcf,'curr_dir'); + + % update filter_pattern + setappdata(gcf,'FilterPattern',deblank(filter_pattern)); + + if isempty(filter_path), + filter_path = filesep; + end; + + update_dirlist; + + h_file = getappdata(gcf,'h_file'); + if isempty(get(h_file,'string')) + setappdata(gcf,'ready',0); + else + setappdata(gcf,'ready',1); + end + + return; % EditFilter + + +% select 'Directories': +% go into the selected dir +% update 'Files' & 'File selection' based on file pattern +% +% -------------------------------------------------------------------- + +function select_dir() + + listed_dir = get(gcbo,'String'); + selected_dir_idx = get(gcbo,'Value'); + selected_dir = listed_dir{selected_dir_idx}; + curr_dir = getappdata(gcf,'curr_dir'); + + % update the selection box + % + try + cd ([curr_dir filesep selected_dir]); + catch + msg = 'ERROR: Cannot access directory'; + uiwait(msgbox(msg,'File Selection Error','modal')); + return; + end; + + if isempty(pwd) + curr_dir = filesep; + else + curr_dir = pwd; + end; + + setappdata(gcf,'curr_dir',curr_dir); + update_dirlist; + + h_file = getappdata(gcf,'h_file'); + if isempty(get(h_file,'string')) + setappdata(gcf,'ready',0); + else + setappdata(gcf,'ready',1); + end + + return; % select_dir + + +% select 'Files': +% update 'File selection' +% +% -------------------------------------------------------------------- + +function select_file() + + setappdata(gcf,'ready',1); + listed_file = get(gcbo,'String'); + selected_file_idx = get(gcbo,'Value'); + selected_file = listed_file{selected_file_idx}; + curr_dir = getappdata(gcf,'curr_dir'); + + if strcmp(curr_dir(end),filesep) % filepath end with filesep + curr_dir = curr_dir(1:end-1); % take filesep out + end + + h_select = getappdata(gcf,'h_select'); + set(h_select,'string',[curr_dir filesep selected_file]); + + return; % select_file + + +% change 'File selection': +% if it is a file, select that, +% if it is more than a file (*), select those, +% if it is a directory, select based on file pattern +% +% -------------------------------------------------------------------- + +function EditSelection() + + filter_string = getappdata(gcf,'filter_string'); + h_select = getappdata(gcf,'h_select'); + selected_file = get(h_select,'string'); + + if exist(selected_file) == 7 % if user enter a dir + setappdata(gcf,'ready',0); + setappdata(gcf,'curr_dir',selected_file); % get new dir + update_dirlist; + else + + setappdata(gcf,'ready',1); + + [fpath fname fext]= fileparts(selected_file); + if exist(fpath) ~=7 % fpath is not a dir + setappdata(gcf,'ready',0); + msg = 'ERROR: Cannot access directory'; + uiwait(msgbox(msg,'File Selection Error','modal')); + end + + % if the file format user entered is not supported by matlab + if isempty(strmatch(['*',fext],filter_string,'exact')) + setappdata(gcf,'ready',0); + msg = 'ERROR: File format is not supported by Matlab.'; + uiwait(msgbox(msg,'File Selection Error','modal')); + end + + end + + return; % EditSelection + + +% -------------------------------------------------------------------- + +function delete_fig() + + try + load('pls_profile'); + pls_profile = which('pls_profile.mat'); + + rri_select_file_pos = get(gcbf,'position'); + + save(pls_profile, '-append', 'rri_select_file_pos'); + catch + end + + return; + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m new file mode 100755 index 00000000..92c91454 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m @@ -0,0 +1,92 @@ +% rri_xhair: create a pair of full_cross_hair at point [x y] in +% axes h_ax, and return xhair struct +% +% Usage: xhair = rri_xhair([x y], xhair, h_ax); +% +% If omit xhair, rri_xhair will create a pair of xhair; otherwise, +% rri_xhair will update the xhair. If omit h_ax, current axes will +% be used. +% + +% 24-nov-2003 jimmy (jimmy@rotman-baycrest.on.ca) +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function xhair = rri_xhair(varargin) + + if nargin == 0 + error('Please enter a point position as first argument'); + return; + end + + if nargin > 0 + p = varargin{1}; + + if ~isnumeric(p) | length(p) ~= 2 + error('Invalid point position'); + return; + else + xhair = []; + end + end + + if nargin > 1 + xhair = varargin{2}; + + if ~isempty(xhair) + if ~isstruct(xhair) + error('Invalid xhair struct'); + return; + elseif ~isfield(xhair,'lx') | ~isfield(xhair,'ly') + error('Invalid xhair struct'); + return; + elseif ~ishandle(xhair.lx) | ~ishandle(xhair.ly) + error('Invalid xhair struct'); + return; + end + + lx = xhair.lx; + ly = xhair.ly; + else + lx = []; + ly = []; + end + end + + if nargin > 2 + h_ax = varargin{3}; + + if ~ishandle(h_ax) + error('Invalid axes handle'); + return; + elseif ~strcmp(lower(get(h_ax,'type')), 'axes') + error('Invalid axes handle'); + return; + end + else + h_ax = gca; + end + + x_range = get(h_ax,'xlim'); + y_range = get(h_ax,'ylim'); + + if ~isempty(xhair) + set(lx, 'ydata', [p(2) p(2)]); + set(ly, 'xdata', [p(1) p(1)]); + set(h_ax, 'selected', 'on'); + set(h_ax, 'selected', 'off'); + else + figure(get(h_ax,'parent')); + axes(h_ax); + + xhair.lx = line('xdata', x_range, 'ydata', [p(2) p(2)], ... + 'zdata', [11 11], 'color', [1 0 0], 'hittest', 'off'); + xhair.ly = line('xdata', [p(1) p(1)], 'ydata', y_range, ... + 'zdata', [11 11], 'color', [1 0 0], 'hittest', 'off'); + end + + set(h_ax,'xlim',x_range); + set(h_ax,'ylim',y_range); + + return; + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m new file mode 100755 index 00000000..f7b7ae04 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m @@ -0,0 +1,33 @@ +% Imbed a zoom menu to any figure. +% +% Usage: rri_zoom_menu(fig); +% + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +%-------------------------------------------------------------------- +function menu_hdl = rri_zoom_menu(fig) + + if isnumeric(fig) + menu_hdl = uimenu('Parent',fig, ... + 'Label','Zoom on', ... + 'Userdata', 1, ... + 'Callback','rri_zoom_menu(''zoom'');'); + + return; + end + + zoom_on_state = get(gcbo,'Userdata'); + + if (zoom_on_state == 1) + zoom on; + set(gcbo,'Userdata',0,'Label','Zoom off'); + set(gcbf,'pointer','crosshair'); + else + zoom off; + set(gcbo,'Userdata',1,'Label','Zoom on'); + set(gcbf,'pointer','arrow'); + end + + return % rri_zoom_menu + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m new file mode 100755 index 00000000..98f7aef9 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m @@ -0,0 +1,286 @@ +% Save NIFTI dataset. Support both *.nii and *.hdr/*.img file extension. +% If file extension is not provided, *.hdr/*.img will be used as default. +% +% Usage: save_nii(nii, filename, [old_RGB]) +% +% nii.hdr - struct with NIFTI header fields (from load_nii.m or make_nii.m) +% +% nii.img - 3D (or 4D) matrix of NIFTI data. +% +% filename - NIFTI file name. +% +% old_RGB - an optional boolean variable to handle special RGB data +% sequence [R1 R2 ... G1 G2 ... B1 B2 ...] that is used only by +% AnalyzeDirect (Analyze Software). Since both NIfTI and Analyze +% file format use RGB triple [R1 G1 B1 R2 G2 B2 ...] sequentially +% for each voxel, this variable is set to FALSE by default. If you +% would like the saved image only to be opened by AnalyzeDirect +% Software, set old_RGB to TRUE (or 1). It will be set to 0, if it +% is default or empty. +% +% Tip: to change the data type, set nii.hdr.dime.datatype, +% and nii.hdr.dime.bitpix to: +% +% 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN +% 1 Binary (ubit1, bitpix=1) % DT_BINARY +% 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 +% 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 +% 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 +% 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 +% 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 +% 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 +% 128 uint RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 +% 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 +% 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 +% 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 +% 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 +% 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 +% 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 +% 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 +% 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% - "old_RGB" related codes in "save_nii.m" are added by Mike Harms (2006.06.28) +% +function save_nii(nii, fileprefix, old_RGB) + + if ~exist('nii','var') | isempty(nii) | ~isfield(nii,'hdr') | ... + ~isfield(nii,'img') | ~exist('fileprefix','var') | isempty(fileprefix) + + error('Usage: save_nii(nii, filename, [old_RGB])'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''save_untouch_nii.m'' for the untouched structure.'); + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(fileprefix) > 2 & strcmp(fileprefix(end-2:end), '.gz') + + if ~strcmp(fileprefix(end-6:end), '.img.gz') & ... + ~strcmp(fileprefix(end-6:end), '.hdr.gz') & ... + ~strcmp(fileprefix(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + fileprefix = fileprefix(1:end-3); + end + end + + filetype = 1; + + % Note: fileprefix is actually the filename you want to save + % + if findstr('.nii',fileprefix) & strcmp(fileprefix(end-3:end), '.nii') + filetype = 2; + fileprefix(end-3:end)=''; + end + + if findstr('.hdr',fileprefix) & strcmp(fileprefix(end-3:end), '.hdr') + fileprefix(end-3:end)=''; + end + + if findstr('.img',fileprefix) & strcmp(fileprefix(end-3:end), '.img') + fileprefix(end-3:end)=''; + end + + write_nii(nii, filetype, fileprefix, old_RGB); + + % gzip output file if requested + % + if exist('gzFile', 'var') + if filetype == 1 + gzip([fileprefix, '.img']); + delete([fileprefix, '.img']); + gzip([fileprefix, '.hdr']); + delete([fileprefix, '.hdr']); + elseif filetype == 2 + gzip([fileprefix, '.nii']); + delete([fileprefix, '.nii']); + end; + end; + + if filetype == 1 + + % So earlier versions of SPM can also open it with correct originator + % + M=[[diag(nii.hdr.dime.pixdim(2:4)) -[nii.hdr.hist.originator(1:3).*nii.hdr.dime.pixdim(2:4)]'];[0 0 0 1]]; + save([fileprefix '.mat'], 'M'); + end + + return % save_nii + + +%----------------------------------------------------------------------------------- +function write_nii(nii, filetype, fileprefix, old_RGB) + + hdr = nii.hdr; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 511, + hdr.dime.bitpix = int16(96); precision = 'float32'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.glmax = round(double(max(nii.img(:)))); + hdr.dime.glmin = round(double(min(nii.img(:)))); + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + ScanDim = double(hdr.dime.dim(5)); % t + SliceDim = double(hdr.dime.dim(4)); % z + RowDim = double(hdr.dime.dim(3)); % y + PixelDim = double(hdr.dime.dim(2)); % x + SliceSz = double(hdr.dime.pixdim(4)); + RowSz = double(hdr.dime.pixdim(3)); + PixelSz = double(hdr.dime.pixdim(2)); + + x = 1:PixelDim; + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + if old_RGB + nii.img = permute(nii.img, [1 2 4 3 5 6 7 8]); + else + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + end + + if double(hdr.dime.datatype) == 511 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + if old_RGB + nii.img = permute(nii.img, [1 2 4 3 5 6 7 8]); + else + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if skip_bytes + fwrite(fid, zeros(1,skip_bytes), 'uint8'); + end + + fwrite(fid, nii.img, precision); +% fwrite(fid, nii.img, precision, skip_bytes); % error using skip + fclose(fid); + + return; % write_nii + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m new file mode 100755 index 00000000..4788649a --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m @@ -0,0 +1,38 @@ +% Save NIFTI header extension. +% +% Usage: save_nii_ext(ext, fid) +% +% ext - struct with NIFTI header extension fields. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_nii_ext(ext, fid) + + if ~exist('ext','var') | ~exist('fid','var') + error('Usage: save_nii_ext(ext, fid)'); + end + + if ~isfield(ext,'extension') | ~isfield(ext,'section') | ~isfield(ext,'num_ext') + error('Wrong header extension'); + end + + write_ext(ext, fid); + + return; % save_nii_ext + + +%--------------------------------------------------------------------- +function write_ext(ext, fid) + + fwrite(fid, ext.extension, 'uchar'); + + for i=1:ext.num_ext + fwrite(fid, ext.section(i).esize, 'int32'); + fwrite(fid, ext.section(i).ecode, 'int32'); + fwrite(fid, ext.section(i).edata, 'uchar'); + end + + return; % write_ext + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m new file mode 100755 index 00000000..d3160a2e --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m @@ -0,0 +1,227 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function save_nii_hdr(hdr, fid) + + if ~exist('hdr','var') | ~exist('fid','var') + error('Usage: save_nii_hdr(hdr, fid)'); + end + + if ~isequal(hdr.hk.sizeof_hdr,348), + error('hdr.hk.sizeof_hdr must be 348.'); + end + + if hdr.hist.qform_code == 0 & hdr.hist.sform_code == 0 + hdr.hist.sform_code = 1; + hdr.hist.srow_x(1) = hdr.dime.pixdim(2); + hdr.hist.srow_x(2) = 0; + hdr.hist.srow_x(3) = 0; + hdr.hist.srow_y(1) = 0; + hdr.hist.srow_y(2) = hdr.dime.pixdim(3); + hdr.hist.srow_y(3) = 0; + hdr.hist.srow_z(1) = 0; + hdr.hist.srow_z(2) = 0; + hdr.hist.srow_z(3) = hdr.dime.pixdim(4); + hdr.hist.srow_x(4) = (1-hdr.hist.originator(1))*hdr.dime.pixdim(2); + hdr.hist.srow_y(4) = (1-hdr.hist.originator(2))*hdr.dime.pixdim(3); + hdr.hist.srow_z(4) = (1-hdr.hist.originator(3))*hdr.dime.pixdim(4); + end + + write_header(hdr, fid); + + return; % save_nii_hdr + + +%--------------------------------------------------------------------- +function write_header(hdr, fid) + + % Original header structures + % struct dsr /* dsr = hdr */ + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + header_key(fid, hdr.hk); + image_dimension(fid, hdr.dime); + data_history(fid, hdr.hist); + + % check the file size is 348 bytes + % + fbytes = ftell(fid); + + if ~isequal(fbytes,348), + msg = sprintf('Header size is not 348 bytes.'); + warning(msg); + end + + return; % write_header + + +%--------------------------------------------------------------------- +function header_key(fid, hk) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + + fwrite(fid, hk.sizeof_hdr(1), 'int32'); % must be 348. + + % data_type = sprintf('%-10s',hk.data_type); % ensure it is 10 chars from left + % fwrite(fid, data_type(1:10), 'uchar'); + pad = zeros(1, 10-length(hk.data_type)); + hk.data_type = [hk.data_type char(pad)]; + fwrite(fid, hk.data_type(1:10), 'uchar'); + + % db_name = sprintf('%-18s', hk.db_name); % ensure it is 18 chars from left + % fwrite(fid, db_name(1:18), 'uchar'); + pad = zeros(1, 18-length(hk.db_name)); + hk.db_name = [hk.db_name char(pad)]; + fwrite(fid, hk.db_name(1:18), 'uchar'); + + fwrite(fid, hk.extents(1), 'int32'); + fwrite(fid, hk.session_error(1), 'int16'); + fwrite(fid, hk.regular(1), 'uchar'); % might be uint8 + + % fwrite(fid, hk.hkey_un0(1), 'uchar'); + % fwrite(fid, hk.hkey_un0(1), 'uint8'); + fwrite(fid, hk.dim_info(1), 'uchar'); + + return; % header_key + + +%--------------------------------------------------------------------- +function image_dimension(fid, dime) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width + % pixdim[2] - voxel height + % pixdim[3] - interslice distance + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + fwrite(fid, dime.dim(1:8), 'int16'); + fwrite(fid, dime.intent_p1(1), 'float32'); + fwrite(fid, dime.intent_p2(1), 'float32'); + fwrite(fid, dime.intent_p3(1), 'float32'); + fwrite(fid, dime.intent_code(1), 'int16'); + fwrite(fid, dime.datatype(1), 'int16'); + fwrite(fid, dime.bitpix(1), 'int16'); + fwrite(fid, dime.slice_start(1), 'int16'); + fwrite(fid, dime.pixdim(1:8), 'float32'); + fwrite(fid, dime.vox_offset(1), 'float32'); + fwrite(fid, dime.scl_slope(1), 'float32'); + fwrite(fid, dime.scl_inter(1), 'float32'); + fwrite(fid, dime.slice_end(1), 'int16'); + fwrite(fid, dime.slice_code(1), 'uchar'); + fwrite(fid, dime.xyzt_units(1), 'uchar'); + fwrite(fid, dime.cal_max(1), 'float32'); + fwrite(fid, dime.cal_min(1), 'float32'); + fwrite(fid, dime.slice_duration(1), 'float32'); + fwrite(fid, dime.toffset(1), 'float32'); + fwrite(fid, dime.glmax(1), 'int32'); + fwrite(fid, dime.glmin(1), 'int32'); + + return; % image_dimension + + +%--------------------------------------------------------------------- +function data_history(fid, hist) + + % Original header structures + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + % descrip = sprintf('%-80s', hist.descrip); % 80 chars from left + % fwrite(fid, descrip(1:80), 'uchar'); + pad = zeros(1, 80-length(hist.descrip)); + hist.descrip = [hist.descrip char(pad)]; + fwrite(fid, hist.descrip(1:80), 'uchar'); + + % aux_file = sprintf('%-24s', hist.aux_file); % 24 chars from left + % fwrite(fid, aux_file(1:24), 'uchar'); + pad = zeros(1, 24-length(hist.aux_file)); + hist.aux_file = [hist.aux_file char(pad)]; + fwrite(fid, hist.aux_file(1:24), 'uchar'); + + fwrite(fid, hist.qform_code, 'int16'); + fwrite(fid, hist.sform_code, 'int16'); + fwrite(fid, hist.quatern_b, 'float32'); + fwrite(fid, hist.quatern_c, 'float32'); + fwrite(fid, hist.quatern_d, 'float32'); + fwrite(fid, hist.qoffset_x, 'float32'); + fwrite(fid, hist.qoffset_y, 'float32'); + fwrite(fid, hist.qoffset_z, 'float32'); + fwrite(fid, hist.srow_x(1:4), 'float32'); + fwrite(fid, hist.srow_y(1:4), 'float32'); + fwrite(fid, hist.srow_z(1:4), 'float32'); + + % intent_name = sprintf('%-16s', hist.intent_name); % 16 chars from left + % fwrite(fid, intent_name(1:16), 'uchar'); + pad = zeros(1, 16-length(hist.intent_name)); + hist.intent_name = [hist.intent_name char(pad)]; + fwrite(fid, hist.intent_name(1:16), 'uchar'); + + % magic = sprintf('%-4s', hist.magic); % 4 chars from left + % fwrite(fid, magic(1:4), 'uchar'); + pad = zeros(1, 4-length(hist.magic)); + hist.magic = [hist.magic char(pad)]; + fwrite(fid, hist.magic(1:4), 'uchar'); + + return; % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m new file mode 100755 index 00000000..b8127763 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m @@ -0,0 +1,219 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function save_nii_hdr(hdr, fid) + + if ~isequal(hdr.hk.sizeof_hdr,348), + error('hdr.hk.sizeof_hdr must be 348.'); + end + + write_header(hdr, fid); + + return; % save_nii_hdr + + +%--------------------------------------------------------------------- +function write_header(hdr, fid) + + % Original header structures + % struct dsr /* dsr = hdr */ + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + header_key(fid, hdr.hk); + image_dimension(fid, hdr.dime); + data_history(fid, hdr.hist); + + % check the file size is 348 bytes + % + fbytes = ftell(fid); + + if ~isequal(fbytes,348), + msg = sprintf('Header size is not 348 bytes.'); + warning(msg); + end + + return; % write_header + + +%--------------------------------------------------------------------- +function header_key(fid, hk) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + + fwrite(fid, hk.sizeof_hdr(1), 'int32'); % must be 348. + + % data_type = sprintf('%-10s',hk.data_type); % ensure it is 10 chars from left + % fwrite(fid, data_type(1:10), 'uchar'); + pad = zeros(1, 10-length(hk.data_type)); + hk.data_type = [hk.data_type char(pad)]; + fwrite(fid, hk.data_type(1:10), 'uchar'); + + % db_name = sprintf('%-18s', hk.db_name); % ensure it is 18 chars from left + % fwrite(fid, db_name(1:18), 'uchar'); + pad = zeros(1, 18-length(hk.db_name)); + hk.db_name = [hk.db_name char(pad)]; + fwrite(fid, hk.db_name(1:18), 'uchar'); + + fwrite(fid, hk.extents(1), 'int32'); + fwrite(fid, hk.session_error(1), 'int16'); + fwrite(fid, hk.regular(1), 'uchar'); + + fwrite(fid, hk.hkey_un0(1), 'uchar'); + + return; % header_key + + +%--------------------------------------------------------------------- +function image_dimension(fid, dime) + + %struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % char vox_units[4]; /* 16 + 4 */ + % char cal_units[8]; /* 20 + 8 */ + % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width + % pixdim[2] - voxel height + % pixdim[3] - interslice distance + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float roi_scale; /* 72 + 4 */ + % float funused1; /* 76 + 4 */ + % float funused2; /* 80 + 4 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % int compressed; /* 92 + 4 */ + % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + fwrite(fid, dime.dim(1:8), 'int16'); + + pad = zeros(1, 4-length(dime.vox_units)); + dime.vox_units = [dime.vox_units char(pad)]; + fwrite(fid, dime.vox_units(1:4), 'uchar'); + + pad = zeros(1, 8-length(dime.cal_units)); + dime.cal_units = [dime.cal_units char(pad)]; + fwrite(fid, dime.cal_units(1:8), 'uchar'); + + fwrite(fid, dime.unused1(1), 'int16'); + fwrite(fid, dime.datatype(1), 'int16'); + fwrite(fid, dime.bitpix(1), 'int16'); + fwrite(fid, dime.dim_un0(1), 'int16'); + fwrite(fid, dime.pixdim(1:8), 'float32'); + fwrite(fid, dime.vox_offset(1), 'float32'); + fwrite(fid, dime.roi_scale(1), 'float32'); + fwrite(fid, dime.funused1(1), 'float32'); + fwrite(fid, dime.funused2(1), 'float32'); + fwrite(fid, dime.cal_max(1), 'float32'); + fwrite(fid, dime.cal_min(1), 'float32'); + fwrite(fid, dime.compressed(1), 'int32'); + fwrite(fid, dime.verified(1), 'int32'); + fwrite(fid, dime.glmax(1), 'int32'); + fwrite(fid, dime.glmin(1), 'int32'); + + return; % image_dimension + + +%--------------------------------------------------------------------- +function data_history(fid, hist) + + % Original header structures - ANALYZE 7.5 + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % char orient; /* 104 + 1 */ + % char originator[10]; /* 105 + 10 */ + % char generated[10]; /* 115 + 10 */ + % char scannum[10]; /* 125 + 10 */ + % char patient_id[10]; /* 135 + 10 */ + % char exp_date[10]; /* 145 + 10 */ + % char exp_time[10]; /* 155 + 10 */ + % char hist_un0[3]; /* 165 + 3 */ + % int views /* 168 + 4 */ + % int vols_added; /* 172 + 4 */ + % int start_field; /* 176 + 4 */ + % int field_skip; /* 180 + 4 */ + % int omax; /* 184 + 4 */ + % int omin; /* 188 + 4 */ + % int smax; /* 192 + 4 */ + % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + % descrip = sprintf('%-80s', hist.descrip); % 80 chars from left + % fwrite(fid, descrip(1:80), 'uchar'); + pad = zeros(1, 80-length(hist.descrip)); + hist.descrip = [hist.descrip char(pad)]; + fwrite(fid, hist.descrip(1:80), 'uchar'); + + % aux_file = sprintf('%-24s', hist.aux_file); % 24 chars from left + % fwrite(fid, aux_file(1:24), 'uchar'); + pad = zeros(1, 24-length(hist.aux_file)); + hist.aux_file = [hist.aux_file char(pad)]; + fwrite(fid, hist.aux_file(1:24), 'uchar'); + + fwrite(fid, hist.orient(1), 'uchar'); + fwrite(fid, hist.originator(1:5), 'int16'); + + pad = zeros(1, 10-length(hist.generated)); + hist.generated = [hist.generated char(pad)]; + fwrite(fid, hist.generated(1:10), 'uchar'); + + pad = zeros(1, 10-length(hist.scannum)); + hist.scannum = [hist.scannum char(pad)]; + fwrite(fid, hist.scannum(1:10), 'uchar'); + + pad = zeros(1, 10-length(hist.patient_id)); + hist.patient_id = [hist.patient_id char(pad)]; + fwrite(fid, hist.patient_id(1:10), 'uchar'); + + pad = zeros(1, 10-length(hist.exp_date)); + hist.exp_date = [hist.exp_date char(pad)]; + fwrite(fid, hist.exp_date(1:10), 'uchar'); + + pad = zeros(1, 10-length(hist.exp_time)); + hist.exp_time = [hist.exp_time char(pad)]; + fwrite(fid, hist.exp_time(1:10), 'uchar'); + + pad = zeros(1, 3-length(hist.hist_un0)); + hist.hist_un0 = [hist.hist_un0 char(pad)]; + fwrite(fid, hist.hist_un0(1:3), 'uchar'); + + fwrite(fid, hist.views(1), 'int32'); + fwrite(fid, hist.vols_added(1), 'int32'); + fwrite(fid, hist.start_field(1),'int32'); + fwrite(fid, hist.field_skip(1), 'int32'); + fwrite(fid, hist.omax(1), 'int32'); + fwrite(fid, hist.omin(1), 'int32'); + fwrite(fid, hist.smax(1), 'int32'); + fwrite(fid, hist.smin(1), 'int32'); + + return; % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m new file mode 100755 index 00000000..edbce71d --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m @@ -0,0 +1,71 @@ +% This function is only used to save Analyze or NIfTI header that is +% ended with .hdr and loaded by load_untouch_header_only.m. If you +% have NIfTI file that is ended with .nii and you want to change its +% header only, you can use load_untouch_nii / save_untouch_nii pair. +% +% Usage: save_untouch_header_only(hdr, new_header_file_name) +% +% hdr - struct with NIfTI / Analyze header fields, which is obtained from: +% hdr = load_untouch_header_only(original_header_file_name) +% +% new_header_file_name - NIfTI / Analyze header name ended with .hdr. +% You can either copy original.img(.gz) to new.img(.gz) manually, +% or simply input original.hdr(.gz) in save_untouch_header_only.m +% to overwrite the original header. +% +% - Jimmy Shen (jshen@research.baycrest.org) +% +function save_untouch_header_only(hdr, filename) + + if ~exist('hdr','var') | isempty(hdr) | ~exist('filename','var') | isempty(filename) + error('Usage: save_untouch_header_only(hdr, filename)'); + end + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.hdr.gz') + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + filename = filename(1:end-3); + end + end + + [p,f] = fileparts(filename); + fileprefix = fullfile(p, f); + + write_hdr(hdr, fileprefix); + + % gzip output file if requested + % + if exist('gzFile', 'var') + gzip([fileprefix, '.hdr']); + delete([fileprefix, '.hdr']); + end; + + return % save_untouch_header_only + + +%----------------------------------------------------------------------------------- +function write_hdr(hdr, fileprefix) + + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if isfield(hdr.hist,'magic') + save_untouch_nii_hdr(hdr, fid); + else + save_untouch0_nii_hdr(hdr, fid); + end + + fclose(fid); + + return % write_hdr + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m new file mode 100755 index 00000000..3faa64c1 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m @@ -0,0 +1,232 @@ +% Save NIFTI or ANALYZE dataset that is loaded by "load_untouch_nii.m". +% The output image format and file extension will be the same as the +% input one (NIFTI.nii, NIFTI.img or ANALYZE.img). Therefore, any file +% extension that you specified will be ignored. +% +% Usage: save_untouch_nii(nii, filename) +% +% nii - nii structure that is loaded by "load_untouch_nii.m" +% +% filename - NIFTI or ANALYZE file name. +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_untouch_nii(nii, filename) + + if ~exist('nii','var') | isempty(nii) | ~isfield(nii,'hdr') | ... + ~isfield(nii,'img') | ~exist('filename','var') | isempty(filename) + + error('Usage: save_untouch_nii(nii, filename)'); + end + + if ~isfield(nii,'untouch') | nii.untouch == 0 + error('Usage: please use ''save_nii.m'' for the modified structure.'); + end + + if isfield(nii.hdr.hist,'magic') & strcmp(nii.hdr.hist.magic(1:3),'ni1') + filetype = 1; + elseif isfield(nii.hdr.hist,'magic') & strcmp(nii.hdr.hist.magic(1:3),'n+1') + filetype = 2; + else + filetype = 0; + end + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + else + gzFile = 1; + filename = filename(1:end-3); + end + end + + [p,f] = fileparts(filename); + fileprefix = fullfile(p, f); + + write_nii(nii, filetype, fileprefix); + + % gzip output file if requested + % + if exist('gzFile', 'var') + if filetype == 1 + gzip([fileprefix, '.img']); + delete([fileprefix, '.img']); + gzip([fileprefix, '.hdr']); + delete([fileprefix, '.hdr']); + elseif filetype == 2 + gzip([fileprefix, '.nii']); + delete([fileprefix, '.nii']); + end; + end; + +% % So earlier versions of SPM can also open it with correct originator + % % + % if filetype == 0 + % M=[[diag(nii.hdr.dime.pixdim(2:4)) -[nii.hdr.hist.originator(1:3).*nii.hdr.dime.pixdim(2:4)]'];[0 0 0 1]]; + % save(fileprefix, 'M'); +% elseif filetype == 1 + % M=[]; + % save(fileprefix, 'M'); + %end + + return % save_untouch_nii + + +%----------------------------------------------------------------------------------- +function write_nii(nii, filetype, fileprefix) + + hdr = nii.hdr; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + +% hdr.dime.glmax = round(double(max(nii.img(:)))); + % hdr.dime.glmin = round(double(min(nii.img(:)))); + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + elseif filetype == 1 + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_untouch_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + save_untouch0_nii_hdr(hdr, fid); + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + ScanDim = double(hdr.dime.dim(5)); % t + SliceDim = double(hdr.dime.dim(4)); % z + RowDim = double(hdr.dime.dim(3)); % y + PixelDim = double(hdr.dime.dim(2)); % x + SliceSz = double(hdr.dime.pixdim(4)); + RowSz = double(hdr.dime.pixdim(3)); + PixelSz = double(hdr.dime.pixdim(2)); + + x = 1:PixelDim; + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if skip_bytes + fwrite(fid, zeros(1,skip_bytes), 'uint8'); + end + + fwrite(fid, nii.img, precision); +% fwrite(fid, nii.img, precision, skip_bytes); % error using skip + fclose(fid); + + return; % write_nii + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m new file mode 100755 index 00000000..9ee0fd63 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m @@ -0,0 +1,207 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function save_nii_hdr(hdr, fid) + + if ~isequal(hdr.hk.sizeof_hdr,348), + error('hdr.hk.sizeof_hdr must be 348.'); + end + + write_header(hdr, fid); + + return; % save_nii_hdr + + +%--------------------------------------------------------------------- +function write_header(hdr, fid) + + % Original header structures + % struct dsr /* dsr = hdr */ + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + header_key(fid, hdr.hk); + image_dimension(fid, hdr.dime); + data_history(fid, hdr.hist); + + % check the file size is 348 bytes + % + fbytes = ftell(fid); + + if ~isequal(fbytes,348), + msg = sprintf('Header size is not 348 bytes.'); + warning(msg); + end + + return; % write_header + + +%--------------------------------------------------------------------- +function header_key(fid, hk) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + + fwrite(fid, hk.sizeof_hdr(1), 'int32'); % must be 348. + + % data_type = sprintf('%-10s',hk.data_type); % ensure it is 10 chars from left + % fwrite(fid, data_type(1:10), 'uchar'); + pad = zeros(1, 10-length(hk.data_type)); + hk.data_type = [hk.data_type char(pad)]; + fwrite(fid, hk.data_type(1:10), 'uchar'); + + % db_name = sprintf('%-18s', hk.db_name); % ensure it is 18 chars from left + % fwrite(fid, db_name(1:18), 'uchar'); + pad = zeros(1, 18-length(hk.db_name)); + hk.db_name = [hk.db_name char(pad)]; + fwrite(fid, hk.db_name(1:18), 'uchar'); + + fwrite(fid, hk.extents(1), 'int32'); + fwrite(fid, hk.session_error(1), 'int16'); + fwrite(fid, hk.regular(1), 'uchar'); % might be uint8 + + % fwrite(fid, hk.hkey_un0(1), 'uchar'); + % fwrite(fid, hk.hkey_un0(1), 'uint8'); + fwrite(fid, hk.dim_info(1), 'uchar'); + + return; % header_key + + +%--------------------------------------------------------------------- +function image_dimension(fid, dime) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width + % pixdim[2] - voxel height + % pixdim[3] - interslice distance + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + fwrite(fid, dime.dim(1:8), 'int16'); + fwrite(fid, dime.intent_p1(1), 'float32'); + fwrite(fid, dime.intent_p2(1), 'float32'); + fwrite(fid, dime.intent_p3(1), 'float32'); + fwrite(fid, dime.intent_code(1), 'int16'); + fwrite(fid, dime.datatype(1), 'int16'); + fwrite(fid, dime.bitpix(1), 'int16'); + fwrite(fid, dime.slice_start(1), 'int16'); + fwrite(fid, dime.pixdim(1:8), 'float32'); + fwrite(fid, dime.vox_offset(1), 'float32'); + fwrite(fid, dime.scl_slope(1), 'float32'); + fwrite(fid, dime.scl_inter(1), 'float32'); + fwrite(fid, dime.slice_end(1), 'int16'); + fwrite(fid, dime.slice_code(1), 'uchar'); + fwrite(fid, dime.xyzt_units(1), 'uchar'); + fwrite(fid, dime.cal_max(1), 'float32'); + fwrite(fid, dime.cal_min(1), 'float32'); + fwrite(fid, dime.slice_duration(1), 'float32'); + fwrite(fid, dime.toffset(1), 'float32'); + fwrite(fid, dime.glmax(1), 'int32'); + fwrite(fid, dime.glmin(1), 'int32'); + + return; % image_dimension + + +%--------------------------------------------------------------------- +function data_history(fid, hist) + + % Original header structures + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + % descrip = sprintf('%-80s', hist.descrip); % 80 chars from left + % fwrite(fid, descrip(1:80), 'uchar'); + pad = zeros(1, 80-length(hist.descrip)); + hist.descrip = [hist.descrip char(pad)]; + fwrite(fid, hist.descrip(1:80), 'uchar'); + + % aux_file = sprintf('%-24s', hist.aux_file); % 24 chars from left + % fwrite(fid, aux_file(1:24), 'uchar'); + pad = zeros(1, 24-length(hist.aux_file)); + hist.aux_file = [hist.aux_file char(pad)]; + fwrite(fid, hist.aux_file(1:24), 'uchar'); + + fwrite(fid, hist.qform_code, 'int16'); + fwrite(fid, hist.sform_code, 'int16'); + fwrite(fid, hist.quatern_b, 'float32'); + fwrite(fid, hist.quatern_c, 'float32'); + fwrite(fid, hist.quatern_d, 'float32'); + fwrite(fid, hist.qoffset_x, 'float32'); + fwrite(fid, hist.qoffset_y, 'float32'); + fwrite(fid, hist.qoffset_z, 'float32'); + fwrite(fid, hist.srow_x(1:4), 'float32'); + fwrite(fid, hist.srow_y(1:4), 'float32'); + fwrite(fid, hist.srow_z(1:4), 'float32'); + + % intent_name = sprintf('%-16s', hist.intent_name); % 16 chars from left + % fwrite(fid, intent_name(1:16), 'uchar'); + pad = zeros(1, 16-length(hist.intent_name)); + hist.intent_name = [hist.intent_name char(pad)]; + fwrite(fid, hist.intent_name(1:16), 'uchar'); + + % magic = sprintf('%-4s', hist.magic); % 4 chars from left + % fwrite(fid, magic(1:4), 'uchar'); + pad = zeros(1, 4-length(hist.magic)); + hist.magic = [hist.magic char(pad)]; + fwrite(fid, hist.magic(1:4), 'uchar'); + + return; % data_history + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m new file mode 100755 index 00000000..a9668d26 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m @@ -0,0 +1,580 @@ +% Save back to the original image with a portion of slices that was +% loaded by "load_untouch_nii". You can process those slices matrix +% in any way, as long as their dimension is not altered. +% +% Usage: save_untouch_slice(slice, filename, ... +% slice_idx, [img_idx], [dim5_idx], [dim6_idx], [dim7_idx]) +% +% slice - a portion of slices that was loaded by "load_untouch_nii". +% This should be a numeric matrix (i.e. only the .img field in the +% loaded structure) +% +% filename - NIfTI or ANALYZE file name. +% +% slice_idx (depending on slice size) - a numerical array of image +% slice indices, which should be the same as that you entered +% in "load_untouch_nii" command. +% +% img_idx (depending on slice size) - a numerical array of image +% volume indices, which should be the same as that you entered +% in "load_untouch_nii" command. +% +% dim5_idx (depending on slice size) - a numerical array of 5th +% dimension indices, which should be the same as that you entered +% in "load_untouch_nii" command. +% +% dim6_idx (depending on slice size) - a numerical array of 6th +% dimension indices, which should be the same as that you entered +% in "load_untouch_nii" command. +% +% dim7_idx (depending on slice size) - a numerical array of 7th +% dimension indices, which should be the same as that you entered +% in "load_untouch_nii" command. +% +% Example: +% nii = load_nii('avg152T1_LR_nifti.nii'); +% save_nii(nii, 'test.nii'); +% view_nii(nii); +% nii = load_untouch_nii('test.nii','','','','','',[40 51:53]); +% nii.img = ones(91,109,4)*122; +% save_untouch_slice(nii.img, 'test.nii', [40 51:52]); +% nii = load_nii('test.nii'); +% view_nii(nii); +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_untouch_slice(slice, filename, slice_idx, img_idx, dim5_idx, dim6_idx, dim7_idx) + + if ~exist('slice','var') | ~isnumeric(slice) + msg = [char(10) '"slice" argument should be a portion of slices that was loaded' char(10)]; + msg = [msg 'by "load_untouch_nii.m". This should be a numeric matrix (i.e.' char(10)]; + msg = [msg 'only the .img field in the loaded structure).']; + error(msg); + end + + if ~exist('filename','var') | ~exist(filename,'file') + error('In order to save back, original NIfTI or ANALYZE file must exist.'); + end + + if ~exist('slice_idx','var') | isempty(slice_idx) | ~isequal(size(slice,3),length(slice_idx)) + msg = [char(10) '"slice_idx" is a numerical array of image slice indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + + if ~isequal(size(slice,4),1) + msg = [char(10) '"img_idx" is a numerical array of image volume indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + elseif ~isequal(size(slice,4),length(img_idx)) + msg = [char(10) '"img_idx" is a numerical array of image volume indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) + dim5_idx = []; + + if ~isequal(size(slice,5),1) + msg = [char(10) '"dim5_idx" is a numerical array of 5th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + elseif ~isequal(size(slice,5),length(img_idx)) + msg = [char(10) '"img_idx" is a numerical array of 5th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) + dim6_idx = []; + + if ~isequal(size(slice,6),1) + msg = [char(10) '"dim6_idx" is a numerical array of 6th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + elseif ~isequal(size(slice,6),length(img_idx)) + msg = [char(10) '"img_idx" is a numerical array of 6th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) + dim7_idx = []; + + if ~isequal(size(slice,7),1) + msg = [char(10) '"dim7_idx" is a numerical array of 7th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + elseif ~isequal(size(slice,7),length(img_idx)) + msg = [char(10) '"img_idx" is a numerical array of 7th dimension indices, which' char(10)]; + msg = [msg 'should be the same as that you entered in "load_untouch_nii.m"' char(10)]; + msg = [msg 'command.']; + error(msg); + end + + + v = version; + + % Check file extension. If .gz, unpack it into temp folder + % + if length(filename) > 2 & strcmp(filename(end-2:end), '.gz') + + if ~strcmp(filename(end-6:end), '.img.gz') & ... + ~strcmp(filename(end-6:end), '.hdr.gz') & ... + ~strcmp(filename(end-6:end), '.nii.gz') + + error('Please check filename.'); + end + + if str2num(v(1:3)) < 7.1 | ~usejava('jvm') + error('Please use MATLAB 7.1 (with java) and above, or run gunzip outside MATLAB.'); + elseif strcmp(filename(end-6:end), '.img.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.hdr.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.hdr.gz') + filename1 = filename; + filename2 = filename; + filename2(end-6:end) = ''; + filename2 = [filename2, '.img.gz']; + + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + + filename1 = gunzip(filename1, tmpDir); + filename2 = gunzip(filename2, tmpDir); + filename = char(filename1); % convert from cell to string + elseif strcmp(filename(end-6:end), '.nii.gz') + tmpDir = tempname; + mkdir(tmpDir); + gzFileName = filename; + filename = gunzip(filename, tmpDir); + filename = char(filename); % convert from cell to string + end + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + if nii.filetype == 0 + nii.hdr = load_untouch0_nii_hdr(nii.fileprefix,nii.machine); + else + nii.hdr = load_untouch_nii_hdr(nii.fileprefix,nii.machine,nii.filetype); + end + + + % Clean up after gunzip + % + if exist('gzFileName', 'var') + + % fix fileprefix so it doesn't point to temp location + % + nii.fileprefix = gzFileName(1:end-7); +% rmdir(tmpDir,'s'); + end + + [p,f] = fileparts(filename); + fileprefix = fullfile(p, f); +% fileprefix = nii.fileprefix; + filetype = nii.filetype; + + if ~isequal( nii.hdr.dime.dim(2:3), [size(slice,1),size(slice,2)] ) + msg = [char(10) 'The first two dimensions of slice matrix should be the same as' char(10)]; + msg = [msg 'the first two dimensions of image loaded by "load_untouch_nii".']; + error(msg); + end + + + % Save the dataset body + % + save_untouch_slice_img(slice, nii.hdr, filetype, fileprefix, ... + nii.machine, slice_idx,img_idx,dim5_idx,dim6_idx,dim7_idx); + + % gzip output file if requested + % + if exist('gzFileName', 'var') + [p,f] = fileparts(gzFileName); + + if filetype == 1 + gzip([fileprefix, '.img']); + delete([fileprefix, '.img']); + movefile([fileprefix, '.img.gz']); + gzip([fileprefix, '.hdr']); + delete([fileprefix, '.hdr']); + movefile([fileprefix, '.hdr.gz']); + elseif filetype == 2 + gzip([fileprefix, '.nii']); + delete([fileprefix, '.nii']); + movefile([fileprefix, '.nii.gz']); + end; + + rmdir(tmpDir,'s'); + end; + + return % save_untouch_slice + + +%-------------------------------------------------------------------------- +function save_untouch_slice_img(slice,hdr,filetype,fileprefix,machine,slice_idx,img_idx,dim5_idx,dim6_idx,dim7_idx) + + if ~exist('hdr','var') | ~exist('filetype','var') | ~exist('fileprefix','var') | ~exist('machine','var') + error('Usage: save_untouch_slice_img(slice,hdr,filetype,fileprefix,machine,slice_idx,[img_idx],[dim5_idx],[dim6_idx],[dim7_idx]);'); + end + + if ~exist('slice_idx','var') | isempty(slice_idx) | hdr.dime.dim(4)<1 + slice_idx = []; + end + + if ~exist('img_idx','var') | isempty(img_idx) | hdr.dime.dim(5)<1 + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) | hdr.dime.dim(6)<1 + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) | hdr.dime.dim(7)<1 + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) | hdr.dime.dim(8)<1 + dim7_idx = []; + end + + % check img_idx + % + if ~isempty(img_idx) & ~isnumeric(img_idx) + error('"img_idx" should be a numerical array.'); + end + + if length(unique(img_idx)) ~= length(img_idx) + error('Duplicate image index in "img_idx"'); + end + + if ~isempty(img_idx) & (min(img_idx) < 1 | max(img_idx) > hdr.dime.dim(5)) + max_range = hdr.dime.dim(5); + + if max_range == 1 + error(['"img_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"img_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim5_idx + % + if ~isempty(dim5_idx) & ~isnumeric(dim5_idx) + error('"dim5_idx" should be a numerical array.'); + end + + if length(unique(dim5_idx)) ~= length(dim5_idx) + error('Duplicate index in "dim5_idx"'); + end + + if ~isempty(dim5_idx) & (min(dim5_idx) < 1 | max(dim5_idx) > hdr.dime.dim(6)) + max_range = hdr.dime.dim(6); + + if max_range == 1 + error(['"dim5_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim5_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim6_idx + % + if ~isempty(dim6_idx) & ~isnumeric(dim6_idx) + error('"dim6_idx" should be a numerical array.'); + end + + if length(unique(dim6_idx)) ~= length(dim6_idx) + error('Duplicate index in "dim6_idx"'); + end + + if ~isempty(dim6_idx) & (min(dim6_idx) < 1 | max(dim6_idx) > hdr.dime.dim(7)) + max_range = hdr.dime.dim(7); + + if max_range == 1 + error(['"dim6_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim6_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim7_idx + % + if ~isempty(dim7_idx) & ~isnumeric(dim7_idx) + error('"dim7_idx" should be a numerical array.'); + end + + if length(unique(dim7_idx)) ~= length(dim7_idx) + error('Duplicate index in "dim7_idx"'); + end + + if ~isempty(dim7_idx) & (min(dim7_idx) < 1 | max(dim7_idx) > hdr.dime.dim(8)) + max_range = hdr.dime.dim(8); + + if max_range == 1 + error(['"dim7_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim7_idx" should be an integer within the range of [' range '].']); + end + end + + % check slice_idx + % + if ~isempty(slice_idx) & ~isnumeric(slice_idx) + error('"slice_idx" should be a numerical array.'); + end + + if length(unique(slice_idx)) ~= length(slice_idx) + error('Duplicate index in "slice_idx"'); + end + + if ~isempty(slice_idx) & (min(slice_idx) < 1 | max(slice_idx) > hdr.dime.dim(4)) + max_range = hdr.dime.dim(4); + + if max_range == 1 + error(['"slice_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"slice_idx" should be an integer within the range of [' range '].']); + end + end + + write_image(slice,hdr,filetype,fileprefix,machine,slice_idx,img_idx,dim5_idx,dim6_idx,dim7_idx); + + return % save_untouch_slice_img + + +%--------------------------------------------------------------------- +function write_image(slice,hdr,filetype,fileprefix,machine,slice_idx,img_idx,dim5_idx,dim6_idx,dim7_idx) + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'r+'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + else + fid = fopen(sprintf('%s.img',fileprefix),'r+'); + + if fid < 0, + msg = sprintf('Cannot open file %s.img.',fileprefix); + error(msg); + end + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.dim(find(hdr.dime.dim < 1)) = 1; + + % move pointer to the start of image block + % + switch filetype + case {0, 1} + fseek(fid, 0, 'bof'); + case 2 + fseek(fid, hdr.dime.vox_offset, 'bof'); + end + + if hdr.dime.datatype == 1 | isequal(hdr.dime.dim(4:8),ones(1,5)) | ... + (isempty(img_idx) & isempty(dim5_idx) & isempty(dim6_idx) & isempty(dim7_idx) & isempty(slice_idx)) + + msg = [char(10) char(10) ' "save_untouch_slice" is used to save back to the original image a' char(10)]; + msg = [msg ' portion of slices that were loaded by "load_untouch_nii". You can' char(10)]; + msg = [msg ' process those slices matrix in any way, as long as their dimension' char(10)]; + msg = [msg ' is not changed.']; + error(msg); + else + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(slice_idx) + slice_idx = 1:d3; + end + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + + %ROMAN: begin + roman = 1; + if(roman) + + % compute size of one slice + % + img_siz = prod(hdr.dime.dim(2:3)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + end; %if(roman) + % ROMAN: end + + for i7=1:length(dim7_idx) + for i6=1:length(dim6_idx) + for i5=1:length(dim5_idx) + for t=1:length(img_idx) + for s=1:length(slice_idx) + + % Position is seeked in bytes. To convert dimension size + % to byte storage size, hdr.dime.bitpix/8 will be + % applied. + % + pos = sub2ind([d1 d2 d3 d4 d5 d6 d7], 1, 1, slice_idx(s), ... + img_idx(t), dim5_idx(i5),dim6_idx(i6),dim7_idx(i7)) -1; + pos = pos * hdr.dime.bitpix/8; + + % ROMAN: begin + if(roman) + % do nothing + else + img_siz = prod(hdr.dime.dim(2:3)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + end; % if (roman) + % ROMAN: end + + if filetype == 2 + fseek(fid, pos + hdr.dime.vox_offset, 'bof'); + else + fseek(fid, pos, 'bof'); + end + + % For each frame, fwrite will write precision of value + % in img_siz times + % + fwrite(fid, slice(:,:,s,t,i5,i6,i7), sprintf('*%s',precision)); + + end + end + end + end + end + end + + fclose(fid); + + return % write_image + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m new file mode 100755 index 00000000..d522ee1b --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m @@ -0,0 +1,40 @@ +% Undo the flipping and rotations performed by xform_nii; spit back only +% the raw img data block. Initial cut will only deal with 3D volumes +% strongly assume we have called xform_nii to write down the steps used +% in xform_nii. +% +% Usage: a = load_nii('original_name'); +% manipulate a.img to make array b; +% +% if you use unxform_nii to un-tranform the image (img) data +% block, then nii.original.hdr is the corresponding header. +% +% nii.original.img = unxform_nii(a, b); +% save_nii(nii.original,'newname'); +% +% Where, 'newname' is created with data in the same space as the +% original_name data +% +% - Jeff Gunter, 26-JUN-06 +% +function outblock = unxform_nii(nii, inblock) + + if isempty(nii.hdr.hist.rot_orient) + outblock=inblock; + else + [dummy unrotate_orient] = sort(nii.hdr.hist.rot_orient); + outblock = permute(inblock, unrotate_orient); + end + + if ~isempty(nii.hdr.hist.flip_orient) + flip_orient = nii.hdr.hist.flip_orient(unrotate_orient); + + for i = 1:3 + if flip_orient(i) + outblock = flipdim(outblock, i); + end + end + end; + + return; + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m new file mode 100755 index 00000000..71bda97d --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m @@ -0,0 +1,45 @@ +% Verify NIFTI header extension to make sure that each extension section +% must be an integer multiple of 16 byte long that includes the first 8 +% bytes of esize and ecode. If the length of extension section is not the +% above mentioned case, edata should be padded with all 0. +% +% Usage: [ext, esize_total] = verify_nii_ext(ext) +% +% ext - Structure of NIFTI header extension, which includes num_ext, +% and all the extended header sections in the header extension. +% Each extended header section will have its esize, ecode, and +% edata, where edata can be plain text, xml, or any raw data +% that was saved in the extended header section. +% +% esize_total - Sum of all esize variable in all header sections. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [ext, esize_total] = verify_nii_ext(ext) + + if ~isfield(ext, 'section') + error('Incorrect NIFTI header extension structure.'); + elseif ~isfield(ext, 'num_ext') + ext.num_ext = length(ext.section); + elseif ~isfield(ext, 'extension') + ext.extension = [1 0 0 0]; + end + + esize_total = 0; + + for i=1:ext.num_ext + if ~isfield(ext.section(i), 'ecode') | ~isfield(ext.section(i), 'edata') + error('Incorrect NIFTI header extension structure.'); + end + + ext.section(i).esize = ceil((length(ext.section(i).edata)+8)/16)*16; + ext.section(i).edata = ... + [ext.section(i).edata ... + zeros(1,ext.section(i).esize-length(ext.section(i).edata)-8)]; + esize_total = esize_total + ext.section(i).esize; + end + + return % verify_nii_ext + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m new file mode 100755 index 00000000..e8b26152 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m @@ -0,0 +1,4873 @@ +% VIEW_NII: Create or update a 3-View (Front, Top, Side) of the +% brain data that is specified by nii structure +% +% Usage: status = view_nii([h], nii, [option]) or +% status = view_nii(h, [option]) +% +% Where, h is the figure on which the 3-View will be plotted; +% nii is the brain data in NIFTI format; +% option is a struct that configures the view plotted, can be: +% +% option.command = 'init' +% option.command = 'update' +% option.command = 'clearnii' +% option.command = 'updatenii' +% option.command = 'updateimg' (nii is nii.img here) +% +% option.usecolorbar = 0 | [1] +% option.usepanel = 0 | [1] +% option.usecrosshair = 0 | [1] +% option.usestretch = 0 | [1] +% option.useimagesc = 0 | [1] +% option.useinterp = [0] | 1 +% +% option.setarea = [x y w h] | [0.05 0.05 0.9 0.9] +% option.setunit = ['vox'] | 'mm' +% option.setviewpoint = [x y z] | [origin] +% option.setscanid = [t] | [1] +% option.setcrosshaircolor = [r g b] | [1 0 0] +% option.setcolorindex = From 1 to 9 (default is 2 or 3) +% option.setcolormap = (Mx3 matrix, 0 <= val <= 1) +% option.setcolorlevel = No more than 256 (default 256) +% option.sethighcolor = [] +% option.setcbarminmax = [] +% option.setvalue = [] +% option.glblocminmax = [] +% option.setbuttondown = '' +% option.setcomplex = [0] | 1 | 2 +% +% Options description in detail: +% ============================== +% +% 1. command: A char string that can control program. +% +% init: If option.command='init', the program will display +% a 3-View plot on the figure specified by figure h +% or on a new figure. If there is already a 3-View +% plot on the figure, please use option.command = +% 'updatenii' (see detail below); otherwise, the +% new 3-View plot will superimpose on the old one. +% If there is no option provided, the program will +% assume that this is an initial plot. If the figure +% handle is omitted, the program knows that it is +% an initial plot. +% +% update: If there is no command specified, and a figure +% handle of the existing 3-View plot is provided, +% the program will choose option.command='update' +% to update the 3-View plot with some new option +% items. +% +% clearnii: Clear 3-View plot on specific figure +% +% updatenii: If a new nii is going to be loaded on a fig +% that has already 3-View plot on it, use this +% command to clear existing 3-View plot, and then +% display with new nii. So, the new nii will not +% superimpose on the existing one. All options +% for 'init' can be used for 'updatenii'. +% +% updateimg: If a new 3D matrix with the same dimension +% is going to be loaded, option.command='updateimg' +% can be used as a light-weighted 'updatenii, since +% it only updates the 3 slices with new values. +% inputing argument nii should be a 3D matrix +% (nii.img) instead of nii struct. No other option +% should be used together with 'updateimg' to keep +% this command as simple as possible. +% +% +% 2. usecolorbar: If specified and usecolorbar=0, the program +% will not include the colorbar in plot area; otherwise, +% a colorbar will be included in plot area. +% +% 3. usepanel: If specified and usepanel=0, the control panel +% at lower right cornor will be invisible; otherwise, +% it will be visible. +% +% 4. usecrosshair: If specified and usecrosshair=0, the crosshair +% will be invisible; otherwise, it will be visible. +% +% 5. usestretch: If specified and usestretch=0, the 3 slices will +% not be stretched, and will be displayed according to +% the actual voxel size; otherwise, the 3 slices will be +% stretched to the edge. +% +% 6. useimagesc: If specified and useimagesc=0, images data will +% be used directly to match the colormap (like 'image' +% command); otherwise, image data will be scaled to full +% colormap with 'imagesc' command in Matlab. +% +% 7. useinterp: If specified and useinterp=1, the image will be +% displayed using interpolation. Otherwise, it will be +% displayed like mosaic, and each tile stands for a +% pixel. This option does not apply to 'setvalue' option +% is set. +% +% +% 8. setarea: 3-View plot will be displayed on this specific +% region. If it is not specified, program will set the +% plot area to [0.05 0.05 0.9 0.9]. +% +% 9. setunit: It can be specified to setunit='voxel' or 'mm' +% and the view will change the axes unit of [X Y Z] +% accordingly. +% +% 10. setviewpoint: If specified, [X Y Z] values will be used +% to set the viewpoint of 3-View plot. +% +% 11. setscanid: If specified, [t] value will be used to display +% the specified image scan in NIFTI data. +% +% 12. setcrosshaircolor: If specified, [r g b] value will be used +% for Crosshair Color. Otherwise, red will be the default. +% +% 13. setcolorindex: If specified, the 3-View will choose the +% following colormap: 2 - Bipolar; 3 - Gray; 4 - Jet; +% 5 - Cool; 6 - Bone; 7 - Hot; 8 - Copper; 9 - Pink; +% If not specified, it will choose 3 - Gray if all data +% values are not less than 0; otherwise, it will choose +% 2 - Bipolar if there is value less than 0. (Contrast +% control can only apply to 3 - Gray colormap. +% +% 14. setcolormap: 3-View plot will use it as a customized colormap. +% It is a 3-column matrix with value between 0 and 1. If +% using MS-Windows version of Matlab, the number of rows +% can not be more than 256, because of Matlab limitation. +% When colormap is used, setcolorlevel option will be +% disabled automatically. +% +% 15. setcolorlevel: If specified (must be no more than 256, and +% cannot be used for customized colormap), row number of +% colormap will be squeezed down to this level; otherwise, +% it will assume that setcolorlevel=256. +% +% 16. sethighcolor: If specified, program will squeeze down the +% colormap, and allocate sethighcolor (an Mx3 matrix) +% to high-end portion of the colormap. The sum of M and +% setcolorlevel should be less than 256. If setcolormap +% option is used, sethighcolor will be inserted on top +% of the setcolormap, and the setcolorlevel option will +% be disabled automatically. +% +% 17. setcbarminmax: if specified, the [min max] will be used to +% set the min and max of the colorbar, which does not +% include any data for highcolor. +% +% 18. setvalue: If specified, setvalue.val (with the same size as +% the source data on solution points) in the source area +% setvalue.idx will be superimposed on the current nii +% image. So, the size of setvalue.val should be equal to +% the size of setvalue.idx. To use this feature, it needs +% single or double nii structure for background image. +% +% 19. glblocminmax: If specified, pgm will use glblocminmax to +% calculate the colormap, instead of minmax of image. +% +% 20. setbuttondown: If specified, pgm will evaluate the command +% after a click or slide action is invoked to the new +% view point. +% +% 21. setcomplex: This option will decide how complex data to be +% displayed: 0 - Real part of complex data; 1 - Imaginary +% part of complex data; 2 - Modulus (magnitude) of complex +% data; If not specified, it will be set to 0 (Real part +% of complex data as default option. This option only apply +% when option.command is set to 'init or 'updatenii'. +% +% +% Additional Options for 'update' command: +% ======================================= +% +% option.enablecursormove = [1] | 0 +% option.enableviewpoint = 0 | [1] +% option.enableorigin = 0 | [1] +% option.enableunit = 0 | [1] +% option.enablecrosshair = 0 | [1] +% option.enablehistogram = 0 | [1] +% option.enablecolormap = 0 | [1] +% option.enablecontrast = 0 | [1] +% option.enablebrightness = 0 | [1] +% option.enableslider = 0 | [1] +% option.enabledirlabel = 0 | [1] +% +% +% e.g.: +% nii = load_nii('T1'); % T1.img/hdr +% view_nii(nii); +% +% or +% +% h = figure('unit','normal','pos', [0.18 0.08 0.64 0.85]); +% opt.setarea = [0.05 0.05 0.9 0.9]; +% view_nii(h, nii, opt); +% +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function status = view_nii(varargin) + + if nargin < 1 + error('Please check inputs using ''help view_nii'''); + end; + + nii = ''; + opt = ''; + command = ''; + + usecolorbar = []; + usepanel = []; + usecrosshair = ''; + usestretch = []; + useimagesc = []; + useinterp = []; + + setarea = []; + setunit = ''; + setviewpoint = []; + setscanid = []; + setcrosshaircolor = []; + setcolorindex = ''; + setcolormap = 'NA'; + setcolorlevel = []; + sethighcolor = 'NA'; + setcbarminmax = []; + setvalue = []; + glblocminmax = []; + setbuttondown = ''; + setcomplex = 0; + + status = []; + + if ishandle(varargin{1}) % plot on top of this figure + + fig = varargin{1}; + + if nargin < 2 + command = 'update'; % just to get 3-View status + end + + if nargin == 2 + if ~isstruct(varargin{2}) + error('2nd parameter should be either nii struct or option struct'); + end + + opt = varargin{2}; + + if isfield(opt,'hdr') & isfield(opt,'img') + nii = opt; + elseif isfield(opt, 'command') & (strcmpi(opt.command,'init') ... + | strcmpi(opt.command,'updatenii') ... + | strcmpi(opt.command,'updateimg') ) + + error('Option here cannot contain "init", "updatenii", or "updateimg" comand'); + end + end + + if nargin == 3 + nii = varargin{2}; + opt = varargin{3}; + + if ~isstruct(opt) + error('3rd parameter should be option struct'); + end + + if ~isfield(opt,'command') | ~strcmpi(opt.command,'updateimg') + if ~isstruct(nii) | ~isfield(nii,'hdr') | ~isfield(nii,'img') + error('2nd parameter should be nii struct'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''load_nii.m'' to load the structure.'); + end + end + end + + set(fig, 'menubar', 'none'); + + elseif ischar(varargin{1}) % call back by event + + command = lower(varargin{1}); + fig = gcbf; + + else % start nii with a new figure + + nii = varargin{1}; + + if ~isstruct(nii) | ~isfield(nii,'hdr') | ~isfield(nii,'img') + error('1st parameter should be either a figure handle or nii struct'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''load_nii.m'' to load the structure.'); + end + + if nargin > 1 + opt = varargin{2}; + + if isfield(opt, 'command') & ~strcmpi(opt.command,'init') + error('Option here must use "init" comand'); + end + end + + command = 'init'; + fig = figure('unit','normal','position',[0.15 0.08 0.70 0.85]); + view_nii_menu(fig); + rri_file_menu(fig); + + end + + if ~isempty(opt) + + if isfield(opt,'command') + command = lower(opt.command); + end + + if isempty(command) + command = 'update'; + end + + if isfield(opt,'usecolorbar') + usecolorbar = opt.usecolorbar; + end + + if isfield(opt,'usepanel') + usepanel = opt.usepanel; + end + + if isfield(opt,'usecrosshair') + usecrosshair = opt.usecrosshair; + end + + if isfield(opt,'usestretch') + usestretch = opt.usestretch; + end + + if isfield(opt,'useimagesc') + useimagesc = opt.useimagesc; + end + + if isfield(opt,'useinterp') + useinterp = opt.useinterp; + end + + if isfield(opt,'setarea') + setarea = opt.setarea; + end + + if isfield(opt,'setunit') + setunit = opt.setunit; + end + + if isfield(opt,'setviewpoint') + setviewpoint = opt.setviewpoint; + end + + if isfield(opt,'setscanid') + setscanid = opt.setscanid; + end + + if isfield(opt,'setcrosshaircolor') + setcrosshaircolor = opt.setcrosshaircolor; + + if ~isempty(setcrosshaircolor) & (~isnumeric(setcrosshaircolor) | ~isequal(size(setcrosshaircolor),[1 3]) | min(setcrosshaircolor(:))<0 | max(setcrosshaircolor(:))>1) + error('Crosshair Color should be a 1x3 matrix with value between 0 and 1'); + end + end + + if isfield(opt,'setcolorindex') + setcolorindex = round(opt.setcolorindex); + + if ~isnumeric(setcolorindex) | setcolorindex < 1 | setcolorindex > 9 + error('Colorindex should be a number between 1 and 9'); + end + end + + if isfield(opt,'setcolormap') + setcolormap = opt.setcolormap; + + if ~isempty(setcolormap) & (~isnumeric(setcolormap) | size(setcolormap,2) ~= 3 | min(setcolormap(:))<0 | max(setcolormap(:))>1) + error('Colormap should be a Mx3 matrix with value between 0 and 1'); + end + end + + if isfield(opt,'setcolorlevel') + setcolorlevel = round(opt.setcolorlevel); + + if ~isnumeric(setcolorlevel) | setcolorlevel > 256 | setcolorlevel < 1 + error('Colorlevel should be a number between 1 and 256'); + end + end + + if isfield(opt,'sethighcolor') + sethighcolor = opt.sethighcolor; + + if ~isempty(sethighcolor) & (~isnumeric(sethighcolor) | size(sethighcolor,2) ~= 3 | min(sethighcolor(:))<0 | max(sethighcolor(:))>1) + error('Highcolor should be a Mx3 matrix with value between 0 and 1'); + end + end + + if isfield(opt,'setcbarminmax') + setcbarminmax = opt.setcbarminmax; + + if isempty(setcbarminmax) | ~isnumeric(setcbarminmax) | length(setcbarminmax) ~= 2 + error('Colorbar MinMax should contain 2 values: [min max]'); + end + end + + if isfield(opt,'setvalue') + setvalue = opt.setvalue; + + if isempty(setvalue) | ~isstruct(setvalue) | ... + ~isfield(opt.setvalue,'idx') | ~isfield(opt.setvalue,'val') + error('setvalue should be a struct contains idx and val'); + end + + if length(opt.setvalue.idx(:)) ~= length(opt.setvalue.val(:)) + error('length of idx and val fields should be the same'); + end + + if ~strcmpi(class(opt.setvalue.idx),'single') + opt.setvalue.idx = single(opt.setvalue.idx); + end + + if ~strcmpi(class(opt.setvalue.val),'single') + opt.setvalue.val = single(opt.setvalue.val); + end + end + + if isfield(opt,'glblocminmax') + glblocminmax = opt.glblocminmax; + end + + if isfield(opt,'setbuttondown') + setbuttondown = opt.setbuttondown; + end + + if isfield(opt,'setcomplex') + setcomplex = opt.setcomplex; + end + + end + + switch command + + case {'init'} + + set(fig, 'InvertHardcopy','off'); + set(fig, 'PaperPositionMode','auto'); + + fig = init(nii, fig, setarea, setunit, setviewpoint, setscanid, setbuttondown, ... + setcolorindex, setcolormap, setcolorlevel, sethighcolor, setcbarminmax, ... + usecolorbar, usepanel, usecrosshair, usestretch, useimagesc, useinterp, ... + setvalue, glblocminmax, setcrosshaircolor, setcomplex); + + % get status + % + status = get_status(fig); + + case {'update'} + + nii_view = getappdata(fig,'nii_view'); + h = fig; + + if isempty(nii_view) + error('The figure should already contain a 3-View plot.'); + end + + if ~isempty(opt) + + % Order of the following update matters. + % + update_shape(h, setarea, usecolorbar, usestretch, useimagesc); + update_useinterp(h, useinterp); + update_useimagesc(h, useimagesc); + update_usepanel(h, usepanel); + update_colorindex(h, setcolorindex); + update_colormap(h, setcolormap); + update_highcolor(h, sethighcolor, setcolorlevel); + update_cbarminmax(h, setcbarminmax); + update_unit(h, setunit); + update_viewpoint(h, setviewpoint); + update_scanid(h, setscanid); + update_buttondown(h, setbuttondown); + update_crosshaircolor(h, setcrosshaircolor); + update_usecrosshair(h, usecrosshair); + + % Enable/Disable object + % + update_enable(h, opt); + + end + + % get status + % + status = get_status(h); + + case {'updateimg'} + + if ~exist('nii','var') + msg = sprintf('Please input a 3D matrix brain data'); + error(msg); + end + + % Note: nii is not nii, nii should be a 3D matrix here + % + if ~isnumeric(nii) + msg = sprintf('2nd parameter should be a 3D matrix, not nii struct'); + error(msg); + end + + nii_view = getappdata(fig,'nii_view'); + + if isempty(nii_view) + error('The figure should already contain a 3-View plot.'); + end + + img = nii; + update_img(img, fig, opt); + + % get status + % + status = get_status(fig); + + case {'updatenii'} + + nii_view = getappdata(fig,'nii_view'); + + if isempty(nii_view) + error('The figure should already contain a 3-View plot.'); + end + + if ~isstruct(nii) | ~isfield(nii,'hdr') | ~isfield(nii,'img') + error('2nd parameter should be nii struct'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''load_nii.m'' to load the structure.'); + end + + opt.command = 'clearnii'; + view_nii(fig, opt); + + opt.command = 'init'; + view_nii(fig, nii, opt); + + % get status + % + status = get_status(fig); + + case {'clearnii'} + + nii_view = getappdata(fig,'nii_view'); + + handles = struct2cell(nii_view.handles); + + for i=1:length(handles) + if ishandle(handles{i}) % in case already del by parent + delete(handles{i}); + end + end + + rmappdata(fig,'nii_view'); + buttonmotion = get(fig,'windowbuttonmotion'); + mymotion = '; view_nii(''move_cursor'');'; + buttonmotion = strrep(buttonmotion, mymotion, ''); + set(fig, 'windowbuttonmotion', buttonmotion); + + case {'axial_image','coronal_image','sagittal_image'} + + switch command + case 'axial_image', view = 'axi'; axi = 0; cor = 1; sag = 1; + case 'coronal_image', view = 'cor'; axi = 1; cor = 0; sag = 1; + case 'sagittal_image', view = 'sag'; axi = 1; cor = 1; sag = 0; + end + + nii_view = getappdata(fig,'nii_view'); + nii_view = get_slice_position(nii_view,view); + + if isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + % CData must be double() for Matlab 6.5 for Windows + % + if axi, + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) & nii_view.useinterp + Saxi = squeeze(nii_view.bgimg(:,:,nii_view.slices.axi)); + set(nii_view.handles.axial_bg,'CData',double(Saxi)'); + end + + if isfield(nii_view.handles,'axial_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Saxi = squeeze(img(:,:,nii_view.slices.axi,:,nii_view.scanid)); + Saxi = permute(Saxi, [2 1 3]); + else + Saxi = squeeze(img(:,:,nii_view.slices.axi,nii_view.scanid)); + Saxi = Saxi'; + end + + set(nii_view.handles.axial_image,'CData',double(Saxi)); + end + + if isfield(nii_view.handles,'axial_slider'), + set(nii_view.handles.axial_slider,'Value',nii_view.slices.axi); + end; + end + + if cor, + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) & nii_view.useinterp + Scor = squeeze(nii_view.bgimg(:,nii_view.slices.cor,:)); + set(nii_view.handles.coronal_bg,'CData',double(Scor)'); + end + + if isfield(nii_view.handles,'coronal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Scor = squeeze(img(:,nii_view.slices.cor,:,:,nii_view.scanid)); + Scor = permute(Scor, [2 1 3]); + else + Scor = squeeze(img(:,nii_view.slices.cor,:,nii_view.scanid)); + Scor = Scor'; + end + + set(nii_view.handles.coronal_image,'CData',double(Scor)); + end + + if isfield(nii_view.handles,'coronal_slider'), + slider_val = nii_view.dims(2) - nii_view.slices.cor + 1; + set(nii_view.handles.coronal_slider,'Value',slider_val); + end; + end; + + if sag, + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) & nii_view.useinterp + Ssag = squeeze(nii_view.bgimg(nii_view.slices.sag,:,:)); + set(nii_view.handles.sagittal_bg,'CData',double(Ssag)'); + end + + if isfield(nii_view.handles,'sagittal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Ssag = squeeze(img(nii_view.slices.sag,:,:,:,nii_view.scanid)); + Ssag = permute(Ssag, [2 1 3]); + else + Ssag = squeeze(img(nii_view.slices.sag,:,:,nii_view.scanid)); + Ssag = Ssag'; + end + + set(nii_view.handles.sagittal_image,'CData',double(Ssag)); + end + + if isfield(nii_view.handles,'sagittal_slider'), + set(nii_view.handles.sagittal_slider,'Value',nii_view.slices.sag); + end; + end; + + update_nii_view(nii_view); + + if ~isempty(nii_view.buttondown) + eval(nii_view.buttondown); + end + + case {'axial_slider','coronal_slider','sagittal_slider'}, + + switch command + case 'axial_slider', view = 'axi'; axi = 1; cor = 0; sag = 0; + case 'coronal_slider', view = 'cor'; axi = 0; cor = 1; sag = 0; + case 'sagittal_slider', view = 'sag'; axi = 0; cor = 0; sag = 1; + end + + nii_view = getappdata(fig,'nii_view'); + nii_view = get_slider_position(nii_view); + + if isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + if axi, + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) & nii_view.useinterp + Saxi = squeeze(nii_view.bgimg(:,:,nii_view.slices.axi)); + set(nii_view.handles.axial_bg,'CData',double(Saxi)'); + end + + if isfield(nii_view.handles,'axial_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Saxi = squeeze(img(:,:,nii_view.slices.axi,:,nii_view.scanid)); + Saxi = permute(Saxi, [2 1 3]); + else + Saxi = squeeze(img(:,:,nii_view.slices.axi,nii_view.scanid)); + Saxi = Saxi'; + end + + set(nii_view.handles.axial_image,'CData',double(Saxi)); + end + + if isfield(nii_view.handles,'axial_slider'), + set(nii_view.handles.axial_slider,'Value',nii_view.slices.axi); + end + end + + if cor, + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) & nii_view.useinterp + Scor = squeeze(nii_view.bgimg(:,nii_view.slices.cor,:)); + set(nii_view.handles.coronal_bg,'CData',double(Scor)'); + end + + if isfield(nii_view.handles,'coronal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Scor = squeeze(img(:,nii_view.slices.cor,:,:,nii_view.scanid)); + Scor = permute(Scor, [2 1 3]); + else + Scor = squeeze(img(:,nii_view.slices.cor,:,nii_view.scanid)); + Scor = Scor'; + end + + set(nii_view.handles.coronal_image,'CData',double(Scor)); + end + + if isfield(nii_view.handles,'coronal_slider'), + slider_val = nii_view.dims(2) - nii_view.slices.cor + 1; + set(nii_view.handles.coronal_slider,'Value',slider_val); + end + end + + if sag, + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) & nii_view.useinterp + Ssag = squeeze(nii_view.bgimg(nii_view.slices.sag,:,:)); + set(nii_view.handles.sagittal_bg,'CData',double(Ssag)'); + end + + if isfield(nii_view.handles,'sagittal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Ssag = squeeze(img(nii_view.slices.sag,:,:,:,nii_view.scanid)); + Ssag = permute(Ssag, [2 1 3]); + else + Ssag = squeeze(img(nii_view.slices.sag,:,:,nii_view.scanid)); + Ssag = Ssag'; + end + + set(nii_view.handles.sagittal_image,'CData',double(Ssag)); + end + + if isfield(nii_view.handles,'sagittal_slider'), + set(nii_view.handles.sagittal_slider,'Value',nii_view.slices.sag); + end + end + + update_nii_view(nii_view); + + if ~isempty(nii_view.buttondown) + eval(nii_view.buttondown); + end + + case {'impos_edit'} + + nii_view = getappdata(fig,'nii_view'); + impos = str2num(get(nii_view.handles.impos,'string')); + + if isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + if isempty(impos) | ~all(size(impos) == [1 3]) + msg = 'Please use 3 numbers to represent X,Y and Z'; + msgbox(msg,'Error'); + return; + end + + slices.sag = round(impos(1)); + slices.cor = round(impos(2)); + slices.axi = round(impos(3)); + + nii_view = convert2voxel(nii_view,slices); + nii_view = check_slices(nii_view); + + impos(1) = nii_view.slices.sag; + impos(2) = nii_view.dims(2) - nii_view.slices.cor + 1; + impos(3) = nii_view.slices.axi; + + if isfield(nii_view.handles,'sagittal_slider'), + set(nii_view.handles.sagittal_slider,'Value',impos(1)); + end + + if isfield(nii_view.handles,'coronal_slider'), + set(nii_view.handles.coronal_slider,'Value',impos(2)); + end + + if isfield(nii_view.handles,'axial_slider'), + set(nii_view.handles.axial_slider,'Value',impos(3)); + end + + nii_view = get_slider_position(nii_view); + update_nii_view(nii_view); + + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) & nii_view.useinterp + Saxi = squeeze(nii_view.bgimg(:,:,nii_view.slices.axi)); + set(nii_view.handles.axial_bg,'CData',double(Saxi)'); + end + + if isfield(nii_view.handles,'axial_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Saxi = squeeze(img(:,:,nii_view.slices.axi,:,nii_view.scanid)); + Saxi = permute(Saxi, [2 1 3]); + else + Saxi = squeeze(img(:,:,nii_view.slices.axi,nii_view.scanid)); + Saxi = Saxi'; + end + + set(nii_view.handles.axial_image,'CData',double(Saxi)); + end + + if isfield(nii_view.handles,'axial_slider'), + set(nii_view.handles.axial_slider,'Value',nii_view.slices.axi); + end + + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) & nii_view.useinterp + Scor = squeeze(nii_view.bgimg(:,nii_view.slices.cor,:)); + set(nii_view.handles.coronal_bg,'CData',double(Scor)'); + end + + if isfield(nii_view.handles,'coronal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Scor = squeeze(img(:,nii_view.slices.cor,:,:,nii_view.scanid)); + Scor = permute(Scor, [2 1 3]); + else + Scor = squeeze(img(:,nii_view.slices.cor,:,nii_view.scanid)); + Scor = Scor'; + end + + set(nii_view.handles.coronal_image,'CData',double(Scor)); + end + + if isfield(nii_view.handles,'coronal_slider'), + slider_val = nii_view.dims(2) - nii_view.slices.cor + 1; + set(nii_view.handles.coronal_slider,'Value',slider_val); + end + + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) & nii_view.useinterp + Ssag = squeeze(nii_view.bgimg(nii_view.slices.sag,:,:)); + set(nii_view.handles.sagittal_bg,'CData',double(Ssag)'); + end + + if isfield(nii_view.handles,'sagittal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Ssag = squeeze(img(nii_view.slices.sag,:,:,:,nii_view.scanid)); + Ssag = permute(Ssag, [2 1 3]); + else + Ssag = squeeze(img(nii_view.slices.sag,:,:,nii_view.scanid)); + Ssag = Ssag'; + end + + set(nii_view.handles.sagittal_image,'CData',double(Ssag)); + end + + if isfield(nii_view.handles,'sagittal_slider'), + set(nii_view.handles.sagittal_slider,'Value',nii_view.slices.sag); + end + + axes(nii_view.handles.axial_axes); + axes(nii_view.handles.coronal_axes); + axes(nii_view.handles.sagittal_axes); + + if ~isempty(nii_view.buttondown) + eval(nii_view.buttondown); + end + + case 'coordinates', + + nii_view = getappdata(fig,'nii_view'); + set_image_value(nii_view); + + case 'crosshair', + + nii_view = getappdata(fig,'nii_view'); + + if get(nii_view.handles.xhair,'value') == 2 % off + set(nii_view.axi_xhair.lx,'visible','off'); + set(nii_view.axi_xhair.ly,'visible','off'); + set(nii_view.cor_xhair.lx,'visible','off'); + set(nii_view.cor_xhair.ly,'visible','off'); + set(nii_view.sag_xhair.lx,'visible','off'); + set(nii_view.sag_xhair.ly,'visible','off'); + else + set(nii_view.axi_xhair.lx,'visible','on'); + set(nii_view.axi_xhair.ly,'visible','on'); + set(nii_view.cor_xhair.lx,'visible','on'); + set(nii_view.cor_xhair.ly,'visible','on'); + set(nii_view.sag_xhair.lx,'visible','on'); + set(nii_view.sag_xhair.ly,'visible','on'); + + set(nii_view.handles.axial_axes,'selected','on'); + set(nii_view.handles.axial_axes,'selected','off'); + set(nii_view.handles.coronal_axes,'selected','on'); + set(nii_view.handles.coronal_axes,'selected','off'); + set(nii_view.handles.sagittal_axes,'selected','on'); + set(nii_view.handles.sagittal_axes,'selected','off'); + end + + case 'xhair_color', + + old_color = get(gcbo,'user'); + new_color = uisetcolor(old_color); + update_crosshaircolor(fig, new_color); + + case {'color','contrast_def'} + + nii_view = getappdata(fig,'nii_view'); + + if nii_view.numscan == 1 + if get(nii_view.handles.colorindex,'value') == 2 + set(nii_view.handles.contrast,'value',128); + elseif get(nii_view.handles.colorindex,'value') == 3 + set(nii_view.handles.contrast,'value',1); + end + end + + [custom_color_map, custom_colorindex] = change_colormap(fig); + + if strcmpi(command, 'color') + + setcolorlevel = nii_view.colorlevel; + + if ~isempty(custom_color_map) % isfield(nii_view, 'color_map') + setcolormap = custom_color_map; % nii_view.color_map; + else + setcolormap = []; + end + + if isfield(nii_view, 'highcolor') + sethighcolor = nii_view.highcolor; + else + sethighcolor = []; + end + + redraw_cbar(fig, setcolorlevel, setcolormap, sethighcolor); + + if nii_view.numscan == 1 & ... + (custom_colorindex < 2 | custom_colorindex > 3) + contrastopt.enablecontrast = 0; + else + contrastopt.enablecontrast = 1; + end + + update_enable(fig, contrastopt); + + end + + case {'neg_color','brightness','contrast'} + + change_colormap(fig); + + case {'brightness_def'} + + nii_view = getappdata(fig,'nii_view'); + set(nii_view.handles.brightness,'value',0); + change_colormap(fig); + + case 'hist_plot' + + hist_plot(fig); + + case 'hist_eq' + + hist_eq(fig); + + case 'move_cursor' + + move_cursor(fig); + + case 'edit_change_scan' + + change_scan('edit_change_scan'); + + case 'slider_change_scan' + + change_scan('slider_change_scan'); + + end + + return; % view_nii + + +%---------------------------------------------------------------- +function fig = init(nii, fig, area, setunit, setviewpoint, setscanid, buttondown, ... + colorindex, color_map, colorlevel, highcolor, cbarminmax, ... + usecolorbar, usepanel, usecrosshair, usestretch, useimagesc, ... + useinterp, setvalue, glblocminmax, setcrosshaircolor, ... + setcomplex) + + % Support data type COMPLEX64 & COMPLEX128 + % + if nii.hdr.dime.datatype == 32 | nii.hdr.dime.datatype == 1792 + switch setcomplex, + case 0, + nii.img = real(nii.img); + case 1, + nii.img = imag(nii.img); + case 2, + if isa(nii.img, 'double') + nii.img = abs(double(nii.img)); + else + nii.img = single(abs(double(nii.img))); + end + end + end + + if isempty(area) + area = [0.05 0.05 0.9 0.9]; + end + + if isempty(setscanid) + setscanid = 1; + else + setscanid = round(setscanid); + + if setscanid < 1 + setscanid = 1; + end + + if setscanid > nii.hdr.dime.dim(5) + setscanid = nii.hdr.dime.dim(5); + end + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + usecolorbar = 0; + elseif isempty(usecolorbar) + usecolorbar = 1; + end + + if isempty(usepanel) + usepanel = 1; + end + + if isempty(usestretch) + usestretch = 1; + end + + if isempty(useimagesc) + useimagesc = 1; + end + + if isempty(useinterp) + useinterp = 0; + end + + if isempty(colorindex) + tmp = min(nii.img(:,:,:,setscanid)); + + if min(tmp(:)) < 0 + colorindex = 2; + setcrosshaircolor = [1 1 0]; + else + colorindex = 3; + end + end + + if isempty(color_map) | ischar(color_map) + color_map = []; + else + colorindex = 1; + end + + bgimg = []; + + if ~isempty(glblocminmax) + minvalue = glblocminmax(1); + maxvalue = glblocminmax(2); + else + minvalue = nii.img(:,:,:,setscanid); + minvalue = double(minvalue(:)); + minvalue = min(minvalue(~isnan(minvalue))); + maxvalue = nii.img(:,:,:,setscanid); + maxvalue = double(maxvalue(:)); + maxvalue = max(maxvalue(~isnan(maxvalue))); + end + + if ~isempty(setvalue) + if ~isempty(glblocminmax) + minvalue = glblocminmax(1); + maxvalue = glblocminmax(2); + else + minvalue = double(min(setvalue.val)); + maxvalue = double(max(setvalue.val)); + end + + bgimg = double(nii.img); + minbg = double(min(bgimg(:))); + maxbg = double(max(bgimg(:))); + + bgimg = scale_in(bgimg, minbg, maxbg, 55) + 200; % scale to 201~256 + + % 56 level for brain structure + % +% highcolor = [zeros(1,3);gray(55)]; + highcolor = gray(56); + cbarminmax = [minvalue maxvalue]; + + if useinterp + + % scale signal data to 1~200 + % + nii.img = repmat(nan, size(nii.img)); + nii.img(setvalue.idx) = setvalue.val; + + % 200 level for source image + % + bgimg = single(scale_out(bgimg, cbarminmax(1), cbarminmax(2), 199)); + else + + bgimg(setvalue.idx) = NaN; + minbg = double(min(bgimg(:))); + maxbg = double(max(bgimg(:))); + bgimg(setvalue.idx) = minbg; + + % bgimg must be normalized to [201 256] + % + bgimg = 55 * (bgimg-min(bgimg(:))) / (max(bgimg(:))-min(bgimg(:))) + 201; + bgimg(setvalue.idx) = 0; + + % scale signal data to 1~200 + % + nii.img = zeros(size(nii.img)); + nii.img(setvalue.idx) = scale_in(setvalue.val, minvalue, maxvalue, 199); + nii.img = nii.img + bgimg; + bgimg = []; + nii.img = scale_out(nii.img, cbarminmax(1), cbarminmax(2), 199); + + minvalue = double(nii.img(:)); + minvalue = min(minvalue(~isnan(minvalue))); + maxvalue = double(nii.img(:)); + maxvalue = max(maxvalue(~isnan(maxvalue))); + + if ~isempty(glblocminmax) % maxvalue is gray + minvalue = glblocminmax(1); + end + + end + + colorindex = 2; + setcrosshaircolor = [1 1 0]; + + end + + if isempty(highcolor) | ischar(highcolor) + highcolor = []; + num_highcolor = 0; + else + num_highcolor = size(highcolor,1); + end + + if isempty(colorlevel) + colorlevel = 256 - num_highcolor; + end + + if usecolorbar + cbar_area = area; + cbar_area(1) = area(1) + area(3)*0.93; + cbar_area(3) = area(3)*0.04; + area(3) = area(3)*0.9; % 90% used for main axes + else + cbar_area = []; + end + + % init color (gray) scaling to make sure the slice clim take the + % global clim [min(nii.img(:)) max(nii.img(:))] + % + if isempty(bgimg) + clim = [minvalue maxvalue]; + else + clim = [minvalue double(max(bgimg(:)))]; + end + + if clim(1) == clim(2) + clim(2) = clim(1) + 0.000001; + end + + if isempty(cbarminmax) + cbarminmax = [minvalue maxvalue]; + end + + xdim = size(nii.img, 1); + ydim = size(nii.img, 2); + zdim = size(nii.img, 3); + + dims = [xdim ydim zdim]; + voxel_size = abs(nii.hdr.dime.pixdim(2:4)); % vol in mm + + if any(voxel_size <= 0) + voxel_size(find(voxel_size <= 0)) = 1; + end + + origin = abs(nii.hdr.hist.originator(1:3)); + + if isempty(origin) | all(origin == 0) % according to SPM + origin = (dims+1)/2; + end; + + origin = round(origin); + + if any(origin > dims) % simulate fMRI + origin(find(origin > dims)) = dims(find(origin > dims)); + end + + if any(origin <= 0) + origin(find(origin <= 0)) = 1; + end + + nii_view.dims = dims; + nii_view.voxel_size = voxel_size; + nii_view.origin = origin; + + nii_view.slices.sag = 1; + nii_view.slices.cor = 1; + nii_view.slices.axi = 1; + if xdim > 1, nii_view.slices.sag = origin(1); end + if ydim > 1, nii_view.slices.cor = origin(2); end + if zdim > 1, nii_view.slices.axi = origin(3); end + + nii_view.area = area; + nii_view.fig = fig; + nii_view.nii = nii; % image data + nii_view.bgimg = bgimg; % background + nii_view.setvalue = setvalue; + nii_view.minvalue = minvalue; + nii_view.maxvalue = maxvalue; + nii_view.numscan = nii.hdr.dime.dim(5); + nii_view.scanid = setscanid; + + Font.FontUnits = 'point'; + Font.FontSize = 12; + + % create axes for colorbar + % + [cbar_axes cbarminmax_axes] = create_cbar_axes(fig, cbar_area); + + if isempty(cbar_area) + nii_view.cbar_area = []; + else + nii_view.cbar_area = cbar_area; + end + + % create axes for top/front/side view + % + vol_size = voxel_size .* dims; + [top_ax, front_ax, side_ax] ... + = create_ax(fig, area, vol_size, usestretch); + + top_pos = get(top_ax,'position'); + front_pos = get(front_ax,'position'); + side_pos = get(side_ax,'position'); + + % Sagittal Slider + % + x = side_pos(1); + y = top_pos(2) + top_pos(4); + w = side_pos(3); + h = (front_pos(2) - y) / 2; + y = y + h; + + pos = [x y w h]; + + if xdim > 1, + slider_step(1) = 1/(xdim); + slider_step(2) = 1.00001/(xdim); + + handles.sagittal_slider = uicontrol('Parent',fig, ... + 'Style','slider','Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment','center',... + 'BackgroundColor',[0.5 0.5 0.5],'ForegroundColor',[0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Sagittal slice navigation',... + 'Min',1,'Max',xdim,'SliderStep',slider_step, ... + 'Value',nii_view.slices.sag,... + 'Callback','view_nii(''sagittal_slider'');'); + + set(handles.sagittal_slider,'position',pos); % linux66 + end + + % Coronal Slider + % + x = top_pos(1); + y = top_pos(2) + top_pos(4); + w = top_pos(3); + h = (front_pos(2) - y) / 2; + y = y + h; + + pos = [x y w h]; + + if ydim > 1, + slider_step(1) = 1/(ydim); + slider_step(2) = 1.00001/(ydim); + + slider_val = nii_view.dims(2) - nii_view.slices.cor + 1; + + handles.coronal_slider = uicontrol('Parent',fig, ... + 'Style','slider','Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment','center',... + 'BackgroundColor',[0.5 0.5 0.5],'ForegroundColor',[0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Coronal slice navigation',... + 'Min',1,'Max',ydim,'SliderStep',slider_step, ... + 'Value',slider_val,... + 'Callback','view_nii(''coronal_slider'');'); + + set(handles.coronal_slider,'position',pos); % linux66 + end + + % Axial Slider + % +% x = front_pos(1) + front_pos(3); +% y = front_pos(2); +% w = side_pos(1) - x; +% h = front_pos(4); + + x = top_pos(1); + y = area(2); + w = top_pos(3); + h = top_pos(2) - y; + + pos = [x y w h]; + + if zdim > 1, + slider_step(1) = 1/(zdim); + slider_step(2) = 1.00001/(zdim); + + handles.axial_slider = uicontrol('Parent',fig, ... + 'Style','slider','Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment','center',... + 'BackgroundColor',[0.5 0.5 0.5],'ForegroundColor',[0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Axial slice navigation',... + 'Min',1,'Max',zdim,'SliderStep',slider_step, ... + 'Value',nii_view.slices.axi,... + 'Callback','view_nii(''axial_slider'');'); + + set(handles.axial_slider,'position',pos); % linux66 + end + + % plot info view + % +% info_pos = [side_pos([1,3]); top_pos([2,4])]; +% info_pos = info_pos(:); + gap = side_pos(1)-(top_pos(1)+top_pos(3)); + info_pos(1) = side_pos(1) + gap; + info_pos(2) = area(2); + info_pos(3) = side_pos(3) - gap; + info_pos(4) = top_pos(2) + top_pos(4) - area(2) - gap; + + num_inputline = 10; + inputline_space =info_pos(4) / num_inputline; + + + % for any info_area change, update_usestretch should also be changed + + + % Image Intensity Value at Cursor + % + x = info_pos(1); + y = info_pos(2); + w = info_pos(3)*0.5; + h = inputline_space*0.6; + + pos = [x y w h]; + + handles.Timvalcur = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Value at cursor:'); + + if usepanel + set(handles.Timvalcur, 'visible', 'on'); + end + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.imvalcur = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'right',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String',' '); + + if usepanel + set(handles.imvalcur, 'visible', 'on'); + end + + % Position at Cursor + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.Timposcur = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','[X Y Z] at cursor:'); + + if usepanel + set(handles.Timposcur, 'visible', 'on'); + end + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.imposcur = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'right',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String',' ','Value',[0 0 0]); + + if usepanel + set(handles.imposcur, 'visible', 'on'); + end + + % Image Intensity Value at Mouse Click + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.Timval = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Value at crosshair:'); + + if usepanel + set(handles.Timval, 'visible', 'on'); + end + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.imval = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'right',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String',' '); + + if usepanel + set(handles.imval, 'visible', 'on'); + end + + % Viewpoint Position at Mouse Click + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.Timpos = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','[X Y Z] at crosshair:'); + + if usepanel + set(handles.Timpos, 'visible', 'on'); + end + + x = x + w + 0.005; + y = y - 0.008; + w = info_pos(3)*0.5; + h = inputline_space*0.9; + + pos = [x y w h]; + + handles.impos = uicontrol('Parent',fig,'Style','edit', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'right',... + 'BackgroundColor', [1 1 1], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'Callback','view_nii(''impos_edit'');', ... + 'TooltipString','Viewpoint Location in Axes Unit', ... + 'visible','off', ... + 'String',' ','Value',[0 0 0]); + + if usepanel + set(handles.impos, 'visible', 'on'); + end + + % Origin Position + % + x = info_pos(1); + y = y + inputline_space*1.2; + w = info_pos(3)*0.5; + h = inputline_space*0.6; + + pos = [x y w h]; + + handles.Torigin = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','[X Y Z] at origin:'); + + if usepanel + set(handles.Torigin, 'visible', 'on'); + end + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.origin = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'right',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String',' ','Value',[0 0 0]); + + if usepanel + set(handles.origin, 'visible', 'on'); + end + +if 0 + % Voxel Unit + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + + handles.Tcoord = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Axes Unit:'); + + if usepanel + set(handles.Tcoord, 'visible', 'on'); + end + + x = x + w + 0.005; + w = info_pos(3)*0.5 - 0.005; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.coord = uicontrol('Parent',fig,'Style','popupmenu', ... + 'Units','Normalized', Font, ... + 'Position',pos, ... + 'BackgroundColor', [1 1 1], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Choose Voxel or Millimeter',... + 'String',{'Voxel','Millimeter'},... + 'visible','off', ... + 'Callback','view_nii(''coordinates'');'); + +% 'TooltipString','Choose Voxel, MNI or Talairach Coordinates',... +% 'String',{'Voxel','MNI (mm)','Talairach (mm)'},... + + Font.FontSize = 12; + + if usepanel + set(handles.coord, 'visible', 'on'); + end +end + + % Crosshair + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.4; + + pos = [x y w h]; + + handles.Txhair = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Crosshair:'); + + if usepanel + set(handles.Txhair, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.2; + h = inputline_space*0.7; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.xhair_color = uicontrol('Parent',fig,'Style','push', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Crosshair Color',... + 'User',[1 0 0],... + 'String','Color',... + 'visible','off', ... + 'Callback','view_nii(''xhair_color'');'); + + if usepanel + set(handles.xhair_color, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.7; + w = info_pos(3)*0.3; + + pos = [x y w h]; + + handles.xhair = uicontrol('Parent',fig,'Style','popupmenu', ... + 'Units','Normalized', Font, ... + 'Position',pos, ... + 'BackgroundColor', [1 1 1], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Display or Hide Crosshair',... + 'String',{'On','Off'},... + 'visible','off', ... + 'Callback','view_nii(''crosshair'');'); + + if usepanel + set(handles.xhair, 'visible', 'on'); + end + + % Histogram & Color + % + x = info_pos(1); + w = info_pos(3)*0.45; + h = inputline_space * 1.5; + + pos = [x, y+inputline_space*0.9, w, h]; + + handles.hist_frame = uicontrol('Parent',fig, ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'Position',pos, ... + 'visible','off', ... + 'Style','frame'); + + if usepanel +% set(handles.hist_frame, 'visible', 'on'); + end + + handles.coord_frame = uicontrol('Parent',fig, ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'Position',pos, ... + 'visible','off', ... + 'Style','frame'); + + if usepanel + set(handles.coord_frame, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.475; + w = info_pos(3)*0.525; + h = inputline_space * 1.5; + + pos = [x, y+inputline_space*0.9, w, h]; + + handles.color_frame = uicontrol('Parent',fig, ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'Position',pos, ... + 'visible','off', ... + 'Style','frame'); + + if usepanel + set(handles.color_frame, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space*1.2; + w = info_pos(3)*0.2; + h = inputline_space*0.7; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.hist_eq = uicontrol('Parent',fig,'Style','toggle', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Histogram Equalization',... + 'String','Hist EQ',... + 'visible','off', ... + 'Callback','view_nii(''hist_eq'');'); + + if usepanel +% set(handles.hist_eq, 'visible', 'on'); + end + + x = x + w; + w = info_pos(3)*0.2; + + pos = [x y w h]; + + handles.hist_plot = uicontrol('Parent',fig,'Style','push', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Histogram Plot',... + 'String','Hist Plot',... + 'visible','off', ... + 'Callback','view_nii(''hist_plot'');'); + + if usepanel +% set(handles.hist_plot, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.025; + w = info_pos(3)*0.4; + + pos = [x y w h]; + + handles.coord = uicontrol('Parent',fig,'Style','popupmenu', ... + 'Units','Normalized', Font, ... + 'Position',pos, ... + 'BackgroundColor', [1 1 1], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Choose Voxel or Millimeter',... + 'String',{'Voxel','Millimeter'},... + 'visible','off', ... + 'Callback','view_nii(''coordinates'');'); + +% 'TooltipString','Choose Voxel, MNI or Talairach Coordinates',... +% 'String',{'Voxel','MNI (mm)','Talairach (mm)'},... + + if usepanel + set(handles.coord, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.2; + + pos = [x y w h]; + + handles.neg_color = uicontrol('Parent',fig,'Style','toggle', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Negative Colormap',... + 'String','Negative',... + 'visible','off', ... + 'Callback','view_nii(''neg_color'');'); + + if usepanel + set(handles.neg_color, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.neg_color, 'enable', 'off'); + end + + x = info_pos(1) + info_pos(3)*0.7; + w = info_pos(3)*0.275; + + pos = [x y w h]; + + handles.colorindex = uicontrol('Parent',fig,'Style','popupmenu', ... + 'Units','Normalized', Font, ... + 'Position',pos, ... + 'BackgroundColor', [1 1 1], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Change Colormap',... + 'String',{'Custom','Bipolar','Gray','Jet','Cool','Bone','Hot','Copper','Pink'},... + 'value', colorindex, ... + 'visible','off', ... + 'Callback','view_nii(''color'');'); + + if usepanel + set(handles.colorindex, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.colorindex, 'enable', 'off'); + end + + x = info_pos(1) + info_pos(3)*0.1; + y = y + inputline_space; + w = info_pos(3)*0.28; + h = inputline_space*0.6; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.Thist = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Histogram'); + + handles.Tcoord = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Axes Unit'); + + if usepanel +% set(handles.Thist, 'visible', 'on'); + set(handles.Tcoord, 'visible', 'on'); + end + + x = info_pos(1) + info_pos(3)*0.60; + w = info_pos(3)*0.28; + + pos = [x y w h]; + + handles.Tcolor = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Colormap'); + + if usepanel + set(handles.Tcolor, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.Tcolor, 'enable', 'off'); + end + + % Contrast Frame + % + x = info_pos(1); + w = info_pos(3)*0.45; + h = inputline_space * 2; + + pos = [x, y+inputline_space*0.8, w, h]; + + handles.contrast_frame = uicontrol('Parent',fig, ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'Position',pos, ... + 'visible','off', ... + 'Style','frame'); + + if usepanel + set(handles.contrast_frame, 'visible', 'on'); + end + + if colorindex < 2 | colorindex > 3 + set(handles.contrast_frame, 'visible', 'off'); + end + + % Brightness Frame + % + x = info_pos(1) + info_pos(3)*0.475; + w = info_pos(3)*0.525; + + pos = [x, y+inputline_space*0.8, w, h]; + + handles.brightness_frame = uicontrol('Parent',fig, ... + 'Units','normal', ... + 'BackgroundColor',[0.8 0.8 0.8], ... + 'Position',pos, ... + 'visible','off', ... + 'Style','frame'); + + if usepanel + set(handles.brightness_frame, 'visible', 'on'); + end + + % Contrast + % + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space; + w = info_pos(3)*0.4; + h = inputline_space*0.6; + + pos = [x y w h]; + + Font.FontSize = 12; + + slider_step(1) = 5/255; + slider_step(2) = 5.00001/255; + + handles.contrast = uicontrol('Parent',fig, ... + 'Style','slider','Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor',[0.5 0.5 0.5],'ForegroundColor',[0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Change contrast',... + 'Min',1,'Max',256,'SliderStep',slider_step, ... + 'Value',1, ... + 'visible','off', ... + 'Callback','view_nii(''contrast'');'); + + if usepanel + set(handles.contrast, 'visible', 'on'); + end + + if (nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511) & nii_view.numscan <= 1 + set(handles.contrast, 'enable', 'off'); + end + + if nii_view.numscan > 1 + set(handles.contrast, 'min', 1, 'max', nii_view.numscan, ... + 'sliderstep',[1/(nii_view.numscan-1) 1.00001/(nii_view.numscan-1)], ... + 'Callback', 'view_nii(''slider_change_scan'');'); + elseif colorindex < 2 | colorindex > 3 + set(handles.contrast, 'visible', 'off'); + elseif colorindex == 2 + set(handles.contrast,'value',128); + end + + set(handles.contrast,'position',pos); % linux66 + + % Brightness + % + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.475; + + pos = [x y w h]; + + Font.FontSize = 12; + + slider_step(1) = 1/50; + slider_step(2) = 1.00001/50; + + handles.brightness = uicontrol('Parent',fig, ... + 'Style','slider','Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor',[0.5 0.5 0.5],'ForegroundColor',[0 0 0],... + 'BusyAction','queue',... + 'TooltipString','Change brightness',... + 'Min',-1,'Max',1,'SliderStep',slider_step, ... + 'Value',0, ... + 'visible','off', ... + 'Callback','view_nii(''brightness'');'); + + if usepanel + set(handles.brightness, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.brightness, 'enable', 'off'); + end + + set(handles.brightness,'position',pos); % linux66 + + % Contrast text/def + % + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space; + w = info_pos(3)*0.22; + + pos = [x y w h]; + + handles.Tcontrast = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Contrast:'); + + if usepanel + set(handles.Tcontrast, 'visible', 'on'); + end + + if (nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511) & nii_view.numscan <= 1 + set(handles.Tcontrast, 'enable', 'off'); + end + + if nii_view.numscan > 1 + set(handles.Tcontrast, 'string', 'Scan ID:'); + set(handles.contrast, 'TooltipString', 'Change Scan ID'); + elseif colorindex < 2 | colorindex > 3 + set(handles.Tcontrast, 'visible', 'off'); + end + + x = x + w; + w = info_pos(3)*0.18; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.contrast_def = uicontrol('Parent',fig,'Style','push', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Restore initial contrast',... + 'String','Reset',... + 'visible','off', ... + 'Callback','view_nii(''contrast_def'');'); + + if usepanel + set(handles.contrast_def, 'visible', 'on'); + end + + if (nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511) & nii_view.numscan <= 1 + set(handles.contrast_def, 'enable', 'off'); + end + + if nii_view.numscan > 1 + set(handles.contrast_def, 'style', 'edit', 'background', 'w', ... + 'TooltipString','Scan (or volume) index in the time series',... + 'string', '1', 'Callback', 'view_nii(''edit_change_scan'');'); + elseif colorindex < 2 | colorindex > 3 + set(handles.contrast_def, 'visible', 'off'); + end + + % Brightness text/def + % + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.295; + + pos = [x y w h]; + + Font.FontSize = 12; + + handles.Tbrightness = uicontrol('Parent',fig,'Style','text', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'left',... + 'BackgroundColor', [0.8 0.8 0.8], 'ForegroundColor', [0 0 0],... + 'BusyAction','queue',... + 'visible','off', ... + 'String','Brightness:'); + + if usepanel + set(handles.Tbrightness, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.Tbrightness, 'enable', 'off'); + end + + x = x + w; + w = info_pos(3)*0.18; + + pos = [x y w h]; + + Font.FontSize = 8; + + handles.brightness_def = uicontrol('Parent',fig,'Style','push', ... + 'Units','Normalized', Font, ... + 'Position',pos, 'HorizontalAlignment', 'center',... + 'TooltipString','Restore initial brightness',... + 'String','Reset',... + 'visible','off', ... + 'Callback','view_nii(''brightness_def'');'); + + if usepanel + set(handles.brightness_def, 'visible', 'on'); + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(handles.brightness_def, 'enable', 'off'); + end + + % init image handles + % + handles.axial_image = []; + handles.coronal_image = []; + handles.sagittal_image = []; + + % plot axial view + % + if ~isempty(nii_view.bgimg) + bg_slice = squeeze(bgimg(:,:,nii_view.slices.axi)); + h1 = plot_view(fig, xdim, ydim, top_ax, bg_slice', clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + handles.axial_bg = h1; + else + handles.axial_bg = []; + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + img_slice = squeeze(nii.img(:,:,nii_view.slices.axi,:,setscanid)); + img_slice = permute(img_slice, [2 1 3]); + else + img_slice = squeeze(nii.img(:,:,nii_view.slices.axi,setscanid)); + img_slice = img_slice'; + end + h1 = plot_view(fig, xdim, ydim, top_ax, img_slice, clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + set(h1,'buttondown','view_nii(''axial_image'');'); + handles.axial_image = h1; + handles.axial_axes = top_ax; + + if size(img_slice,1) == 1 | size(img_slice,2) == 1 + set(top_ax,'visible','off'); + + if isfield(handles,'sagittal_slider') & ishandle(handles.sagittal_slider) + set(handles.sagittal_slider, 'visible', 'off'); + end + + if isfield(handles,'coronal_slider') & ishandle(handles.coronal_slider) + set(handles.coronal_slider, 'visible', 'off'); + end + + if isfield(handles,'axial_slider') & ishandle(handles.axial_slider) + set(handles.axial_slider, 'visible', 'off'); + end + end + + % plot coronal view + % + if ~isempty(nii_view.bgimg) + bg_slice = squeeze(bgimg(:,nii_view.slices.cor,:)); + h1 = plot_view(fig, xdim, zdim, front_ax, bg_slice', clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + handles.coronal_bg = h1; + else + handles.coronal_bg = []; + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + img_slice = squeeze(nii.img(:,nii_view.slices.cor,:,:,setscanid)); + img_slice = permute(img_slice, [2 1 3]); + else + img_slice = squeeze(nii.img(:,nii_view.slices.cor,:,setscanid)); + img_slice = img_slice'; + end + h1 = plot_view(fig, xdim, zdim, front_ax, img_slice, clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + set(h1,'buttondown','view_nii(''coronal_image'');'); + handles.coronal_image = h1; + handles.coronal_axes = front_ax; + + if size(img_slice,1) == 1 | size(img_slice,2) == 1 + set(front_ax,'visible','off'); + + if isfield(handles,'sagittal_slider') & ishandle(handles.sagittal_slider) + set(handles.sagittal_slider, 'visible', 'off'); + end + + if isfield(handles,'coronal_slider') & ishandle(handles.coronal_slider) + set(handles.coronal_slider, 'visible', 'off'); + end + + if isfield(handles,'axial_slider') & ishandle(handles.axial_slider) + set(handles.axial_slider, 'visible', 'off'); + end + end + + % plot sagittal view + % + if ~isempty(nii_view.bgimg) + bg_slice = squeeze(bgimg(nii_view.slices.sag,:,:)); + + h1 = plot_view(fig, ydim, zdim, side_ax, bg_slice', clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + handles.sagittal_bg = h1; + else + handles.sagittal_bg = []; + end + + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + img_slice = squeeze(nii.img(nii_view.slices.sag,:,:,:,setscanid)); + img_slice = permute(img_slice, [2 1 3]); + else + img_slice = squeeze(nii.img(nii_view.slices.sag,:,:,setscanid)); + img_slice = img_slice'; + end + + h1 = plot_view(fig, ydim, zdim, side_ax, img_slice, clim, cbarminmax, ... + handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, nii_view.numscan); + set(h1,'buttondown','view_nii(''sagittal_image'');'); + set(side_ax,'Xdir', 'reverse'); + handles.sagittal_image = h1; + handles.sagittal_axes = side_ax; + + if size(img_slice,1) == 1 | size(img_slice,2) == 1 + set(side_ax,'visible','off'); + + if isfield(handles,'sagittal_slider') & ishandle(handles.sagittal_slider) + set(handles.sagittal_slider, 'visible', 'off'); + end + + if isfield(handles,'coronal_slider') & ishandle(handles.coronal_slider) + set(handles.coronal_slider, 'visible', 'off'); + end + + if isfield(handles,'axial_slider') & ishandle(handles.axial_slider) + set(handles.axial_slider, 'visible', 'off'); + end + end + + [top1_label, top2_label, side1_label, side2_label] = ... + dir_label(fig, top_ax, front_ax, side_ax); + + % store label handles + % + handles.top1_label = top1_label; + handles.top2_label = top2_label; + handles.side1_label = side1_label; + handles.side2_label = side2_label; + + % plot colorbar + % + if ~isempty(cbar_axes) & ~isempty(cbarminmax_axes) + +if 0 + if isempty(color_map) + level = colorlevel + num_highcolor; + else + level = size([color_map; highcolor], 1); + end +end + + if isempty(color_map) + level = colorlevel; + else + level = size([color_map], 1); + end + + niiclass = class(nii.img); + + h1 = plot_cbar(fig, cbar_axes, cbarminmax_axes, cbarminmax, ... + level, handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, niiclass, nii_view.numscan); + handles.cbar_image = h1; + handles.cbar_axes = cbar_axes; + handles.cbarminmax_axes = cbarminmax_axes; + + end + + nii_view.handles = handles; % store handles + + nii_view.usepanel = usepanel; % whole panel at low right cornor + nii_view.usestretch = usestretch; % stretch display of voxel_size + nii_view.useinterp = useinterp; % use interpolation + nii_view.colorindex = colorindex; % store colorindex variable + nii_view.buttondown = buttondown; % command after button down click + nii_view.cbarminmax = cbarminmax; % store min max value for colorbar + + set_coordinates(nii_view,useinterp); % coord unit + + if ~isfield(nii_view, 'axi_xhair') | ... + ~isfield(nii_view, 'cor_xhair') | ... + ~isfield(nii_view, 'sag_xhair') + + nii_view.axi_xhair = []; % top cross hair + nii_view.cor_xhair = []; % front cross hair + nii_view.sag_xhair = []; % side cross hair + + end + + if ~isempty(color_map) + nii_view.color_map = color_map; + end + + if ~isempty(colorlevel) + nii_view.colorlevel = colorlevel; + end + + if ~isempty(highcolor) + nii_view.highcolor = highcolor; + end + + update_nii_view(nii_view); + + if ~isempty(setunit) + update_unit(fig, setunit); + end + + if ~isempty(setviewpoint) + update_viewpoint(fig, setviewpoint); + end + + if ~isempty(setcrosshaircolor) + update_crosshaircolor(fig, setcrosshaircolor); + end + + if ~isempty(usecrosshair) + update_usecrosshair(fig, usecrosshair); + end + + nii_menu = getappdata(fig, 'nii_menu'); + + if ~isempty(nii_menu) + if nii.hdr.dime.datatype == 128 | nii.hdr.dime.datatype == 511 + set(nii_menu.Minterp,'Userdata',1,'Label','Interp on','enable','off'); + elseif useinterp + set(nii_menu.Minterp,'Userdata',0,'Label','Interp off'); + else + set(nii_menu.Minterp,'Userdata',1,'Label','Interp on'); + end + end + + windowbuttonmotion = get(fig, 'windowbuttonmotion'); + windowbuttonmotion = [windowbuttonmotion '; view_nii(''move_cursor'');']; + set(fig, 'windowbuttonmotion', windowbuttonmotion); + + return; % init + + +%---------------------------------------------------------------- +function fig = update_img(img, fig, opt) + + nii_menu = getappdata(fig,'nii_menu'); + + if ~isempty(nii_menu) + set(nii_menu.Mzoom,'Userdata',1,'Label','Zoom on'); + set(fig,'pointer','arrow'); + zoom off; + end + + nii_view = getappdata(fig,'nii_view'); + change_interp = 0; + + if isfield(opt, 'useinterp') & opt.useinterp ~= nii_view.useinterp + nii_view.useinterp = opt.useinterp; + change_interp = 1; + end + + setscanid = 1; + + if isfield(opt, 'setscanid') + setscanid = round(opt.setscanid); + + if setscanid < 1 + setscanid = 1; + end + + if setscanid > nii_view.numscan + setscanid = nii_view.numscan; + end + end + + if isfield(opt, 'glblocminmax') & ~isempty(opt.glblocminmax) + minvalue = opt.glblocminmax(1); + maxvalue = opt.glblocminmax(2); + else + minvalue = img(:,:,:,setscanid); + minvalue = double(minvalue(:)); + minvalue = min(minvalue(~isnan(minvalue))); + maxvalue = img(:,:,:,setscanid); + maxvalue = double(maxvalue(:)); + maxvalue = max(maxvalue(~isnan(maxvalue))); + end + + if isfield(opt, 'setvalue') + setvalue = opt.setvalue; + + if isfield(opt, 'glblocminmax') & ~isempty(opt.glblocminmax) + minvalue = opt.glblocminmax(1); + maxvalue = opt.glblocminmax(2); + else + minvalue = double(min(setvalue.val)); + maxvalue = double(max(setvalue.val)); + end + + bgimg = double(img); + minbg = double(min(bgimg(:))); + maxbg = double(max(bgimg(:))); + + bgimg = scale_in(bgimg, minbg, maxbg, 55) + 200; % scale to 201~256 + + cbarminmax = [minvalue maxvalue]; + + if nii_view.useinterp + + % scale signal data to 1~200 + % + img = repmat(nan, size(img)); + img(setvalue.idx) = setvalue.val; + + % 200 level for source image + % + bgimg = single(scale_out(bgimg, cbarminmax(1), cbarminmax(2), 199)); + + else + + bgimg(setvalue.idx) = NaN; + minbg = double(min(bgimg(:))); + maxbg = double(max(bgimg(:))); + bgimg(setvalue.idx) = minbg; + + % bgimg must be normalized to [201 256] + % + bgimg = 55 * (bgimg-min(bgimg(:))) / (max(bgimg(:))-min(bgimg(:))) + 201; + bgimg(setvalue.idx) = 0; + + % scale signal data to 1~200 + % + img = zeros(size(img)); + img(setvalue.idx) = scale_in(setvalue.val, minvalue, maxvalue, 199); + img = img + bgimg; + bgimg = []; + img = scale_out(img, cbarminmax(1), cbarminmax(2), 199); + + minvalue = double(min(img(:))); + maxvalue = double(max(img(:))); + + if isfield(opt,'glblocminmax') & ~isempty(opt.glblocminmax) + minvalue = opt.glblocminmax(1); + end + + end + + nii_view.bgimg = bgimg; + nii_view.setvalue = setvalue; + + else + cbarminmax = [minvalue maxvalue]; + end + + update_cbarminmax(fig, cbarminmax); + nii_view.cbarminmax = cbarminmax; + nii_view.nii.img = img; + nii_view.minvalue = minvalue; + nii_view.maxvalue = maxvalue; + nii_view.scanid = setscanid; + change_colormap(fig); + + % init color (gray) scaling to make sure the slice clim take the + % global clim [min(nii.img(:)) max(nii.img(:))] + % + if isempty(nii_view.bgimg) + clim = [minvalue maxvalue]; + else + clim = [minvalue double(max(nii_view.bgimg(:)))]; + end + + if clim(1) == clim(2) + clim(2) = clim(1) + 0.000001; + end + + if strcmpi(get(nii_view.handles.axial_image,'cdatamapping'), 'direct') + useimagesc = 0; + else + useimagesc = 1; + end + + if ~isempty(nii_view.bgimg) % with interpolation + + Saxi = squeeze(nii_view.bgimg(:,:,nii_view.slices.axi)); + + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) + set(nii_view.handles.axial_bg,'CData',double(Saxi)'); + else + axes(nii_view.handles.axial_axes); + + if useimagesc + nii_view.handles.axial_bg = surface(zeros(size(Saxi')),double(Saxi'),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.axial_bg = surface(zeros(size(Saxi')),double(Saxi'),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + + order = get(gca,'child'); + order(find(order == nii_view.handles.axial_bg)) = []; + order = [order; nii_view.handles.axial_bg]; + set(gca, 'child', order); + end + + end + + if isfield(nii_view.handles,'axial_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Saxi = squeeze(nii_view.nii.img(:,:,nii_view.slices.axi,:,setscanid)); + Saxi = permute(Saxi, [2 1 3]); + else + Saxi = squeeze(nii_view.nii.img(:,:,nii_view.slices.axi,setscanid)); + Saxi = Saxi'; + end + + set(nii_view.handles.axial_image,'CData',double(Saxi)); + end + + set(nii_view.handles.axial_axes,'CLim',clim); + + if ~isempty(nii_view.bgimg) + Scor = squeeze(nii_view.bgimg(:,nii_view.slices.cor,:)); + + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) + set(nii_view.handles.coronal_bg,'CData',double(Scor)'); + else + axes(nii_view.handles.coronal_axes); + + if useimagesc + nii_view.handles.coronal_bg = surface(zeros(size(Scor')),double(Scor'),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.coronal_bg = surface(zeros(size(Scor')),double(Scor'),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + + order = get(gca,'child'); + order(find(order == nii_view.handles.coronal_bg)) = []; + order = [order; nii_view.handles.coronal_bg]; + set(gca, 'child', order); + end + end + + if isfield(nii_view.handles,'coronal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Scor = squeeze(nii_view.nii.img(:,nii_view.slices.cor,:,:,setscanid)); + Scor = permute(Scor, [2 1 3]); + else + Scor = squeeze(nii_view.nii.img(:,nii_view.slices.cor,:,setscanid)); + Scor = Scor'; + end + + set(nii_view.handles.coronal_image,'CData',double(Scor)); + end + + set(nii_view.handles.coronal_axes,'CLim',clim); + + if ~isempty(nii_view.bgimg) + Ssag = squeeze(nii_view.bgimg(nii_view.slices.sag,:,:)); + + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) + set(nii_view.handles.sagittal_bg,'CData',double(Ssag)'); + else + axes(nii_view.handles.sagittal_axes); + + if useimagesc + nii_view.handles.sagittal_bg = surface(zeros(size(Ssag')),double(Ssag'),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.sagittal_bg = surface(zeros(size(Ssag')),double(Ssag'),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + + order = get(gca,'child'); + order(find(order == nii_view.handles.sagittal_bg)) = []; + order = [order; nii_view.handles.sagittal_bg]; + set(gca, 'child', order); + end + end + + if isfield(nii_view.handles,'sagittal_image'), + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + Ssag = squeeze(nii_view.nii.img(nii_view.slices.sag,:,:,:,setscanid)); + Ssag = permute(Ssag, [2 1 3]); + else + Ssag = squeeze(nii_view.nii.img(nii_view.slices.sag,:,:,setscanid)); + Ssag = Ssag'; + end + + set(nii_view.handles.sagittal_image,'CData',double(Ssag)); + end + + set(nii_view.handles.sagittal_axes,'CLim',clim); + + update_nii_view(nii_view); + + if isfield(opt, 'setvalue') + + if ~isfield(nii_view,'highcolor') | ~isequal(size(nii_view.highcolor),[56 3]) + + % 55 level for brain structure (paded 0 for highcolor level 1, i.e. normal level 201, to make 56 highcolor) + % + update_highcolor(fig, [zeros(1,3);gray(55)], []); + + end + + if nii_view.colorindex ~= 2 + update_colorindex(fig, 2); + end + + old_color = get(nii_view.handles.xhair_color,'user'); + + if isequal(old_color, [1 0 0]) + update_crosshaircolor(fig, [1 1 0]); + end + +% if change_interp + % update_useinterp(fig, nii_view.useinterp); + % end + + end + + if change_interp + update_useinterp(fig, nii_view.useinterp); + end + + return; % update_img + + +%---------------------------------------------------------------- +function [top_pos, front_pos, side_pos] = ... + axes_pos(fig,area,vol_size,usestretch) + + set(fig,'unit','pixel'); + + fig_pos = get(fig,'position'); + + gap_x = 15/fig_pos(3); % width of vertical scrollbar + gap_y = 15/fig_pos(4); % width of horizontal scrollbar + + a = (area(3) - gap_x * 1.3) * fig_pos(3) / (vol_size(1) + vol_size(2)); % no crosshair lost in zoom + b = (area(4) - gap_y * 3) * fig_pos(4) / (vol_size(2) + vol_size(3)); + c = min([a b]); % make sure 'ax' is inside 'area' + + top_w = vol_size(1) * c / fig_pos(3); + side_w = vol_size(2) * c / fig_pos(3); + top_h = vol_size(2) * c / fig_pos(4); + side_h = vol_size(3) * c / fig_pos(4); + side_x = area(1) + top_w + gap_x * 1.3; % no crosshair lost in zoom + side_y = area(2) + top_h + gap_y * 3; + + if usestretch + if a > b % top touched ceiling, use b + d = (area(3) - gap_x * 1.3) / (top_w + side_w); % no crosshair lost in zoom + top_w = top_w * d; + side_w = side_w * d; + side_x = area(1) + top_w + gap_x * 1.3; % no crosshair lost in zoom + else + d = (area(4) - gap_y * 3) / (top_h + side_h); + top_h = top_h * d; + side_h = side_h * d; + side_y = area(2) + top_h + gap_y * 3; + end + end + + top_pos = [area(1) area(2)+gap_y top_w top_h]; + front_pos = [area(1) side_y top_w side_h]; + side_pos = [side_x side_y side_w side_h]; + + set(fig,'unit','normal'); + + return; % axes_pos + + +%---------------------------------------------------------------- +function [top_ax, front_ax, side_ax] ... + = create_ax(fig, area, vol_size, usestretch) + + cur_fig = gcf; % save h_wait fig + figure(fig); + + [top_pos, front_pos, side_pos] = ... + axes_pos(fig,area,vol_size,usestretch); + + nii_view = getappdata(fig, 'nii_view'); + + if isempty(nii_view) + top_ax = axes('position', top_pos); + front_ax = axes('position', front_pos); + side_ax = axes('position', side_pos); + else + top_ax = nii_view.handles.axial_axes; + front_ax = nii_view.handles.coronal_axes; + side_ax = nii_view.handles.sagittal_axes; + + set(top_ax, 'position', top_pos); + set(front_ax, 'position', front_pos); + set(side_ax, 'position', side_pos); + end + + figure(cur_fig); + + return; % create_ax + + +%---------------------------------------------------------------- +function [cbar_axes, cbarminmax_axes] = create_cbar_axes(fig, cbar_area, nii_view) + + if isempty(cbar_area) % without_cbar + cbar_axes = []; + cbarminmax_axes = []; + return; + end + + cur_fig = gcf; % save h_wait fig + figure(fig); + + if ~exist('nii_view', 'var') + nii_view = getappdata(fig, 'nii_view'); + end + + if isempty(nii_view) | ~isfield(nii_view.handles,'cbar_axes') | isempty(nii_view.handles.cbar_axes) + cbarminmax_axes = axes('position', cbar_area); + cbar_axes = axes('position', cbar_area); + else + cbarminmax_axes = nii_view.handles.cbarminmax_axes; + cbar_axes = nii_view.handles.cbar_axes; + set(cbarminmax_axes, 'position', cbar_area); + set(cbar_axes, 'position', cbar_area); + end + + figure(cur_fig); + + return; % create_cbar_axes + + +%---------------------------------------------------------------- +function h1 = plot_view(fig, x, y, img_ax, img_slice, clim, ... + cbarminmax, handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, useinterp, numscan) + + h1 = []; + + if x > 1 & y > 1, + + axes(img_ax); + + nii_view = getappdata(fig, 'nii_view'); + + if isempty(nii_view) + + % set colormap first + % + nii.handles = handles; + nii.handles.axial_axes = img_ax; + nii.colorindex = colorindex; + nii.color_map = color_map; + nii.colorlevel = colorlevel; + nii.highcolor = highcolor; + nii.numscan = numscan; + + change_colormap(fig, nii, colorindex, cbarminmax); + + if useinterp + if useimagesc + h1 = surface(zeros(size(img_slice)),double(img_slice),'edgecolor','none','facecolor','interp'); + else + h1 = surface(zeros(size(img_slice)),double(img_slice),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + + set(gca,'clim',clim); + else + if useimagesc + h1 = imagesc(img_slice,clim); + else + h1 = image(img_slice); + end + + set(gca,'clim',clim); + end + + else + + h1 = nii_view.handles.axial_image; + + if ~isequal(get(h1,'parent'), img_ax) + h1 = nii_view.handles.coronal_image; + end + + if ~isequal(get(h1,'parent'), img_ax) + h1 = nii_view.handles.sagittal_image; + end + + set(h1, 'cdata', double(img_slice)); + set(h1, 'xdata', 1:size(img_slice,2)); + set(h1, 'ydata', 1:size(img_slice,1)); + + end + + set(img_ax,'YDir','normal','XLimMode','manual','YLimMode','manual',... + 'ClimMode','manual','visible','off', ... + 'xtick',[],'ytick',[], 'clim', clim); + + end + + return; % plot_view + + +%---------------------------------------------------------------- +function h1 = plot_cbar(fig, cbar_axes, cbarminmax_axes, cbarminmax, ... + level, handles, useimagesc, colorindex, color_map, ... + colorlevel, highcolor, niiclass, numscan, nii_view) + + cbar_image = [1:level]'; + + % In a uint8 or uint16 indexed image, 0 points to the first row + % in the colormap + % + if 0 % strcmpi(niiclass,'uint8') | strcmpi(niiclass,'uint16') + % we use single for display anyway + ylim = [0, level-1]; + else + ylim = [1, level]; + end + + axes(cbarminmax_axes); + + plot([0 0], cbarminmax, 'w'); + axis tight; + + set(cbarminmax_axes,'YDir','normal', ... + 'XLimMode','manual','YLimMode','manual','YColor',[0 0 0], ... + 'XColor',[0 0 0],'xtick',[],'YAxisLocation','right'); + + ylimb = get(cbarminmax_axes,'ylim'); + ytickb = get(cbarminmax_axes,'ytick'); + ytick=(ylim(2)-ylim(1))*(ytickb-ylimb(1))/(ylimb(2)-ylimb(1))+ylim(1); + + axes(cbar_axes); + + if ~exist('nii_view', 'var') + nii_view = getappdata(fig, 'nii_view'); + end + + if isempty(nii_view) | ~isfield(nii_view.handles,'cbar_image') | isempty(nii_view.handles.cbar_image) + + % set colormap first + % + nii.handles = handles; + nii.colorindex = colorindex; + nii.color_map = color_map; + nii.colorlevel = colorlevel; + nii.highcolor = highcolor; + nii.numscan = numscan; + + change_colormap(fig, nii, colorindex, cbarminmax); + h1 = image([0,1], [ylim(1),ylim(2)], cbar_image); + + else + h1 = nii_view.handles.cbar_image; + set(h1, 'cdata', double(cbar_image)); + end + + set(cbar_axes,'YDir','normal','XLimMode','manual', ... + 'YLimMode','manual','YColor',[0 0 0],'XColor',[0 0 0],'xtick',[], ... + 'YAxisLocation','right','ylim',ylim,'ytick',ytick,'yticklabel',''); + + return; % plot_cbar + + +%---------------------------------------------------------------- +function set_coordinates(nii_view,useinterp) + + imgPlim.vox = nii_view.dims; + imgNlim.vox = [1 1 1]; + + if useinterp + xdata_ax = [imgNlim.vox(1) imgPlim.vox(1)]; + ydata_ax = [imgNlim.vox(2) imgPlim.vox(2)]; + zdata_ax = [imgNlim.vox(3) imgPlim.vox(3)]; + else + xdata_ax = [imgNlim.vox(1)-0.5 imgPlim.vox(1)+0.5]; + ydata_ax = [imgNlim.vox(2)-0.5 imgPlim.vox(2)+0.5]; + zdata_ax = [imgNlim.vox(3)-0.5 imgPlim.vox(3)+0.5]; + end + + if isfield(nii_view.handles,'axial_image') & ~isempty(nii_view.handles.axial_image) + set(nii_view.handles.axial_axes,'Xlim',xdata_ax); + set(nii_view.handles.axial_axes,'Ylim',ydata_ax); + end; + if isfield(nii_view.handles,'coronal_image') & ~isempty(nii_view.handles.coronal_image) + set(nii_view.handles.coronal_axes,'Xlim',xdata_ax); + set(nii_view.handles.coronal_axes,'Ylim',zdata_ax); + end; + if isfield(nii_view.handles,'sagittal_image') & ~isempty(nii_view.handles.sagittal_image) + set(nii_view.handles.sagittal_axes,'Xlim',ydata_ax); + set(nii_view.handles.sagittal_axes,'Ylim',zdata_ax); + end; + + return % set_coordinates + + +%---------------------------------------------------------------- +function set_image_value(nii_view), + + % get coordinates of selected voxel and the image intensity there + % + sag = round(nii_view.slices.sag); + cor = round(nii_view.slices.cor); + axi = round(nii_view.slices.axi); + + if 0 % isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + if nii_view.nii.hdr.dime.datatype == 128 + imgvalue = [double(img(sag,cor,axi,1,nii_view.scanid)) double(img(sag,cor,axi,2,nii_view.scanid)) double(img(sag,cor,axi,3,nii_view.scanid))]; + set(nii_view.handles.imval,'Value',imgvalue); + set(nii_view.handles.imval,'String',sprintf('%7.4g %7.4g %7.4g',imgvalue)); + elseif nii_view.nii.hdr.dime.datatype == 511 + R = double(img(sag,cor,axi,1,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + G = double(img(sag,cor,axi,2,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + B = double(img(sag,cor,axi,3,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + imgvalue = [double(img(sag,cor,axi,1,nii_view.scanid)) double(img(sag,cor,axi,2,nii_view.scanid)) double(img(sag,cor,axi,3,nii_view.scanid))]; + set(nii_view.handles.imval,'Value',imgvalue); + imgvalue = [R G B]; + set(nii_view.handles.imval,'String',sprintf('%7.4g %7.4g %7.4g',imgvalue)); + else + imgvalue = double(img(sag,cor,axi,nii_view.scanid)); + set(nii_view.handles.imval,'Value',imgvalue); + + if isnan(imgvalue) | imgvalue > nii_view.cbarminmax(2) + imgvalue = 0; + end + + set(nii_view.handles.imval,'String',sprintf('%.6g',imgvalue)); + end + + % Now update the coordinates of the selected voxel + + nii_view = update_imgXYZ(nii_view); + + if get(nii_view.handles.coord,'value') == 1, + sag = nii_view.imgXYZ.vox(1); + cor = nii_view.imgXYZ.vox(2); + axi = nii_view.imgXYZ.vox(3); + org = nii_view.origin; + elseif get(nii_view.handles.coord,'value') == 2, + sag = nii_view.imgXYZ.mm(1); + cor = nii_view.imgXYZ.mm(2); + axi = nii_view.imgXYZ.mm(3); + org = [0 0 0]; + elseif get(nii_view.handles.coord,'value') == 3, + sag = nii_view.imgXYZ.tal(1); + cor = nii_view.imgXYZ.tal(2); + axi = nii_view.imgXYZ.tal(3); + org = [0 0 0]; + end + + set(nii_view.handles.impos,'Value',[sag,cor,axi]); + + if get(nii_view.handles.coord,'value') == 1, + string = sprintf('%7.0f %7.0f %7.0f',sag,cor,axi); + org_str = sprintf('%7.0f %7.0f %7.0f', org(1), org(2), org(3)); + else + string = sprintf('%7.1f %7.1f %7.1f',sag,cor,axi); + org_str = sprintf('%7.1f %7.1f %7.1f', org(1), org(2), org(3)); + end; + + set(nii_view.handles.impos,'String',string); + set(nii_view.handles.origin, 'string', org_str); + + return % set_image_value + + +%---------------------------------------------------------------- +function nii_view = get_slice_position(nii_view,view), + + % obtain slices that is in correct unit, then update slices + % + slices = nii_view.slices; + + switch view, + case 'sag', + currentpoint = get(nii_view.handles.sagittal_axes,'CurrentPoint'); + slices.cor = currentpoint(1,1); + slices.axi = currentpoint(1,2); + case 'cor', + currentpoint = get(nii_view.handles.coronal_axes,'CurrentPoint'); + slices.sag = currentpoint(1,1); + slices.axi = currentpoint(1,2); + case 'axi', + currentpoint = get(nii_view.handles.axial_axes,'CurrentPoint'); + slices.sag = currentpoint(1,1); + slices.cor = currentpoint(1,2); + end + + % update nii_view.slices with the updated slices + % + nii_view.slices.axi = round(slices.axi); + nii_view.slices.cor = round(slices.cor); + nii_view.slices.sag = round(slices.sag); + + return % get_slice_position + + +%---------------------------------------------------------------- +function nii_view = get_slider_position(nii_view), + + [nii_view.slices.sag,nii_view.slices.cor,nii_view.slices.axi] = deal(0); + + if isfield(nii_view.handles,'sagittal_slider'), + if ishandle(nii_view.handles.sagittal_slider), + nii_view.slices.sag = ... + round(get(nii_view.handles.sagittal_slider,'Value')); + end + end + + if isfield(nii_view.handles,'coronal_slider'), + if ishandle(nii_view.handles.coronal_slider), + nii_view.slices.cor = ... + round(nii_view.dims(2) - ... + get(nii_view.handles.coronal_slider,'Value') + 1); + end + end + + if isfield(nii_view.handles,'axial_slider'), + if ishandle(nii_view.handles.axial_slider), + nii_view.slices.axi = ... + round(get(nii_view.handles.axial_slider,'Value')); + end + end + + nii_view = check_slices(nii_view); + + return % get_slider_position + + +%---------------------------------------------------------------- +function nii_view = update_imgXYZ(nii_view), + + nii_view.imgXYZ.vox = ... + [nii_view.slices.sag,nii_view.slices.cor,nii_view.slices.axi]; + nii_view.imgXYZ.mm = ... + (nii_view.imgXYZ.vox - nii_view.origin) .* nii_view.voxel_size; +% nii_view.imgXYZ.tal = mni2tal(nii_view.imgXYZ.mni); + + return % update_imgXYZ + + +%---------------------------------------------------------------- +function nii_view = convert2voxel(nii_view,slices), + + if get(nii_view.handles.coord,'value') == 1, + + % [slices.axi, slices.cor, slices.sag] are in vox + % + nii_view.slices.axi = round(slices.axi); + nii_view.slices.cor = round(slices.cor); + nii_view.slices.sag = round(slices.sag); + + elseif get(nii_view.handles.coord,'value') == 2, + + % [slices.axi, slices.cor, slices.sag] are in mm + % + xpix = nii_view.voxel_size(1); + ypix = nii_view.voxel_size(2); + zpix = nii_view.voxel_size(3); + + nii_view.slices.axi = round(slices.axi / zpix + nii_view.origin(3)); + nii_view.slices.cor = round(slices.cor / ypix + nii_view.origin(2)); + nii_view.slices.sag = round(slices.sag / xpix + nii_view.origin(1)); + elseif get(nii_view.handles.coord,'value') == 3, + + % [slices.axi, slices.cor, slices.sag] are in talairach + % + xpix = nii_view.voxel_size(1); + ypix = nii_view.voxel_size(2); + zpix = nii_view.voxel_size(3); + + xyz_tal = [slices.sag, slices.cor, slices.axi]; + xyz_mni = tal2mni(xyz_tal); + + nii_view.slices.axi = round(xyz_mni(3) / zpix + nii_view.origin(3)); + nii_view.slices.cor = round(xyz_mni(2) / ypix + nii_view.origin(2)); + nii_view.slices.sag = round(xyz_mni(1) / xpix + nii_view.origin(1)); + + end + + return % convert2voxel + + +%---------------------------------------------------------------- +function nii_view = check_slices(nii_view), + + img = nii_view.nii.img; + + [ SagSize, CorSize, AxiSize, TimeSize ] = size(img); + if nii_view.slices.sag > SagSize, nii_view.slices.sag = SagSize; end; + if nii_view.slices.sag < 1, nii_view.slices.sag = 1; end; + if nii_view.slices.cor > CorSize, nii_view.slices.cor = CorSize; end; + if nii_view.slices.cor < 1, nii_view.slices.cor = 1; end; + if nii_view.slices.axi > AxiSize, nii_view.slices.axi = AxiSize; end; + if nii_view.slices.axi < 1, nii_view.slices.axi = 1; end; + if nii_view.scanid > TimeSize, nii_view.scanid = TimeSize; end; + if nii_view.scanid < 1, nii_view.scanid = 1; end; + + return % check_slices + + +%---------------------------------------------------------------- +% +% keep this function small, since it will be called for every click +% +function nii_view = update_nii_view(nii_view) + + % add imgXYZ into nii_view struct + % + nii_view = check_slices(nii_view); + nii_view = update_imgXYZ(nii_view); + + % update xhair + % + p_axi = nii_view.imgXYZ.vox([1 2]); + p_cor = nii_view.imgXYZ.vox([1 3]); + p_sag = nii_view.imgXYZ.vox([2 3]); + + nii_view.axi_xhair = ... + rri_xhair(p_axi, nii_view.axi_xhair, nii_view.handles.axial_axes); + + nii_view.cor_xhair = ... + rri_xhair(p_cor, nii_view.cor_xhair, nii_view.handles.coronal_axes); + + nii_view.sag_xhair = ... + rri_xhair(p_sag, nii_view.sag_xhair, nii_view.handles.sagittal_axes); + + setappdata(nii_view.fig, 'nii_view', nii_view); + set_image_value(nii_view); + + return; % update_nii_view + + +%---------------------------------------------------------------- +function hist_plot(fig) + + nii_view = getappdata(fig,'nii_view'); + + if isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + img = double(img(:)); + + if length(unique(round(img))) == length(unique(img)) + is_integer = 1; + range = max(img) - min(img) + 1; + figure; hist(img, range); + set(gca, 'xlim', [-range/5, max(img)]); + else + is_integer = 0; + figure; hist(img); + end + + xlabel('Voxel Intensity'); + ylabel('Voxel Numbers for Each Intensity'); + set(gcf, 'NumberTitle','off','Name','Histogram Plot'); + + return; % hist_plot + + +%---------------------------------------------------------------- +function hist_eq(fig) + + nii_view = getappdata(fig,'nii_view'); + + old_pointer = get(fig,'Pointer'); + set(fig,'Pointer','watch'); + + if get(nii_view.handles.hist_eq,'value') + max_img = double(max(nii_view.nii.img(:))); + tmp = double(nii_view.nii.img) / max_img; % normalize for histeq + tmp = histeq(tmp(:)); + nii_view.disp = reshape(tmp, size(nii_view.nii.img)); + min_disp = min(nii_view.disp(:)); + nii_view.disp = (nii_view.disp - min_disp); % range having eq hist + nii_view.disp = nii_view.disp * max_img / max(nii_view.disp(:)); + nii_view.disp = single(nii_view.disp); + else + if isfield(nii_view, 'disp') + nii_view.disp = nii_view.nii.img; + else + set(fig,'Pointer',old_pointer); + return; + end + end + + % update axial view + % + img_slice = squeeze(double(nii_view.disp(:,:,nii_view.slices.axi))); + h1 = nii_view.handles.axial_image; + set(h1, 'cdata', double(img_slice)'); + + % update coronal view + % + img_slice = squeeze(double(nii_view.disp(:,nii_view.slices.cor,:))); + h1 = nii_view.handles.coronal_image; + set(h1, 'cdata', double(img_slice)'); + + % update sagittal view + % + img_slice = squeeze(double(nii_view.disp(nii_view.slices.sag,:,:))); + + h1 = nii_view.handles.sagittal_image; + set(h1, 'cdata', double(img_slice)'); + + % remove disp field if un-check 'histeq' button + % + if ~get(nii_view.handles.hist_eq,'value') & isfield(nii_view, 'disp') + nii_view = rmfield(nii_view, 'disp'); + end + + update_nii_view(nii_view); + + set(fig,'Pointer',old_pointer); + + return; % hist_eq + + +%---------------------------------------------------------------- +function [top1_label, top2_label, side1_label, side2_label] = ... + dir_label(fig, top_ax, front_ax, side_ax) + + nii_view = getappdata(fig,'nii_view'); + + top_pos = get(top_ax,'position'); + front_pos = get(front_ax,'position'); + side_pos = get(side_ax,'position'); + + top_gap_x = (side_pos(1)-top_pos(1)-top_pos(3)) / (2*top_pos(3)); + top_gap_y = (front_pos(2)-top_pos(2)-top_pos(4)) / (2*top_pos(4)); + side_gap_x = (side_pos(1)-top_pos(1)-top_pos(3)) / (2*side_pos(3)); + side_gap_y = (front_pos(2)-top_pos(2)-top_pos(4)) / (2*side_pos(4)); + + top1_label_pos = [0, 1]; % rot0 + top2_label_pos = [1, 0]; % rot90 + side1_label_pos = [1, - side_gap_y]; % rot0 + side2_label_pos = [0, 0]; % rot90 + + if isempty(nii_view) + axes(top_ax); + top1_label = text(double(top1_label_pos(1)),double(top1_label_pos(2)), ... + '== X =>', ... + 'vertical', 'bottom', ... + 'unit', 'normal', 'fontsize', 8); + + axes(top_ax); + top2_label = text(double(top2_label_pos(1)),double(top2_label_pos(2)), ... + '== Y =>', ... + 'rotation', 90, 'vertical', 'top', ... + 'unit', 'normal', 'fontsize', 8); + + axes(side_ax); + side1_label = text(double(side1_label_pos(1)),double(side1_label_pos(2)), ... + '<= Y ==', ... + 'horizontal', 'right', 'vertical', 'top', ... + 'unit', 'normal', 'fontsize', 8); + + axes(side_ax); + side2_label = text(double(side2_label_pos(1)),double(side2_label_pos(2)), ... + '== Z =>', ... + 'rotation', 90, 'vertical', 'bottom', ... + 'unit', 'normal', 'fontsize', 8); + else + top1_label = nii_view.handles.top1_label; + top2_label = nii_view.handles.top2_label; + side1_label = nii_view.handles.side1_label; + side2_label = nii_view.handles.side2_label; + + set(top1_label, 'position', [top1_label_pos 0]); + set(top2_label, 'position', [top2_label_pos 0]); + set(side1_label, 'position', [side1_label_pos 0]); + set(side2_label, 'position', [side2_label_pos 0]); + end + + return; % dir_label + + +%---------------------------------------------------------------- +function update_enable(h, opt); + + nii_view = getappdata(h,'nii_view'); + handles = nii_view.handles; + + if isfield(opt,'enablecursormove') + if opt.enablecursormove + v = 'on'; + else + v = 'off'; + end + + set(handles.Timposcur, 'visible', v); + set(handles.imposcur, 'visible', v); + set(handles.Timvalcur, 'visible', v); + set(handles.imvalcur, 'visible', v); + end + + if isfield(opt,'enableviewpoint') + if opt.enableviewpoint + v = 'on'; + else + v = 'off'; + end + + set(handles.Timpos, 'visible', v); + set(handles.impos, 'visible', v); + set(handles.Timval, 'visible', v); + set(handles.imval, 'visible', v); + end + + if isfield(opt,'enableorigin') + if opt.enableorigin + v = 'on'; + else + v = 'off'; + end + + set(handles.Torigin, 'visible', v); + set(handles.origin, 'visible', v); + end + + if isfield(opt,'enableunit') + if opt.enableunit + v = 'on'; + else + v = 'off'; + end + + set(handles.Tcoord, 'visible', v); + set(handles.coord_frame, 'visible', v); + set(handles.coord, 'visible', v); + end + + if isfield(opt,'enablecrosshair') + if opt.enablecrosshair + v = 'on'; + else + v = 'off'; + end + + set(handles.Txhair, 'visible', v); + set(handles.xhair_color, 'visible', v); + set(handles.xhair, 'visible', v); + end + + if isfield(opt,'enablehistogram') + if opt.enablehistogram + v = 'on'; + vv = 'off'; + else + v = 'off'; + vv = 'on'; + end + + set(handles.Tcoord, 'visible', vv); + set(handles.coord_frame, 'visible', vv); + set(handles.coord, 'visible', vv); + + set(handles.Thist, 'visible', v); + set(handles.hist_frame, 'visible', v); + set(handles.hist_eq, 'visible', v); + set(handles.hist_plot, 'visible', v); + end + + if isfield(opt,'enablecolormap') + if opt.enablecolormap + v = 'on'; + else + v = 'off'; + end + + set(handles.Tcolor, 'visible', v); + set(handles.color_frame, 'visible', v); + set(handles.neg_color, 'visible', v); + set(handles.colorindex, 'visible', v); + end + + if isfield(opt,'enablecontrast') + if opt.enablecontrast + v = 'on'; + else + v = 'off'; + end + + set(handles.Tcontrast, 'visible', v); + set(handles.contrast_frame, 'visible', v); + set(handles.contrast_def, 'visible', v); + set(handles.contrast, 'visible', v); + end + + if isfield(opt,'enablebrightness') + if opt.enablebrightness + v = 'on'; + else + v = 'off'; + end + + set(handles.Tbrightness, 'visible', v); + set(handles.brightness_frame, 'visible', v); + set(handles.brightness_def, 'visible', v); + set(handles.brightness, 'visible', v); + end + + if isfield(opt,'enabledirlabel') + if opt.enabledirlabel + v = 'on'; + else + v = 'off'; + end + + set(handles.top1_label, 'visible', v); + set(handles.top2_label, 'visible', v); + set(handles.side1_label, 'visible', v); + set(handles.side2_label, 'visible', v); + end + + if isfield(opt,'enableslider') + if opt.enableslider + v = 'on'; + else + v = 'off'; + end + + if isfield(handles,'sagittal_slider') & ishandle(handles.sagittal_slider) + set(handles.sagittal_slider, 'visible', v); + end + + if isfield(handles,'coronal_slider') & ishandle(handles.coronal_slider) + set(handles.coronal_slider, 'visible', v); + end + + if isfield(handles,'axial_slider') & ishandle(handles.axial_slider) + set(handles.axial_slider, 'visible', v); + end + end + + return; % update_enable + + +%---------------------------------------------------------------- +function update_usepanel(fig, usepanel) + + if isempty(usepanel) + return; + end + + if usepanel + opt.enablecursormove = 1; + opt.enableviewpoint = 1; + opt.enableorigin = 1; + opt.enableunit = 1; + opt.enablecrosshair = 1; +% opt.enablehistogram = 1; + opt.enablecolormap = 1; + opt.enablecontrast = 1; + opt.enablebrightness = 1; + else + opt.enablecursormove = 0; + opt.enableviewpoint = 0; + opt.enableorigin = 0; + opt.enableunit = 0; + opt.enablecrosshair = 0; +% opt.enablehistogram = 0; + opt.enablecolormap = 0; + opt.enablecontrast = 0; + opt.enablebrightness = 0; + end + + update_enable(fig, opt); + + nii_view = getappdata(fig,'nii_view'); + nii_view.usepanel = usepanel; + setappdata(fig,'nii_view',nii_view); + + return; % update_usepanel + + +%---------------------------------------------------------------- +function update_usecrosshair(fig, usecrosshair) + + if isempty(usecrosshair) + return; + end + + if usecrosshair + v=1; + else + v=2; + end + + nii_view = getappdata(fig,'nii_view'); + set(nii_view.handles.xhair,'value',v); + + opt.command = 'crosshair'; + view_nii(fig, opt); + + return; % update_usecrosshair + + +%---------------------------------------------------------------- +function update_usestretch(fig, usestretch) + + nii_view = getappdata(fig,'nii_view'); + + handles = nii_view.handles; + fig = nii_view.fig; + area = nii_view.area; + vol_size = nii_view.voxel_size .* nii_view.dims; + + % Three Axes & label + % + [top_ax, front_ax, side_ax] = ... + create_ax(fig, area, vol_size, usestretch); + + dir_label(fig, top_ax, front_ax, side_ax); + + top_pos = get(top_ax,'position'); + front_pos = get(front_ax,'position'); + side_pos = get(side_ax,'position'); + + % Sagittal Slider + % + x = side_pos(1); + y = top_pos(2) + top_pos(4); + w = side_pos(3); + h = (front_pos(2) - y) / 2; + y = y + h; + pos = [x y w h]; + + if isfield(handles,'sagittal_slider') & ishandle(handles.sagittal_slider) + set(handles.sagittal_slider,'position',pos); + end + + % Coronal Slider + % + x = top_pos(1); + y = top_pos(2) + top_pos(4); + w = top_pos(3); + h = (front_pos(2) - y) / 2; + y = y + h; + pos = [x y w h]; + + if isfield(handles,'coronal_slider') & ishandle(handles.coronal_slider) + set(handles.coronal_slider,'position',pos); + end + + % Axial Slider + % + x = top_pos(1); + y = area(2); + w = top_pos(3); + h = top_pos(2) - y; + pos = [x y w h]; + + if isfield(handles,'axial_slider') & ishandle(handles.axial_slider) + set(handles.axial_slider,'position',pos); + end + + % plot info view + % +% info_pos = [side_pos([1,3]); top_pos([2,4])]; +% info_pos = info_pos(:); + gap = side_pos(1)-(top_pos(1)+top_pos(3)); + info_pos(1) = side_pos(1) + gap; + info_pos(2) = area(2); + info_pos(3) = side_pos(3) - gap; + info_pos(4) = top_pos(2) + top_pos(4) - area(2) - gap; + + num_inputline = 10; + inputline_space =info_pos(4) / num_inputline; + + + % Image Intensity Value at Cursor + % + x = info_pos(1); + y = info_pos(2); + w = info_pos(3)*0.5; + h = inputline_space*0.6; + + pos = [x y w h]; + set(handles.Timvalcur,'position',pos); + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.imvalcur,'position',pos); + + % Position at Cursor + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.Timposcur,'position',pos); + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.imposcur,'position',pos); + + % Image Intensity Value at Mouse Click + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.Timval,'position',pos); + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.imval,'position',pos); + + % Viewpoint Position at Mouse Click + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.Timpos,'position',pos); + + x = x + w + 0.005; + y = y - 0.008; + w = info_pos(3)*0.5; + h = inputline_space*0.9; + + pos = [x y w h]; + set(handles.impos,'position',pos); + + % Origin Position + % + x = info_pos(1); + y = y + inputline_space*1.2; + w = info_pos(3)*0.5; + h = inputline_space*0.6; + + pos = [x y w h]; + set(handles.Torigin,'position',pos); + + x = x + w; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.origin,'position',pos); + +if 0 + % Axes Unit + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.5; + + pos = [x y w h]; + set(handles.Tcoord,'position',pos); + + x = x + w + 0.005; + w = info_pos(3)*0.5 - 0.005; + + pos = [x y w h]; + set(handles.coord,'position',pos); +end + + % Crosshair + % + x = info_pos(1); + y = y + inputline_space; + w = info_pos(3)*0.4; + + pos = [x y w h]; + set(handles.Txhair,'position',pos); + + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.2; + h = inputline_space*0.7; + + pos = [x y w h]; + set(handles.xhair_color,'position',pos); + + x = info_pos(1) + info_pos(3)*0.7; + w = info_pos(3)*0.3; + + pos = [x y w h]; + set(handles.xhair,'position',pos); + + % Histogram & Color + % + x = info_pos(1); + w = info_pos(3)*0.45; + h = inputline_space * 1.5; + pos = [x, y+inputline_space*0.9, w, h]; + set(handles.hist_frame,'position',pos); + set(handles.coord_frame,'position',pos); + + x = info_pos(1) + info_pos(3)*0.475; + w = info_pos(3)*0.525; + h = inputline_space * 1.5; + + pos = [x, y+inputline_space*0.9, w, h]; + set(handles.color_frame,'position',pos); + + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space*1.2; + w = info_pos(3)*0.2; + h = inputline_space*0.7; + + pos = [x y w h]; + set(handles.hist_eq,'position',pos); + + x = x + w; + w = info_pos(3)*0.2; + + pos = [x y w h]; + set(handles.hist_plot,'position',pos); + + x = info_pos(1) + info_pos(3)*0.025; + w = info_pos(3)*0.4; + + pos = [x y w h]; + set(handles.coord,'position',pos); + + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.2; + pos = [x y w h]; + set(handles.neg_color,'position',pos); + + x = info_pos(1) + info_pos(3)*0.7; + w = info_pos(3)*0.275; + + pos = [x y w h]; + set(handles.colorindex,'position',pos); + + x = info_pos(1) + info_pos(3)*0.1; + y = y + inputline_space; + w = info_pos(3)*0.28; + h = inputline_space*0.6; + + pos = [x y w h]; + set(handles.Thist,'position',pos); + set(handles.Tcoord,'position',pos); + + x = info_pos(1) + info_pos(3)*0.60; + w = info_pos(3)*0.28; + + pos = [x y w h]; + set(handles.Tcolor,'position',pos); + + % Contrast Frame + % + x = info_pos(1); + w = info_pos(3)*0.45; + h = inputline_space * 2; + + pos = [x, y+inputline_space*0.8, w, h]; + set(handles.contrast_frame,'position',pos); + + % Brightness Frame + % + x = info_pos(1) + info_pos(3)*0.475; + w = info_pos(3)*0.525; + + pos = [x, y+inputline_space*0.8, w, h]; + set(handles.brightness_frame,'position',pos); + + % Contrast + % + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space; + w = info_pos(3)*0.4; + h = inputline_space*0.6; + + pos = [x y w h]; + set(handles.contrast,'position',pos); + + % Brightness + % + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.475; + + pos = [x y w h]; + set(handles.brightness,'position',pos); + + % Contrast text/def + % + x = info_pos(1) + info_pos(3)*0.025; + y = y + inputline_space; + w = info_pos(3)*0.22; + + pos = [x y w h]; + set(handles.Tcontrast,'position',pos); + + x = x + w; + w = info_pos(3)*0.18; + + pos = [x y w h]; + set(handles.contrast_def,'position',pos); + + % Brightness text/def + % + x = info_pos(1) + info_pos(3)*0.5; + w = info_pos(3)*0.295; + + pos = [x y w h]; + set(handles.Tbrightness,'position',pos); + + x = x + w; + w = info_pos(3)*0.18; + + pos = [x y w h]; + set(handles.brightness_def,'position',pos); + + return; % update_usestretch + + +%---------------------------------------------------------------- +function update_useinterp(fig, useinterp) + + if isempty(useinterp) + return; + end + + nii_menu = getappdata(fig, 'nii_menu'); + + if ~isempty(nii_menu) + if get(nii_menu.Minterp,'user') + set(nii_menu.Minterp,'Userdata',0,'Label','Interp off'); + else + set(nii_menu.Minterp,'Userdata',1,'Label','Interp on'); + end + end + + nii_view = getappdata(fig, 'nii_view'); + nii_view.useinterp = useinterp; + + if ~isempty(nii_view.handles.axial_image) + if strcmpi(get(nii_view.handles.axial_image,'cdatamapping'), 'direct') + useimagesc = 0; + else + useimagesc = 1; + end + elseif ~isempty(nii_view.handles.coronal_image) + if strcmpi(get(nii_view.handles.coronal_image,'cdatamapping'), 'direct') + useimagesc = 0; + else + useimagesc = 1; + end + else + if strcmpi(get(nii_view.handles.sagittal_image,'cdatamapping'), 'direct') + useimagesc = 0; + else + useimagesc = 1; + end + end + + if ~isempty(nii_view.handles.axial_image) + img_slice = get(nii_view.handles.axial_image, 'cdata'); + delete(nii_view.handles.axial_image); + axes(nii_view.handles.axial_axes); + clim = get(gca,'clim'); + + if useinterp + if useimagesc + nii_view.handles.axial_image = surface(zeros(size(img_slice)),double(img_slice),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.axial_image = surface(zeros(size(img_slice)),double(img_slice),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + else + if useimagesc + nii_view.handles.axial_image = imagesc('cdata',img_slice); + else + nii_view.handles.axial_image = image('cdata',img_slice); + end + end + + set(gca,'clim',clim); + + order = get(gca,'child'); + order(find(order == nii_view.handles.axial_image)) = []; + order = [order; nii_view.handles.axial_image]; + + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) + order(find(order == nii_view.handles.axial_bg)) = []; + order = [order; nii_view.handles.axial_bg]; + end + + set(gca, 'child', order); + + if ~useinterp + if isfield(nii_view.handles,'axial_bg') & ~isempty(nii_view.handles.axial_bg) + delete(nii_view.handles.axial_bg); + nii_view.handles.axial_bg = []; + end + end + + set(nii_view.handles.axial_image,'buttondown','view_nii(''axial_image'');'); + end + + if ~isempty(nii_view.handles.coronal_image) + img_slice = get(nii_view.handles.coronal_image, 'cdata'); + delete(nii_view.handles.coronal_image); + axes(nii_view.handles.coronal_axes); + clim = get(gca,'clim'); + + if useinterp + if useimagesc + nii_view.handles.coronal_image = surface(zeros(size(img_slice)),double(img_slice),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.coronal_image = surface(zeros(size(img_slice)),double(img_slice),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + else + if useimagesc + nii_view.handles.coronal_image = imagesc('cdata',img_slice); + else + nii_view.handles.coronal_image = image('cdata',img_slice); + end + end + + set(gca,'clim',clim); + + order = get(gca,'child'); + order(find(order == nii_view.handles.coronal_image)) = []; + order = [order; nii_view.handles.coronal_image]; + + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) + order(find(order == nii_view.handles.coronal_bg)) = []; + order = [order; nii_view.handles.coronal_bg]; + end + + set(gca, 'child', order); + + if ~useinterp + if isfield(nii_view.handles,'coronal_bg') & ~isempty(nii_view.handles.coronal_bg) + delete(nii_view.handles.coronal_bg); + nii_view.handles.coronal_bg = []; + end + end + + set(nii_view.handles.coronal_image,'buttondown','view_nii(''coronal_image'');'); + end + + if ~isempty(nii_view.handles.sagittal_image) + img_slice = get(nii_view.handles.sagittal_image, 'cdata'); + delete(nii_view.handles.sagittal_image); + axes(nii_view.handles.sagittal_axes); + clim = get(gca,'clim'); + + if useinterp + if useimagesc + nii_view.handles.sagittal_image = surface(zeros(size(img_slice)),double(img_slice),'edgecolor','none','facecolor','interp'); + else + nii_view.handles.sagittal_image = surface(zeros(size(img_slice)),double(img_slice),'cdatamapping','direct','edgecolor','none','facecolor','interp'); + end + else + if useimagesc + nii_view.handles.sagittal_image = imagesc('cdata',img_slice); + else + nii_view.handles.sagittal_image = image('cdata',img_slice); + end + end + + set(gca,'clim',clim); + + order = get(gca,'child'); + order(find(order == nii_view.handles.sagittal_image)) = []; + order = [order; nii_view.handles.sagittal_image]; + + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) + order(find(order == nii_view.handles.sagittal_bg)) = []; + order = [order; nii_view.handles.sagittal_bg]; + end + + set(gca, 'child', order); + + if ~useinterp + if isfield(nii_view.handles,'sagittal_bg') & ~isempty(nii_view.handles.sagittal_bg) + delete(nii_view.handles.sagittal_bg); + nii_view.handles.sagittal_bg = []; + end + end + + set(nii_view.handles.sagittal_image,'buttondown','view_nii(''sagittal_image'');'); + end + + if ~useinterp + nii_view.bgimg = []; + end + + set_coordinates(nii_view,useinterp); + setappdata(fig, 'nii_view', nii_view); + + return; % update_useinterp + + +%---------------------------------------------------------------- +function update_useimagesc(fig, useimagesc) + + if isempty(useimagesc) + return; + end + + if useimagesc + v='scaled'; + else + v='direct'; + end + + nii_view = getappdata(fig,'nii_view'); + handles = nii_view.handles; + + if isfield(handles,'cbar_image') & ishandle(handles.cbar_image) +% set(handles.cbar_image,'cdatamapping',v); + end + + set(handles.axial_image,'cdatamapping',v); + set(handles.coronal_image,'cdatamapping',v); + set(handles.sagittal_image,'cdatamapping',v); + + return; % update_useimagesc + + +%---------------------------------------------------------------- +function update_shape(fig, area, usecolorbar, usestretch, useimagesc) + + nii_view = getappdata(fig,'nii_view'); + + if isempty(usestretch) % no change, get usestretch + stretchchange = 0; + usestretch = nii_view.usestretch; + else % change, set usestretch + stretchchange = 1; + nii_view.usestretch = usestretch; + end + + if isempty(area) % no change, get area + + areachange = 0; + area = nii_view.area; + + elseif ~isempty(nii_view.cbar_area) % change, set area & cbar_area + + areachange = 1; + cbar_area = area; + cbar_area(1) = area(1) + area(3)*0.93; + cbar_area(3) = area(3)*0.04; + area(3) = area(3)*0.9; % 90% used for main axes + + [cbar_axes cbarminmax_axes] = create_cbar_axes(fig, cbar_area); + + nii_view.area = area; + nii_view.cbar_area = cbar_area; + + else % change, set area only + areachange = 1; + nii_view.area = area; + end + + % Add colorbar + % + if ~isempty(usecolorbar) & usecolorbar & isempty(nii_view.cbar_area) + + colorbarchange = 1; + + cbar_area = area; + cbar_area(1) = area(1) + area(3)*0.93; + cbar_area(3) = area(3)*0.04; + area(3) = area(3)*0.9; % 90% used for main axes + + % create axes for colorbar + % + [cbar_axes cbarminmax_axes] = create_cbar_axes(fig, cbar_area); + + nii_view.area = area; + nii_view.cbar_area = cbar_area; + + % useimagesc follows axial image + % + if isempty(useimagesc) + if strcmpi(get(nii_view.handles.axial_image,'cdatamap'),'scaled') + useimagesc = 1; + else + useimagesc = 0; + end + end + + if isfield(nii_view, 'highcolor') & ~isempty(highcolor) + num_highcolor = size(nii_view.highcolor,1); + else + num_highcolor = 0; + end + + if isfield(nii_view, 'colorlevel') & ~isempty(nii_view.colorlevel) + colorlevel = nii_view.colorlevel; + else + colorlevel = 256 - num_highcolor; + end + + if isfield(nii_view, 'color_map') + color_map = nii_view.color_map; + else + color_map = []; + end + + if isfield(nii_view, 'highcolor') + highcolor = nii_view.highcolor; + else + highcolor = []; + end + + % plot colorbar + % +if 0 + if isempty(color_map) + level = colorlevel + num_highcolor; + else + level = size([color_map; highcolor], 1); + end +end + + if isempty(color_map) + level = colorlevel; + else + level = size([color_map], 1); + end + + cbar_image = [1:level]'; + + niiclass = class(nii_view.nii.img); + + h1 = plot_cbar(fig, cbar_axes, cbarminmax_axes, nii_view.cbarminmax, ... + level, nii_view.handles, useimagesc, nii_view.colorindex, ... + color_map, colorlevel, highcolor, niiclass, nii_view.numscan); + nii_view.handles.cbar_image = h1; + nii_view.handles.cbar_axes = cbar_axes; + nii_view.handles.cbarminmax_axes = cbar_axes; + + % remove colorbar + % + elseif ~isempty(usecolorbar) & ~usecolorbar & ~isempty(nii_view.cbar_area) + + colorbarchange = 1; + + area(3) = area(3) / 0.9; + + nii_view.area = area; + nii_view.cbar_area = []; + + nii_view.handles = rmfield(nii_view.handles,'cbar_image'); + delete(nii_view.handles.cbarminmax_axes); + nii_view.handles = rmfield(nii_view.handles,'cbarminmax_axes'); + delete(nii_view.handles.cbar_axes); + nii_view.handles = rmfield(nii_view.handles,'cbar_axes'); + + else + colorbarchange = 0; + end + + if colorbarchange | stretchchange | areachange + setappdata(fig,'nii_view',nii_view); + update_usestretch(fig, usestretch); + end + + return; % update_shape + + +%---------------------------------------------------------------- +function update_unit(fig, setunit) + + if isempty(setunit) + return; + end + + if strcmpi(setunit,'mm') | strcmpi(setunit,'millimeter') | strcmpi(setunit,'mni') + v = 2; +% elseif strcmpi(setunit,'tal') | strcmpi(setunit,'talairach') + % v = 3; + elseif strcmpi(setunit,'vox') | strcmpi(setunit,'voxel') + v = 1; + else + v = 1; + end + + nii_view = getappdata(fig,'nii_view'); + set(nii_view.handles.coord, 'value', v); + set_image_value(nii_view); + + return; % update_unit + + +%---------------------------------------------------------------- +function update_viewpoint(fig, setviewpoint) + + if isempty(setviewpoint) + return; + end + + nii_view = getappdata(fig,'nii_view'); + + if length(setviewpoint) ~= 3 + error('Viewpoint position should contain [x y z]'); + end + + set(nii_view.handles.impos,'string',num2str(setviewpoint)); + + opt.command = 'impos_edit'; + view_nii(fig, opt); + + set(nii_view.handles.axial_axes,'selected','on'); + set(nii_view.handles.axial_axes,'selected','off'); + set(nii_view.handles.coronal_axes,'selected','on'); + set(nii_view.handles.coronal_axes,'selected','off'); + set(nii_view.handles.sagittal_axes,'selected','on'); + set(nii_view.handles.sagittal_axes,'selected','off'); + + return; % update_viewpoint + + +%---------------------------------------------------------------- +function update_scanid(fig, setscanid) + + if isempty(setscanid) + return; + end + + nii_view = getappdata(fig,'nii_view'); + + if setscanid < 1 + setscanid = 1; + end + + if setscanid > nii_view.numscan + setscanid = nii_view.numscan; + end + + set(nii_view.handles.contrast_def,'string',num2str(setscanid)); + set(nii_view.handles.contrast,'value',setscanid); + + opt.command = 'updateimg'; + opt.setscanid = setscanid; + + view_nii(fig, nii_view.nii.img, opt); + + return; % update_scanid + + +%---------------------------------------------------------------- +function update_crosshaircolor(fig, new_color) + + if isempty(new_color) + return; + end + + nii_view = getappdata(fig,'nii_view'); + xhair_color = nii_view.handles.xhair_color; + + set(xhair_color,'user',new_color); + set(nii_view.axi_xhair.lx,'color',new_color); + set(nii_view.axi_xhair.ly,'color',new_color); + set(nii_view.cor_xhair.lx,'color',new_color); + set(nii_view.cor_xhair.ly,'color',new_color); + set(nii_view.sag_xhair.lx,'color',new_color); + set(nii_view.sag_xhair.ly,'color',new_color); + + return; % update_crosshaircolor + + +%---------------------------------------------------------------- +function update_colorindex(fig, colorindex) + + if isempty(colorindex) + return; + end + + nii_view = getappdata(fig,'nii_view'); + nii_view.colorindex = colorindex; + setappdata(fig, 'nii_view', nii_view); + set(nii_view.handles.colorindex,'value',colorindex); + + opt.command = 'color'; + view_nii(fig, opt); + + return; % update_colorindex + + +%---------------------------------------------------------------- +function redraw_cbar(fig, colorlevel, color_map, highcolor) + + nii_view = getappdata(fig,'nii_view'); + + if isempty(nii_view.cbar_area) + return; + end + + colorindex = nii_view.colorindex; + + if isempty(highcolor) + num_highcolor = 0; + else + num_highcolor = size(highcolor,1); + end + + if isempty(colorlevel) + colorlevel=256; + end + + if colorindex == 1 + colorlevel = size(color_map, 1); + end + +% level = colorlevel + num_highcolor; + level = colorlevel; + + cbar_image = [1:level]'; + + cbar_area = nii_view.cbar_area; + + % useimagesc follows axial image + % + if strcmpi(get(nii_view.handles.axial_image,'cdatamap'),'scaled') + useimagesc = 1; + else + useimagesc = 0; + end + + niiclass = class(nii_view.nii.img); + + delete(nii_view.handles.cbar_image); + delete(nii_view.handles.cbar_axes); + delete(nii_view.handles.cbarminmax_axes); + + [nii_view.handles.cbar_axes nii_view.handles.cbarminmax_axes] = ... + create_cbar_axes(fig, cbar_area, []); + + nii_view.handles.cbar_image = plot_cbar(fig, ... + nii_view.handles.cbar_axes, nii_view.handles.cbarminmax_axes, ... + nii_view.cbarminmax, level, nii_view.handles, useimagesc, ... + colorindex, color_map, colorlevel, highcolor, niiclass, ... + nii_view.numscan, []); + + setappdata(fig, 'nii_view', nii_view); + + return; % redraw_cbar + + +%---------------------------------------------------------------- +function update_buttondown(fig, setbuttondown) + + if isempty(setbuttondown) + return; + end + + nii_view = getappdata(fig,'nii_view'); + nii_view.buttondown = setbuttondown; + setappdata(fig, 'nii_view', nii_view); + + return; % update_buttondown + + +%---------------------------------------------------------------- +function update_cbarminmax(fig, cbarminmax) + + if isempty(cbarminmax) + return; + end + + nii_view = getappdata(fig, 'nii_view'); + + if ~isfield(nii_view.handles, 'cbarminmax_axes') + return; + end + + nii_view.cbarminmax = cbarminmax; + setappdata(fig, 'nii_view', nii_view); + + axes(nii_view.handles.cbarminmax_axes); + + plot([0 0], cbarminmax, 'w'); + axis tight; + + set(nii_view.handles.cbarminmax_axes,'YDir','normal', ... + 'XLimMode','manual','YLimMode','manual','YColor',[0 0 0], ... + 'XColor',[0 0 0],'xtick',[],'YAxisLocation','right'); + + ylim = get(nii_view.handles.cbar_axes,'ylim'); + ylimb = get(nii_view.handles.cbarminmax_axes,'ylim'); + ytickb = get(nii_view.handles.cbarminmax_axes,'ytick'); + ytick=(ylim(2)-ylim(1))*(ytickb-ylimb(1))/(ylimb(2)-ylimb(1))+ylim(1); + + axes(nii_view.handles.cbar_axes); + + set(nii_view.handles.cbar_axes,'YDir','normal','XLimMode','manual', ... + 'YLimMode','manual','YColor',[0 0 0],'XColor',[0 0 0],'xtick',[], ... + 'YAxisLocation','right','ylim',ylim,'ytick',ytick,'yticklabel',''); + + return; % update_cbarminmax + + +%---------------------------------------------------------------- +function update_highcolor(fig, highcolor, colorlevel) + + nii_view = getappdata(fig,'nii_view'); + + if ischar(highcolor) & (isempty(colorlevel) | nii_view.colorindex == 1) + return; + end + + if ~ischar(highcolor) + nii_view.highcolor = highcolor; + + if isempty(highcolor) + nii_view = rmfield(nii_view, 'highcolor'); + end + else + highcolor = []; + end + + if isempty(colorlevel) | nii_view.colorindex == 1 + nii_view.colorlevel = nii_view.colorlevel - size(highcolor,1); + else + nii_view.colorlevel = colorlevel; + end + + setappdata(fig, 'nii_view', nii_view); + + if isfield(nii_view,'color_map') + color_map = nii_view.color_map; + else + color_map = []; + end + + redraw_cbar(fig, nii_view.colorlevel, color_map, highcolor); + change_colormap(fig); + + return; % update_highcolor + + +%---------------------------------------------------------------- +function update_colormap(fig, color_map) + + if ischar(color_map) + return; + end + + nii_view = getappdata(fig,'nii_view'); + nii = nii_view.nii; + minvalue = nii_view.minvalue; + + if isempty(color_map) + if minvalue < 0 + colorindex = 2; + else + colorindex = 3; + end + + nii_view = rmfield(nii_view, 'color_map'); + setappdata(fig,'nii_view',nii_view); + update_colorindex(fig, colorindex); + return; + else + colorindex = 1; + nii_view.color_map = color_map; + nii_view.colorindex = colorindex; + setappdata(fig,'nii_view',nii_view); + set(nii_view.handles.colorindex,'value',colorindex); + end + + colorlevel = nii_view.colorlevel; + + if isfield(nii_view, 'highcolor') + highcolor = nii_view.highcolor; + else + highcolor = []; + end + + redraw_cbar(fig, colorlevel, color_map, highcolor); + change_colormap(fig); + + opt.enablecontrast = 0; + update_enable(fig, opt); + + return; % update_colormap + + +%---------------------------------------------------------------- +function status = get_status(h); + + nii_view = getappdata(h,'nii_view'); + + status.fig = h; + status.area = nii_view.area; + + if isempty(nii_view.cbar_area) + status.usecolorbar = 0; + else + status.usecolorbar = 1; + width = status.area(3) / 0.9; + status.area(3) = width; + end + + if strcmpi(get(nii_view.handles.imval,'visible'), 'on') + status.usepanel = 1; + else + status.usepanel = 0; + end + + if get(nii_view.handles.xhair,'value') == 1 + status.usecrosshair = 1; + else + status.usecrosshair = 0; + end + + status.usestretch = nii_view.usestretch; + + if strcmpi(get(nii_view.handles.axial_image,'cdatamapping'), 'direct') + status.useimagesc = 0; + else + status.useimagesc = 1; + end + + status.useinterp = nii_view.useinterp; + + if get(nii_view.handles.coord,'value') == 1 + status.unit = 'vox'; + elseif get(nii_view.handles.coord,'value') == 2 + status.unit = 'mm'; + elseif get(nii_view.handles.coord,'value') == 3 + status.unit = 'tal'; + end + + status.viewpoint = get(nii_view.handles.impos,'value'); + status.scanid = nii_view.scanid; + status.intensity = get(nii_view.handles.imval,'value'); + status.colorindex = get(nii_view.handles.colorindex,'value'); + + if isfield(nii_view,'color_map') + status.colormap = nii_view.color_map; + else + status.colormap = []; + end + + status.colorlevel = nii_view.colorlevel; + + if isfield(nii_view,'highcolor') + status.highcolor = nii_view.highcolor; + else + status.highcolor = []; + end + + status.cbarminmax = nii_view.cbarminmax; + status.buttondown = nii_view.buttondown; + + return; % get_status + + +%---------------------------------------------------------------- +function [custom_color_map, colorindex] ... + = change_colormap(fig, nii, colorindex, cbarminmax) + + custom_color_map = []; + + if ~exist('nii', 'var') + nii_view = getappdata(fig,'nii_view'); + else + nii_view = nii; + end + + if ~exist('colorindex', 'var') + colorindex = get(nii_view.handles.colorindex,'value'); + end + + if ~exist('cbarminmax', 'var') + cbarminmax = nii_view.cbarminmax; + end + + if isfield(nii_view, 'highcolor') & ~isempty(nii_view.highcolor) + highcolor = nii_view.highcolor; + num_highcolor = size(highcolor,1); + else + highcolor = []; + num_highcolor = 0; + end + +% if isfield(nii_view, 'colorlevel') & ~isempty(nii_view.colorlevel) + if nii_view.colorlevel < 256 + num_color = nii_view.colorlevel; + else + num_color = 256 - num_highcolor; + end + + contrast = []; + + if colorindex == 3 % for gray + if nii_view.numscan > 1 + contrast = 1; + else + contrast = (num_color-1)*(get(nii_view.handles.contrast,'value')-1)/255+1; + contrast = floor(contrast); + end + elseif colorindex == 2 % for bipolar + if nii_view.numscan > 1 + contrast = 128; + else + contrast = get(nii_view.handles.contrast,'value'); + end + end + + if isfield(nii_view,'color_map') & ~isempty(nii_view.color_map) + color_map = nii_view.color_map; + custom_color_map = color_map; + elseif colorindex == 1 + [f p] = uigetfile('*.txt', 'Input colormap text file'); + + if p==0 + colorindex = nii_view.colorindex; + set(nii_view.handles.colorindex,'value',colorindex); + return; + end; + + try + custom_color_map = load(fullfile(p,f)); + loadfail = 0; + catch + loadfail = 1; + end + + if loadfail | isempty(custom_color_map) | size(custom_color_map,2)~=3 ... + | min(custom_color_map(:)) < 0 | max(custom_color_map(:)) > 1 + + msg = 'Colormap should be a Mx3 matrix with value between 0 and 1'; + msgbox(msg,'Error in colormap file'); + colorindex = nii_view.colorindex; + set(nii_view.handles.colorindex,'value',colorindex); + return; + end + + color_map = custom_color_map; + nii_view.color_map = color_map; + end + + switch colorindex + case {2} + color_map = bipolar(num_color, cbarminmax(1), cbarminmax(2), contrast); + case {3} + color_map = gray(num_color - contrast + 1); + case {4} + color_map = jet(num_color); + case {5} + color_map = cool(num_color); + case {6} + color_map = bone(num_color); + case {7} + color_map = hot(num_color); + case {8} + color_map = copper(num_color); + case {9} + color_map = pink(num_color); + end + + nii_view.colorindex = colorindex; + + if ~exist('nii', 'var') + setappdata(fig,'nii_view',nii_view); + end + + if colorindex == 3 + color_map = [zeros(contrast,3); color_map(2:end,:)]; + end + + if get(nii_view.handles.neg_color,'value') & isempty(highcolor) + color_map = flipud(color_map); + elseif get(nii_view.handles.neg_color,'value') & ~isempty(highcolor) + highcolor = flipud(highcolor); + end + + brightness = get(nii_view.handles.brightness,'value'); + color_map = brighten(color_map, brightness); + + color_map = [color_map; highcolor]; + + set(fig, 'colormap', color_map); + + return; % change_colormap + + +%---------------------------------------------------------------- +function move_cursor(fig) + + nii_view = getappdata(fig, 'nii_view'); + + if isempty(nii_view) + return; + end + + axi = get(nii_view.handles.axial_axes, 'pos'); + cor = get(nii_view.handles.coronal_axes, 'pos'); + sag = get(nii_view.handles.sagittal_axes, 'pos'); + curr = get(fig, 'currentpoint'); + + if curr(1) >= axi(1) & curr(1) <= axi(1)+axi(3) & ... + curr(2) >= axi(2) & curr(2) <= axi(2)+axi(4) + + curr = get(nii_view.handles.axial_axes, 'current'); + sag = curr(1,1); + cor = curr(1,2); + axi = nii_view.slices.axi; + + elseif curr(1) >= cor(1) & curr(1) <= cor(1)+cor(3) & ... + curr(2) >= cor(2) & curr(2) <= cor(2)+cor(4) + + curr = get(nii_view.handles.coronal_axes, 'current'); + sag = curr(1,1); + cor = nii_view.slices.cor; + axi = curr(1,2); + + elseif curr(1) >= sag(1) & curr(1) <= sag(1)+sag(3) & ... + curr(2) >= sag(2) & curr(2) <= sag(2)+sag(4) + + curr = get(nii_view.handles.sagittal_axes, 'current'); + + sag = nii_view.slices.sag; + cor = curr(1,1); + axi = curr(1,2); + + else + + set(nii_view.handles.imvalcur,'String',' '); + set(nii_view.handles.imposcur,'String',' '); + return; + + end + + sag = round(sag); + cor = round(cor); + axi = round(axi); + + if sag < 1 + sag = 1; + elseif sag > nii_view.dims(1) + sag = nii_view.dims(1); + end + + if cor < 1 + cor = 1; + elseif cor > nii_view.dims(2) + cor = nii_view.dims(2); + end + + if axi < 1 + axi = 1; + elseif axi > nii_view.dims(3) + axi = nii_view.dims(3); + end + + if 0 % isfield(nii_view, 'disp') + img = nii_view.disp; + else + img = nii_view.nii.img; + end + + if nii_view.nii.hdr.dime.datatype == 128 + imgvalue = [double(img(sag,cor,axi,1,nii_view.scanid)) double(img(sag,cor,axi,2,nii_view.scanid)) double(img(sag,cor,axi,3,nii_view.scanid))]; + set(nii_view.handles.imvalcur,'String',sprintf('%7.4g %7.4g %7.4g',imgvalue)); + elseif nii_view.nii.hdr.dime.datatype == 511 + R = double(img(sag,cor,axi,1,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + G = double(img(sag,cor,axi,2,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + B = double(img(sag,cor,axi,3,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + imgvalue = [R G B]; + set(nii_view.handles.imvalcur,'String',sprintf('%7.4g %7.4g %7.4g',imgvalue)); + else + imgvalue = double(img(sag,cor,axi,nii_view.scanid)); + + if isnan(imgvalue) | imgvalue > nii_view.cbarminmax(2) + imgvalue = 0; + end + + set(nii_view.handles.imvalcur,'String',sprintf('%.6g',imgvalue)); + end + + nii_view.slices.sag = sag; + nii_view.slices.cor = cor; + nii_view.slices.axi = axi; + + nii_view = update_imgXYZ(nii_view); + + if get(nii_view.handles.coord,'value') == 1, + sag = nii_view.imgXYZ.vox(1); + cor = nii_view.imgXYZ.vox(2); + axi = nii_view.imgXYZ.vox(3); + elseif get(nii_view.handles.coord,'value') == 2, + sag = nii_view.imgXYZ.mm(1); + cor = nii_view.imgXYZ.mm(2); + axi = nii_view.imgXYZ.mm(3); + elseif get(nii_view.handles.coord,'value') == 3, + sag = nii_view.imgXYZ.tal(1); + cor = nii_view.imgXYZ.tal(2); + axi = nii_view.imgXYZ.tal(3); + end + + if get(nii_view.handles.coord,'value') == 1, + string = sprintf('%7.0f %7.0f %7.0f',sag,cor,axi); + else + string = sprintf('%7.1f %7.1f %7.1f',sag,cor,axi); + end; + + set(nii_view.handles.imposcur,'String',string); + + return; % move_cursor + + +%---------------------------------------------------------------- +function change_scan(hdl_str) + + fig = gcbf; + nii_view = getappdata(fig,'nii_view'); + + if strcmpi(hdl_str, 'edit_change_scan') % edit + hdl = nii_view.handles.contrast_def; + setscanid = round(str2num(get(hdl, 'string'))); + else % slider + hdl = nii_view.handles.contrast; + setscanid = round(get(hdl, 'value')); + end + + update_scanid(fig, setscanid); + + return; % change_scan + + +%---------------------------------------------------------------- +function val = scale_in(val, minval, maxval, range) + + % scale value into range + % + val = range*(double(val)-double(minval))/(double(maxval)-double(minval))+1; + + return; % scale_in + + +%---------------------------------------------------------------- +function val = scale_out(val, minval, maxval, range) + + % according to [minval maxval] and range of color levels (e.g. 199) + % scale val back from any thing between 1~256 to a small number that + % is corresonding to [minval maxval]. + % + val = (double(val)-1)*(double(maxval)-double(minval))/range+double(minval); + + return; % scale_out + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m new file mode 100755 index 00000000..d113053b --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m @@ -0,0 +1,480 @@ +% Imbed Zoom, Interp, and Info menu to view_nii window. +% +% Usage: view_nii_menu(fig); +% + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +%-------------------------------------------------------------------- +function menu_hdl = view_nii_menu(fig, varargin) + + if isnumeric(fig) + menu_hdl = init(fig); + return; + end + + menu_hdl = []; + + switch fig + case 'interp' + if nargin > 1 + fig = varargin{1}; + else + fig = gcbf; + end + + nii_menu = getappdata(fig, 'nii_menu'); + interp_on_state = get(nii_menu.Minterp,'Userdata'); + + if (interp_on_state == 1) + opt.useinterp = 1; + view_nii(fig,opt); + set(nii_menu.Minterp,'Userdata',0,'Label','Interp off'); + reset_zoom(fig); + else + opt.useinterp = 0; + view_nii(fig,opt); + set(nii_menu.Minterp,'Userdata',1,'Label','Interp on'); + reset_zoom(fig); + end + case 'reset_zoom' + if nargin > 1 + fig = varargin{1}; + else + fig = gcbf; + end + + reset_zoom(fig); + case 'orient' + orient; + case 'editvox' + editvox; + case 'img_info' + img_info; + case 'img_hist' + img_hist; + case 'save_disp' + save_disp; + end + + return % view_nii_menu + + +%-------------------------------------------------------------------- +function menu_hdl = init(fig) + + % search for edit, view menu + % + nii_menu.Mfile = []; + nii_menu.Medit = []; + nii_menu.Mview = []; + menuitems = findobj(fig, 'type', 'uimenu'); + + for i=1:length(menuitems) + filelabel = get(menuitems(i),'label'); + + if strcmpi(strrep(filelabel, '&', ''), 'file') + nii_menu.Mfile = menuitems(i); + end + + editlabel = get(menuitems(i),'label'); + + if strcmpi(strrep(editlabel, '&', ''), 'edit') + nii_menu.Medit = menuitems(i); + end + + viewlabel = get(menuitems(i),'label'); + + if strcmpi(strrep(viewlabel, '&', ''), 'view') + nii_menu.Mview = menuitems(i); + end + end + + set(fig, 'menubar', 'none'); + + if isempty(nii_menu.Mfile) + nii_menu.Mfile = uimenu('Parent',fig, ... + 'Label','File'); + + nii_menu.Mfile_save = uimenu('Parent',nii_menu.Mfile, ... + 'Label','Save displayed image as ...', ... + 'Callback','view_nii_menu(''save_disp'');'); + else + nii_menu.Mfile_save = uimenu('Parent',nii_menu.Mfile, ... + 'Label','Save displayed image as ...', ... + 'separator','on', ... + 'Callback','view_nii_menu(''save_disp'');'); + end + + if isempty(nii_menu.Medit) + nii_menu.Medit = uimenu('Parent',fig, ... + 'Label','Edit'); + + nii_menu.Medit_orient = uimenu('Parent',nii_menu.Medit, ... + 'Label','Convert to RAS orientation', ... + 'Callback','view_nii_menu(''orient'');'); + + nii_menu.Medit_editvox = uimenu('Parent',nii_menu.Medit, ... + 'Label','Edit voxel value at crosshair', ... + 'Callback','view_nii_menu(''editvox'');'); + else + nii_menu.Medit_orient = uimenu('Parent',nii_menu.Medit, ... + 'Label','Convert to RAS orientation', ... + 'separator','on', ... + 'Callback','view_nii_menu(''orient'');'); + + nii_menu.Medit_editvox = uimenu('Parent',nii_menu.Medit, ... + 'Label','Edit voxel value at crosshair', ... + 'Callback','view_nii_menu(''editvox'');'); + end + + if isempty(nii_menu.Mview) + nii_menu.Mview = uimenu('Parent',fig, ... + 'Label','View'); + + nii_menu.Mview_info = uimenu('Parent',nii_menu.Mview, ... + 'Label','Image Information', ... + 'Callback','view_nii_menu(''img_info'');'); + + nii_menu.Mview_info = uimenu('Parent',nii_menu.Mview, ... + 'Label','Volume Histogram', ... + 'Callback','view_nii_menu(''img_hist'');'); + else + nii_menu.Mview_info = uimenu('Parent',nii_menu.Mview, ... + 'Label','Image Information', ... + 'separator','on', ... + 'Callback','view_nii_menu(''img_info'');'); + + nii_menu.Mview_info = uimenu('Parent',nii_menu.Mview, ... + 'Label','Volume Histogram', ... + 'Callback','view_nii_menu(''img_hist'');'); + end + + nii_menu.Mzoom = rri_zoom_menu(fig); + + nii_menu.Minterp = uimenu('Parent',fig, ... + 'Label','Interp on', ... + 'Userdata', 1, ... + 'Callback','view_nii_menu(''interp'');'); + + setappdata(fig,'nii_menu',nii_menu); + menu_hdl = nii_menu.Minterp; + + return % init + + +%---------------------------------------------------------------- +function reset_zoom(fig) + + old_handle_vis = get(fig, 'HandleVisibility'); + set(fig, 'HandleVisibility', 'on'); + + nii_view = getappdata(fig, 'nii_view'); + nii_menu = getappdata(fig, 'nii_menu'); + + set(nii_menu.Mzoom,'Userdata',1,'Label','Zoom on'); + set(fig,'pointer','arrow'); + zoom off; + + axes(nii_view.handles.axial_axes); + setappdata(get(gca,'zlabel'), 'ZOOMAxesData', ... + [get(gca, 'xlim') get(gca, 'ylim')]) +% zoom reset; + % zoom getlimits; + zoom out; + + axes(nii_view.handles.coronal_axes); + setappdata(get(gca,'zlabel'), 'ZOOMAxesData', ... + [get(gca, 'xlim') get(gca, 'ylim')]) +% zoom reset; + % zoom getlimits; + zoom out; + + axes(nii_view.handles.sagittal_axes); + setappdata(get(gca,'zlabel'), 'ZOOMAxesData', ... + [get(gca, 'xlim') get(gca, 'ylim')]) +% zoom reset; + % zoom getlimits; + zoom out; + + set(fig, 'HandleVisibility', old_handle_vis); + + return; % reset_zoom + + +%---------------------------------------------------------------- +function img_info + + nii_view = getappdata(gcbf, 'nii_view'); + hdr = nii_view.nii.hdr; + + max_value = num2str(double(max(nii_view.nii.img(:)))); + min_value = num2str(double(min(nii_view.nii.img(:)))); + + dim = sprintf('%d %d %d', double(hdr.dime.dim(2:4))); + vox = sprintf('%.3f %.3f %.3f', double(hdr.dime.pixdim(2:4))); + + if double(hdr.dime.datatype) == 1 + type = '1-bit binary'; + elseif double(hdr.dime.datatype) == 2 + type = '8-bit unsigned integer'; + elseif double(hdr.dime.datatype) == 4 + type = '16-bit signed integer'; + elseif double(hdr.dime.datatype) == 8 + type = '32-bit signed integer'; + elseif double(hdr.dime.datatype) == 16 + type = '32-bit single float'; + elseif double(hdr.dime.datatype) == 64 + type = '64-bit double precision'; + elseif double(hdr.dime.datatype) == 128 + type = '24-bit RGB true color'; + elseif double(hdr.dime.datatype) == 256 + type = '8-bit signed integer'; + elseif double(hdr.dime.datatype) == 511 + type = '96-bit RGB true color'; + elseif double(hdr.dime.datatype) == 512 + type = '16-bit unsigned integer'; + elseif double(hdr.dime.datatype) == 768 + type = '32-bit unsigned integer'; + elseif double(hdr.dime.datatype) == 1024 + type = '64-bit signed integer'; + elseif double(hdr.dime.datatype) == 1280 + type = '64-bit unsigned integer'; + end + + msg = {}; + msg = [msg {''}]; + msg = [msg {['Dimension: [', dim, ']']}]; + msg = [msg {''}]; + msg = [msg {['Voxel Size: [', vox, ']']}]; + msg = [msg {''}]; + msg = [msg {['Data Type: [', type, ']']}]; + msg = [msg {''}]; + msg = [msg {['Max Value: [', max_value, ']']}]; + msg = [msg {''}]; + msg = [msg {['Min Value: [', min_value, ']']}]; + msg = [msg {''}]; + + if isfield(nii_view.nii, 'fileprefix') + if isfield(nii_view.nii, 'filetype') & nii_view.nii.filetype == 2 + msg = [msg {['File Name: [', nii_view.nii.fileprefix, '.nii]']}]; + msg = [msg {''}]; + elseif isfield(nii_view.nii, 'filetype') + msg = [msg {['File Name: [', nii_view.nii.fileprefix, '.img]']}]; + msg = [msg {''}]; + else + msg = [msg {['File Prefix: [', nii_view.nii.fileprefix, ']']}]; + msg = [msg {''}]; + end + end + + h = msgbox(msg, 'Image Information', 'modal'); + set(h,'color',[1 1 1]); + + return; % img_info + + +%---------------------------------------------------------------- +function orient + + fig = gcbf; + nii_view = getappdata(fig, 'nii_view'); + nii = nii_view.nii; + + if ~isempty(nii_view.bgimg) + msg = 'You can not modify an overlay image'; + h = msgbox(msg, 'Error', 'modal'); + return; + end + + old_pointer = get(fig,'Pointer'); + set(fig,'Pointer','watch'); + + [nii orient] = rri_orient(nii); + + if isequal(orient, [1 2 3]) % do nothing + set(fig,'Pointer',old_pointer); + return; + end + + oldopt = view_nii(fig); + opt.command = 'updatenii'; + opt.usecolorbar = oldopt.usecolorbar; + opt.usepanel = oldopt.usepanel; + opt.usecrosshair = oldopt.usecrosshair; + opt.usestretch = oldopt.usestretch; + opt.useimagesc = oldopt.useimagesc; + opt.useinterp = oldopt.useinterp; + opt.setarea = oldopt.area; + opt.setunit = oldopt.unit; + opt.setviewpoint = oldopt.viewpoint; + opt.setscanid = oldopt.scanid; + opt.setcbarminmax = oldopt.cbarminmax; + opt.setcolorindex = oldopt.colorindex; + opt.setcolormap = oldopt.colormap; + opt.setcolorlevel = oldopt.colorlevel; + + if isfield(oldopt,'highcolor') + opt.sethighcolor = oldopt.highcolor; + end + + view_nii(fig, nii, opt); + set(fig,'Pointer',old_pointer); + reset_zoom(fig); + + return; % orient + + +%---------------------------------------------------------------- +function editvox + + fig = gcbf; + nii_view = getappdata(fig, 'nii_view'); + + if ~isempty(nii_view.bgimg) + msg = 'You can not modify an overlay image'; + h = msgbox(msg, 'Error', 'modal'); + return; + end + + nii = nii_view.nii; + oldopt = view_nii(fig); + sag = nii_view.imgXYZ.vox(1); + cor = nii_view.imgXYZ.vox(2); + axi = nii_view.imgXYZ.vox(3); + + if nii_view.nii.hdr.dime.datatype == 128 + imgvalue = [double(nii.img(sag,cor,axi,1,nii_view.scanid)) double(nii.img(sag,cor,axi,2,nii_view.scanid)) double(nii.img(sag,cor,axi,3,nii_view.scanid))]; + init_val = sprintf('%7.4g %7.4g %7.4g',imgvalue); + elseif nii_view.nii.hdr.dime.datatype == 511 + R = double(nii.img(sag,cor,axi,1,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + G = double(nii.img(sag,cor,axi,2,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + B = double(nii.img(sag,cor,axi,3,nii_view.scanid)) * (nii_view.nii.hdr.dime.glmax - ... + nii_view.nii.hdr.dime.glmin) + nii_view.nii.hdr.dime.glmin; + imgvalue = [R G B]; + init_val = sprintf('%7.4g %7.4g %7.4g',imgvalue); + else + imgvalue = double(nii.img(sag,cor,axi,nii_view.scanid)); + init_val = sprintf('%.6g',imgvalue); + end + + old_pointer = get(fig,'Pointer'); + set(fig,'Pointer','watch'); + + repeat = 1; + while repeat + if nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511 + init_val = inputdlg({'Replace the current voxel values with 3 new numbers:'}, ... + 'Edit voxel value at crosshair', 1, {num2str(init_val)}); + else + init_val = inputdlg({'Replace the current voxel value with 1 new number:'}, ... + 'Edit voxel value at crosshair', 1, {num2str(init_val)}); + end + + if isempty(init_val) + set(fig,'Pointer',old_pointer); + return + end + + imgvalue = str2num(init_val{1}); + + if ( (nii_view.nii.hdr.dime.datatype == 128 | nii_view.nii.hdr.dime.datatype == 511) ... + & length(imgvalue) ~= 3 ) | ... + ( (nii_view.nii.hdr.dime.datatype ~= 128 & nii_view.nii.hdr.dime.datatype ~= 511) ... + & length(imgvalue) ~= 1 ) + % do nothing + else + repeat = 0; + end + end + + if nii_view.nii.hdr.dime.datatype == 128 + nii.img(sag,cor,axi,1,nii_view.scanid) = imgvalue(1); + nii.img(sag,cor,axi,2,nii_view.scanid) = imgvalue(2); + nii.img(sag,cor,axi,3,nii_view.scanid) = imgvalue(3); + elseif nii_view.nii.hdr.dime.datatype == 511 + nii.img(sag,cor,axi,1,nii_view.scanid) = (imgvalue(1) - nii_view.nii.hdr.dime.glmin) ... + / (nii_view.nii.hdr.dime.glmax - nii_view.nii.hdr.dime.glmin); + nii.img(sag,cor,axi,2,nii_view.scanid) = (imgvalue(2) - nii_view.nii.hdr.dime.glmin) ... + / (nii_view.nii.hdr.dime.glmax - nii_view.nii.hdr.dime.glmin); + nii.img(sag,cor,axi,3,nii_view.scanid) = (imgvalue(3) - nii_view.nii.hdr.dime.glmin) ... + / (nii_view.nii.hdr.dime.glmax - nii_view.nii.hdr.dime.glmin); + else + nii.img(sag,cor,axi,nii_view.scanid) = imgvalue; + end + + opt.command = 'updatenii'; + opt.usecolorbar = oldopt.usecolorbar; + opt.usepanel = oldopt.usepanel; + opt.usecrosshair = oldopt.usecrosshair; + opt.usestretch = oldopt.usestretch; + opt.useimagesc = oldopt.useimagesc; + opt.useinterp = oldopt.useinterp; + opt.setarea = oldopt.area; + opt.setunit = oldopt.unit; + opt.setviewpoint = oldopt.viewpoint; + opt.setscanid = oldopt.scanid; + opt.setcbarminmax = oldopt.cbarminmax; + opt.setcolorindex = oldopt.colorindex; + opt.setcolormap = oldopt.colormap; + opt.setcolorlevel = oldopt.colorlevel; + + if isfield(oldopt,'highcolor') + opt.sethighcolor = oldopt.highcolor; + end + + view_nii(fig, nii, opt); + set(fig,'Pointer',old_pointer); + reset_zoom(fig); + + return; % editvox + + +%---------------------------------------------------------------- +function save_disp + + [filename pathname] = uiputfile('*.*', 'Save displayed image as (*.nii or *.img)'); + + if isequal(filename,0) | isequal(pathname,0) + return; + else + out_imgfile = fullfile(pathname, filename); % original image file + end + + old_pointer = get(gcbf,'Pointer'); + set(gcbf,'Pointer','watch'); + + nii_view = getappdata(gcbf, 'nii_view'); + nii = nii_view.nii; + + try + save_nii(nii, out_imgfile); + catch + msg = 'File can not be saved.'; + msgbox(msg, 'File write error', 'modal'); + end + + set(gcbf,'Pointer',old_pointer); + + return; % save_disp + + +%---------------------------------------------------------------- +function img_hist + + nii_view = getappdata(gcbf, 'nii_view'); + N = hist(double(nii_view.nii.img(:)),256); + x = linspace(double(min(nii_view.nii.img(:))), double(max(nii_view.nii.img(:))), 256); + figure;bar(x,N); + set(gcf, 'number', 'off', 'name', 'Volume Histogram'); + set(gcf, 'windowstyle', 'modal'); % no zoom ... + + xspan = max(x) - min(x) + 1; + yspan = max(N) + 1; + set(gca, 'xlim', [min(x)-xspan/20, max(x)+xspan/20]); + set(gca, 'ylim', [-yspan/20, max(N)+yspan/20]); + + return; % img_hist + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m new file mode 100755 index 00000000..21d82cd8 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m @@ -0,0 +1,521 @@ +% internal function + +% 'xform_nii.m' is an internal function called by "load_nii.m", so +% you do not need run this program by yourself. It does simplified +% NIfTI sform/qform affine transform, and supports some of the +% affine transforms, including translation, reflection, and +% orthogonal rotation (N*90 degree). +% +% For other affine transforms, e.g. any degree rotation, shearing +% etc. you will have to use the included 'reslice_nii.m' program +% to reslice the image volume. 'reslice_nii.m' is not called by +% any other program, and you have to run 'reslice_nii.m' explicitly +% for those NIfTI files that you want to reslice them. +% +% Since 'xform_nii.m' does not involve any interpolation or any +% slice change, the original image volume is supposed to be +% untouched, although it is translated, reflected, or even +% orthogonally rotated, based on the affine matrix in the +% NIfTI header. +% +% However, the affine matrix in the header of a lot NIfTI files +% contain slightly non-orthogonal rotation. Therefore, optional +% input parameter 'tolerance' is used to allow some distortion +% in the loaded image for any non-orthogonal rotation or shearing +% of NIfTI affine matrix. If you set 'tolerance' to 0, it means +% that you do not allow any distortion. If you set 'tolerance' to +% 1, it means that you do not care any distortion. The image will +% fail to be loaded if it can not be tolerated. The tolerance will +% be set to 0.1 (10%), if it is default or empty. +% +% Because 'reslice_nii.m' has to perform 3D interpolation, it can +% be slow depending on image size and affine matrix in the header. +% +% After you perform the affine transform, the 'nii' structure +% generated from 'xform_nii.m' or new NIfTI file created from +% 'reslice_nii.m' will be in RAS orientation, i.e. X axis from +% Left to Right, Y axis from Posterior to Anterior, and Z axis +% from Inferior to Superior. +% +% NOTE: This function should be called immediately after load_nii. +% +% Usage: [ nii ] = xform_nii(nii, [tolerance], [preferredForm]) +% +% nii - NIFTI structure (returned from load_nii) +% +% tolerance (optional) - distortion allowed for non-orthogonal rotation +% or shearing in NIfTI affine matrix. It will be set to 0.1 (10%), +% if it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = xform_nii(nii, tolerance, preferredForm) + + % save a copy of the header as it was loaded. This is the + % header before any sform, qform manipulation is done. + % + nii.original.hdr = nii.hdr; + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; + elseif(tolerance<=0) + tolerance = eps; + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + % if scl_slope field is nonzero, then each voxel value in the + % dataset should be scaled as: y = scl_slope * x + scl_inter + % I bring it here because hdr will be modified by change_hdr. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [2,4,8,16,64,256,512,768]) & ... + (nii.hdr.dime.scl_slope ~= 1 | nii.hdr.dime.scl_inter ~= 0) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 64 + + nii.hdr.dime.datatype = 64; + nii.hdr.dime.bitpix = 64; + else + nii.img = single(nii.img); + + nii.hdr.dime.datatype = 16; + nii.hdr.dime.bitpix = 32; + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % However, the scaling is to be ignored if datatype is DT_RGB24. + + % If datatype is a complex type, then the scaling is to be applied + % to both the real and imaginary parts. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [32,1792]) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 32 + nii.img = single(nii.img); + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % There is no need for this program to transform Analyze data + % + if nii.filetype == 0 & exist([nii.fileprefix '.mat'],'file') + load([nii.fileprefix '.mat']); % old SPM affine matrix + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + nii.hdr.hist.qform_code=0; + nii.hdr.hist.sform_code=1; + nii.hdr.hist.srow_x=M(1,:); + nii.hdr.hist.srow_y=M(2,:); + nii.hdr.hist.srow_z=M(3,:); + elseif nii.filetype == 0 + nii.hdr.hist.rot_orient = []; + nii.hdr.hist.flip_orient = []; + return; % no sform/qform for Analyze format + end + + hdr = nii.hdr; + + [hdr,orient]=change_hdr(hdr,tolerance,preferredForm); + + % flip and/or rotate image data + % + if ~isequal(orient, [1 2 3]) + + old_dim = hdr.dime.dim([2:4]); + + % More than 1 time frame + % + if ndims(nii.img) > 3 + pattern = 1:prod(old_dim); + else + pattern = []; + end + + if ~isempty(pattern) + pattern = reshape(pattern, old_dim); + end + + % calculate for rotation after flip + % + rot_orient = mod(orient + 2, 3) + 1; + + % do flip: + % + flip_orient = orient - rot_orient; + + for i = 1:3 + if flip_orient(i) + if ~isempty(pattern) + pattern = flipdim(pattern, i); + else + nii.img = flipdim(nii.img, i); + end + end + end + + % get index of orient (rotate inversely) + % + [tmp rot_orient] = sort(rot_orient); + + new_dim = old_dim; + new_dim = new_dim(rot_orient); + hdr.dime.dim([2:4]) = new_dim; + + new_pixdim = hdr.dime.pixdim([2:4]); + new_pixdim = new_pixdim(rot_orient); + hdr.dime.pixdim([2:4]) = new_pixdim; + + % re-calculate originator + % + tmp = hdr.hist.originator([1:3]); + tmp = tmp(rot_orient); + flip_orient = flip_orient(rot_orient); + + for i = 1:3 + if flip_orient(i) & ~isequal(tmp(i), 0) + tmp(i) = new_dim(i) - tmp(i) + 1; + end + end + + hdr.hist.originator([1:3]) = tmp; + hdr.hist.rot_orient = rot_orient; + hdr.hist.flip_orient = flip_orient; + + % do rotation: + % + if ~isempty(pattern) + pattern = permute(pattern, rot_orient); + pattern = pattern(:); + + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + tmp = reshape(nii.img(:,:,:,1), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,1) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + tmp = reshape(nii.img(:,:,:,2), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,2) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + tmp = reshape(nii.img(:,:,:,3), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,3) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + end + + else + nii.img = reshape(nii.img, [prod(new_dim) hdr.dime.dim(5:8)]); + nii.img = nii.img(pattern, :); + nii.img = reshape(nii.img, [new_dim hdr.dime.dim(5:8)]); + end + else + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + nii.img(:,:,:,1) = permute(nii.img(:,:,:,1), rot_orient); + nii.img(:,:,:,2) = permute(nii.img(:,:,:,2), rot_orient); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + nii.img(:,:,:,3) = permute(nii.img(:,:,:,3), rot_orient); + end + else + nii.img = permute(nii.img, rot_orient); + end + end + else + hdr.hist.rot_orient = []; + hdr.hist.flip_orient = []; + end + + nii.hdr = hdr; + + return; % xform_nii + + +%----------------------------------------------------------------------- +function [hdr, orient] = change_hdr(hdr, tolerance, preferredForm) + + orient = [1 2 3]; + affine_transform = 1; + + % NIFTI can have both sform and qform transform. This program + % will check sform_code prior to qform_code by default. + % + % If user specifys "preferredForm", user can then choose the + % priority. - Jeff + % + useForm=[]; % Jeff + + if isequal(preferredForm,'S') + if isequal(hdr.hist.sform_code,0) + error('User requires sform, sform not set in header'); + else + useForm='s'; + end + end % Jeff + + if isequal(preferredForm,'Q') + if isequal(hdr.hist.qform_code,0) + error('User requires qform, qform not set in header'); + else + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'s') + if hdr.hist.sform_code > 0 + useForm='s'; + elseif hdr.hist.qform_code > 0 + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'q') + if hdr.hist.qform_code > 0 + useForm='q'; + elseif hdr.hist.sform_code > 0 + useForm='s'; + end + end % Jeff + + if isequal(useForm,'s') + R = [hdr.hist.srow_x(1:3) + hdr.hist.srow_y(1:3) + hdr.hist.srow_z(1:3)]; + + T = [hdr.hist.srow_x(4) + hdr.hist.srow_y(4) + hdr.hist.srow_z(4)]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + hdr.hist.old_affine = [ [R;[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + end + + elseif isequal(useForm,'q') + b = hdr.hist.quatern_b; + c = hdr.hist.quatern_c; + d = hdr.hist.quatern_d; + + if 1.0-(b*b+c*c+d*d) < 0 + if abs(1.0-(b*b+c*c+d*d)) < 1e-5 + a = 0; + else + error('Incorrect quaternion values in this NIFTI data.'); + end + else + a = sqrt(1.0-(b*b+c*c+d*d)); + end + + qfac = hdr.dime.pixdim(1); + if qfac==0, qfac = 1; end + i = hdr.dime.pixdim(2); + j = hdr.dime.pixdim(3); + k = qfac * hdr.dime.pixdim(4); + + R = [a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c + 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b + 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b]; + + T = [hdr.hist.qoffset_x + hdr.hist.qoffset_y + hdr.hist.qoffset_z]; + + % qforms are expected to generate rotation matrices R which are + % det(R) = 1; we'll make sure that happens. + % + % now we make the same checks as were done above for sform data + % BUT we do it on a transform that is in terms of voxels not mm; + % after we figure out the angles and squash them to closest + % rectilinear direction. After that, the voxel sizes are then + % added. + % + % This part is modified by Jeff Gunter. + % + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + + % det(R) == 0 is not a common trigger for this --- + % R(find(R)) is a list of non-zero elements in R; if that + % is straight (not oblique) then it should be the same as + % columnwise summation. Could just as well have checked the + % lengths of R(find(R)) and sum(R)' (which should be 3) + % + hdr.hist.old_affine = [ [R * diag([i j k]);[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + R = R * diag([i j k]); + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + + else + R = R * diag([i j k]); + end % 1st det(R) + + else + affine_transform = 0; % no sform or qform transform + end + + if affine_transform == 1 + voxel_size = abs(sum(R,1)); + inv_R = inv(R); + originator = inv_R*(-T)+1; + orient = get_orient(inv_R); + + % modify pixdim and originator + % + hdr.dime.pixdim(2:4) = voxel_size; + hdr.hist.originator(1:3) = originator; + + % set sform or qform to non-use, because they have been + % applied in xform_nii + % + hdr.hist.qform_code = 0; + hdr.hist.sform_code = 0; + end + + % apply space_unit to pixdim if not 1 (mm) + % + space_unit = get_units(hdr); + + if space_unit ~= 1 + hdr.dime.pixdim(2:4) = hdr.dime.pixdim(2:4) * space_unit; + + % set space_unit of xyzt_units to millimeter, because + % voxel_size has been re-scaled + % + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,1,0)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,2,1)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,3,0)); + end + + hdr.dime.pixdim = abs(hdr.dime.pixdim); + + return; % change_hdr + + +%----------------------------------------------------------------------- +function orient = get_orient(R) + + orient = []; + + for i = 1:3 + switch find(R(i,:)) * sign(sum(R(i,:))) + case 1 + orient = [orient 1]; % Left to Right + case 2 + orient = [orient 2]; % Posterior to Anterior + case 3 + orient = [orient 3]; % Inferior to Superior + case -1 + orient = [orient 4]; % Right to Left + case -2 + orient = [orient 5]; % Anterior to Posterior + case -3 + orient = [orient 6]; % Superior to Inferior + end + end + + return; % get_orient + + +%----------------------------------------------------------------------- +function [space_unit, time_unit] = get_units(hdr) + + switch bitand(hdr.dime.xyzt_units, 7) % mask with 0x07 + case 1 + space_unit = 1e+3; % meter, m + case 3 + space_unit = 1e-3; % micrometer, um + otherwise + space_unit = 1; % millimeter, mm + end + + switch bitand(hdr.dime.xyzt_units, 56) % mask with 0x38 + case 16 + time_unit = 1e-3; % millisecond, ms + case 24 + time_unit = 1e-6; % microsecond, us + otherwise + time_unit = 1; % second, s + end + + return; % get_units + diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m new file mode 100755 index 00000000..d1c7c877 --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m @@ -0,0 +1,45 @@ +function nii = nii_load(niifile,untouchopt,img_idx) + +%author EML + +vartype = 16; + +if nargin < 2 || isempty(untouchopt) + untouchopt = 1; +end + +if nargin < 3 || isempty(img_idx) + img_idx = 0; +end + +if ~untouchopt + try + if ~img_idx + niiraw = load_nii(niifile); + else + niiraw = load_nii(niifile,img_idx); + end + catch + disp(['Shear in image header detected, loading untouch nii ' niifile]); + if ~img_idx + niiraw = load_untouch_nii(niifile); + else + niiraw = load_untouch_nii(niifile,img_idx); + end + end +else + if ~img_idx + niiraw = load_untouch_nii(niifile); + idxstr = ''; + else + niiraw = load_untouch_nii(niifile,img_idx); + idxstr = [' vol ' num2str(img_idx)]; + end + disp(['option selected loading untouched nii ' niifile idxstr]); +end + +nii = niiraw; +nii.hdr.dime.datatype = vartype; +nii.hdr.dime.bitpix = vartype; + +nii.img = double(niiraw.img); \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m new file mode 100755 index 00000000..6bfdc9de --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m @@ -0,0 +1,14 @@ +function nii_save(nii,niifile,vartype) +%author EML + +if nargin > 2 + nii.hdr.dime.datatype = vartype; + nii.hdr.dime.bitpix = vartype; +end + +if isfield(nii, 'untouch') + disp('Untouch option found'); + save_untouch_nii(nii,niifile); +else + save_nii(nii,niifile); +end \ No newline at end of file From 4d11874f3b91cbdf291498dd259d990d89c32a2e Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:44:27 -0400 Subject: [PATCH 02/10] Delete MRI-QAMPER_IVIM/buildBatchStruct.m --- .../MRI-QAMPER_IVIM/buildBatchStruct.m | 336 ------------------ 1 file changed, 336 deletions(-) delete mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m deleted file mode 100755 index 2a4846d0..00000000 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchStruct.m +++ /dev/null @@ -1,336 +0,0 @@ - -function batchStruct = buildBatchStruct(handles) - -batchStruct.batch_id = handles.batch_id; - - -batchStruct.currentNiiPath = handles.outputDirectoryText.String; - -qamperMode = handles.qamperMode; - -if strcmp(qamperMode,'DWI') - DWIstruct.runDWI = 1; -% -% DWIstruct.ADC = handles.ADCCheckBox.Value; -% DWIstruct.IVIM = handles.IVIMCheckBox.Value; -% DWIstruct.NGIVIM = handles.NGIVIMCheckBox.Value; -% DWIstruct.Kurtosis = handles.KurtosisCheckBox.Value; -% DWIstruct.Parallel = handles.ParallelButton_DWI.Value; - - fn = handles.fn; -% -% DWIstruct.files{1} = handles.inputDataText.String; -% DWIstruct.files{2} = handles.inputROIText.String; -% - for i = 1:numel(fn) - inputfile{i} = fn(i).filename; vol_num{i} = fn(i).filevolnum; - DWIstruct.bval_arr(i) = fn(i).bval_arr; - end -% dwinputfilename = unique(inputfile); -% if numel(dwinputfilename) == 1 -% % = string(dwinputfilename); -% dwinputfilename = char(dwinputfilename); -% end - DWIstruct.files{1} = inputfile; DWIstruct.vols = vol_num; - DWIstruct.files{2} = handles.inputROIText.String; - -% [currentNiiPath,~,~] = fileparts(DWIstruct.files{1}); -% batchStruct.currentNiiPath = currentNiiPath; - - DWIstruct.ADC = handles.ADCCheckBox.Value; - DWIstruct.IVIM = handles.IVIMCheckBox.Value; - DWIstruct.NGIVIM = handles.NGIVIMCheckBox.Value; - DWIstruct.Kurtosis = handles.DKICheckBox.Value; - - DWIstruct.roiType = handles.ROITypeDropDown.String{handles.ROITypeDropDown.Value}; - - DWIstruct.avgRepeatBvals = handles.avgRepeatBvalsCheckBox.Value; - - % DWIstruct.channels = handles.channels; - - if isfield(handles, 'channels') - DWIstruct.channels = handles.channels; - else - DWIstruct.channels = 24; % - end - - % DWIstruct.avgChannels = handles.avgChannels; - - if isfield(handles, 'avgChannels') - DWIstruct.avgChannels = handles.avgChannels; - else - DWIstruct.avgChannels = 24; % - end - -% -% presetValue = handles.presetPopupMenu.Value; -% if presetValue == 1 -% presetValue = 2; -% end -% -% DWIstruct.preset = handles.presetPopupMenu.String{presetValue}; - - if DWIstruct.IVIM - DWIstruct.LB_IVIM = cell2mat(handles.IVIMUITable.Data(1,:)); - DWIstruct.UB_IVIM = cell2mat(handles.IVIMUITable.Data(2,:)); - DWIstruct.x0_IVIM = cell2mat(handles.IVIMUITable.Data(3,:)); - end - if DWIstruct.NGIVIM - DWIstruct.LB_NG = cell2mat(handles.NGIVIMUITable.Data(1,:)); - DWIstruct.UB_NG = cell2mat(handles.NGIVIMUITable.Data(2,:)); - DWIstruct.x0_NG = cell2mat(handles.NGIVIMUITable.Data(3,:)); - end - - if ~handles.parallelCheckBox.Value - DWIstruct.Parallel = 0; - - else - DWIstruct.Parallel = 1; - end - - previewMode = handles.fitPreviewButtonGroup.SelectedObject.Tag; - - switch previewMode - case 'previewNoneRadioButton' - DWIstruct.previewMode = 0; - case 'preview100RadioButton' - DWIstruct.previewMode = 100; - case 'previewFullRadioButton' - DWIstruct.previewMode = 1; - end - -% DWIstruct.bval_arr = handles.bval_arr; - DWIstruct.bval_tf = ones(size(DWIstruct.bval_arr)); - - DWIstruct.previewAxes = handles.fittingAxes; - - DWIstruct.nanopt = 1; - - if str2num(handles.smoothKernelEdit.String) > 0 - DWIstruct.smoothopt = 1; - DWIstruct.kernelsize = str2num(handles.smoothKernelEdit.String); - else - DWIstruct.smoothopt = 0; - end - -% if isempty(DWIstruct.bval_arr) -% dwiniifile = DWIstruct.files{1}; -% [d,f,~] = fileparts(dwiniifile); -% load([d filesep f(1:end-4) '.mat']); -% DWIstruct.bval_arr = bval_arr; -% if exist(bval_tf,'var') -% DWIstruct.bval_tf = bval_tf; -% else -% DWIstruct.bval_tf = ones(size(bval_arr)); -% end -% end - -else - DWIstruct.runDWI = 0; -end - -if strcmp(qamperMode,'T1') - T1struct.runT1 = 1; - T1struct.AIFonly = 0; %handles.AIFcalcOnlyCheckBox.Value; - - fn = handles.fn; - T1struct.FA = zeros(numel(fn),1); - T1struct.TR = zeros(numel(fn),1); - - for i = 1:numel(fn) - T1struct.files{i} = fn(i).filename; - T1struct.FA(i) = fn(i).FA; - T1struct.TR(i) = fn(i).TR; - end - - T1struct.ROI = handles.inputROIText.String; - - if strcmp(handles.fitMethodButtonGroup.SelectedObject.String,'Non-Linear Fitting') && numel(fn) > 2 - T1struct.Nonlinear = 1; - T1struct.LB = cell2mat(handles.T1NonLinearTable.Data(1,:)); - T1struct.UB = cell2mat(handles.T1NonLinearTable.Data(2,:)); - else - T1struct.Nonlinear = 0; - end - - switch handles.fitPreviewButtonGroup.SelectedObject.String - case 'No Preview' - T1struct.previewMode = 0; - case 'First 200 Points Preview' - T1struct.previewMode = 100; - case 'Full Preview' - T1struct.previewMode = 1; - end - - T1struct.Parallel = handles.parallelCheckBox.Value; - T1struct.previewAxes = handles.fittingAxes; - -else - T1struct.runT1 = 0; -end - -if strcmp(qamperMode,'T2') - T2struct.runT2 = 1; - - fn = handles.fn; - T2struct.TE = zeros(numel(fn),1); - T2struct.TR = zeros(numel(fn),1); - - for i = 1:numel(fn) - T2struct.files{i} = fn(i).filename; - T2struct.TE(i) = fn(i).TE; - T2struct.TR(i) = fn(i).TR; - end - - T2struct.ROI = handles.inputROIText.String; - -% if strcmp(handles.fitMethodButtonGroup.SelectedObject.String,'Non-Linear Fitting') && numel(fn) > 2 -% T2struct.Nonlinear = 1; -% else -% T2struct.Nonlinear = 0; -% end - - switch handles.fitPreviewButtonGroup.SelectedObject.String - case 'No Preview' - T2struct.previewMode = 0; - case 'First 200 Points Preview' - T2struct.previewMode = 100; - case 'Full Preview' - T2struct.previewMode = 1; - end - - T2struct.Parallel = handles.parallelCheckBox.Value; - T2struct.previewAxes = handles.fittingAxes; - -else - T2struct.runT2 = 0; -end - -if strcmp(qamperMode,'DCE') - DCEstruct.runDCE = 1; - - fn = handles.fn; - - DCEstruct.AIF = handles.aifPopupMenu.String{handles.aifPopupMenu.Value}; - DCEstruct.Cpmat = handles.Cpmat; - DCEstruct.Cp = handles.Cp; - - DCEstruct.SM2 = handles.SM2CheckBox.Value; - DCEstruct.SM3 = handles.SM3CheckBox.Value; - DCEstruct.SSM = handles.SSMCheckBox.Value; - DCEstruct.Patlak = handles.PatlakCheckBox.Value; - DCEstruct.CTUM = handles.CTUMCheckBox.Value; - DCEstruct.CXM = handles.CXMCheckBox.Value; - - DCEstruct.roiType = handles.ROITypeDropDown.String{handles.ROITypeDropDown.Value}; - -% dcemodelidx = handles.modelPopupMenu.Value; -% dcemodel = handles.modelPopupMenu.String{dcemodelidx}; -% if dcemodelidx == 5 || dcemodelidx == 6 -% dcemodel = dcemodel(2:end); -% end - if DCEstruct.SM2 == 1 - eval('DCEstruct.SM2_LB = cell2mat(handles.SM2UITable.Data(1,:));'); - eval('DCEstruct.SM2_UB = cell2mat(handles.SM2UITable.Data(2,:));'); - end - if DCEstruct.SM3 == 1 - eval('DCEstruct.SM3_LB = cell2mat(handles.SM3UITable.Data(1,:));'); - eval('DCEstruct.SM3_UB = cell2mat(handles.SM3UITable.Data(2,:));'); - end - if DCEstruct.SSM == 1 - eval('DCEstruct.SSM_LB = cell2mat(handles.SSMUITable.Data(1,:));'); - eval('DCEstruct.SSM_UB = cell2mat(handles.SSMUITable.Data(2,:));'); - end - if DCEstruct.CTUM == 1 - eval('DCEstruct.CTUM_LB = cell2mat(handles.CTUMUITable.Data(1,:));'); - eval('DCEstruct.CTUM_UB = cell2mat(handles.CTUMUITable.Data(2,:));'); - end - if DCEstruct.CXM == 1 - eval('DCEstruct.CXM_LB = cell2mat(handles.CXMUITable.Data(1,:));'); - eval('DCEstruct.CXM_UB = cell2mat(handles.CXMUITable.Data(2,:));'); - end -% if ~strcmp(dcemodel(1:3),'AIF') -% eval(['DCEstruct.' dcemodel ' = 1']); -% if ~strcmp(dcemodel(1:3),'Pat') -% eval(['DCEstruct.LB = cell2mat(handles. ' dcemodel 'UITable.Data(1,:));']); -% eval(['DCEstruct.UB = cell2mat(handles. ' dcemodel 'UITable.Data(2,:));']); -% end -% end - if handles.numItersPopupMenu.Value == 1 - DCEstruct.numIters = 4; - else - DCEstruct.numIters = str2num(handles.numItersPopupMenu.String{handles.numItersPopupMenu.Value}); - end - -% DCEstruct.T10 = handles.T10TextEdit.String; - DCEstruct.T1 = ''; - DCEstruct.r1 = str2num(handles.RelaxivityTextEdit.String); - if handles.constantT10CheckBox.Value - DCEstruct.T1 = 'constant'; - DCEstruct.T10 = str2num(handles.T10TextEdit.String); - else - DCEstruct.T10file = handles.T10DataText.String; - DCEstruct.m0file = handles.m0DataText.String; - end - -% DCEstruct.Cpmat = handles.Cpmat; - -% if isempty(DCEstruct.acqDuration) - DCEstruct.acqDuration = fn(end).t; -% end -% -% if str2num(handles.smoothKernelEdit.String) > 0 -% DCEstruct.smoothopt = 1; -% DCEstruct.kernelsize = str2num(handles.smoothKernelEdit.String); -% else - DCEstruct.smoothopt = 0; -% end - - DCEstruct.Parallel = handles.parallelCheckBox.Value; - - % DCEstruct.files{1} = handles.inputDataText.String; - DCEstruct.t = zeros(1,numel(fn)); - DCEstruct.dcenii = fn(1).nii; - for i = 1:numel(fn) - inputfile{i} = fn(i).filename; - DCEstruct.t(i) = fn(i).t; - end - dceinputfilename = unique(inputfile); - if numel(dceinputfilename) == 1 - dceinputfilename = char(dceinputfilename); - end - DCEstruct.files{1} = dceinputfilename; - DCEstruct.files{2} = handles.inputROIText.String; - - DCEstruct.FA = fn(end).FA; - DCEstruct.TR = fn(end).TR; - -% DCEstruct.t = handles.t; - - previewMode = handles.fitPreviewButtonGroup.SelectedObject.Tag; - - switch previewMode - case 'previewNoneRadioButton' - DCEstruct.previewMode = 0; - case 'preview100RadioButton' - DCEstruct.previewMode = 100; - otherwise - DCEstruct.previewMode = 1; - end - - DCEstruct.previewAxes = handles.fittingAxes; - DCEstruct.fn = fn; -else - DCEstruct.runDCE = 0; -end - -if strcmp(qamperMode,'T2') - T2struct.runT2 = 1; -else - T2struct.runT2 = 0; -end - -batchStruct.DWIstruct = DWIstruct; -batchStruct.T1struct = T1struct; -batchStruct.DCEstruct = DCEstruct; -batchStruct.T2struct = T2struct; From dede24b60a0e88f817d1f1f4bd9c62a485a3b6b3 Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:44:43 -0400 Subject: [PATCH 03/10] Delete src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m --- .../MRI-QAMPER_IVIM/buildBatchFromCSV.m | 107 ------------------ 1 file changed, 107 deletions(-) delete mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m deleted file mode 100755 index bf4541d2..00000000 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/buildBatchFromCSV.m +++ /dev/null @@ -1,107 +0,0 @@ -function batchStruct = buildBatchFromCSV(img_nii,roi_nii,qamper_mode,optionS) - -batchStruct = []; -batchStruct.batch_id = num2str(now); - -if strcmp(qamper_mode,'DWI') - if isfield(optionS,'DWIstruct') - DWIstruct = optionS.DWIstruct; - end - - DWIstruct.runDWI = 1; - [droot,fbase,ext] = fileparts(img_nii); - batchStruct.currentNiiPath = fullfile(droot,'dwi_maps'); - - - if strcmp(ext,'.gz') - [~,fbase,~] = fileparts(fbase); - end - - bval_mat = fullfile(droot,[fbase '.mat']); - - T = load(bval_mat); - - DWIstruct.bval_arr = T.bval_arr; - - img = nii_load(img_nii,1); - vol_num = size(img.img,4); - - DWIstruct.files{1} = img_nii; DWIstruct.vols = vol_num; - DWIstruct.files{2} = roi_nii; - - if ~isfield(DWIstruct,'ADC') - DWIstruct.ADC = 0; - end - if ~isfield(DWIstruct,'IVIM') - DWIstruct.IVIM = 0; - end - if ~isfield(DWIstruct,'NGIVIM') - DWIstruct.NGIVIM = 0; - end - if ~isfield(DWIstruct,'Kurtosis') - DWIstruct.Kurtosis = 0; - end - - if ~isfield(DWIstruct,'roiType') - DWIstruct.roiType = 'node'; - end - - if ~isfield(DWIstruct,'avgRepeatBvals') - DWIstruct.avgRepeatBvals = 1; - end - - if ~isfield(DWIstruct,'channels') - DWIstruct.channels = 16; % - end - - if ~isfield(DWIstruct, 'avgChannels') - DWIstruct.avgChannels = 2; % - end - - if DWIstruct.IVIM - if ~isfield(DWIstruct,'LB_IVIM') - IVIMBoundsTable = [{0} {0} {0} {0}; {0.5} {0.005} {0.5} {1.0}; {0.05} {0.0005} {0.01} {0.5}]; - - DWIstruct.LB_IVIM = cell2mat(IVIMBoundsTable(1,:)); - DWIstruct.UB_IVIM = cell2mat(IVIMBoundsTable(2,:)); - DWIstruct.x0_IVIM = cell2mat(IVIMBoundsTable(3,:)); - end - end - if DWIstruct.NGIVIM - if ~isfield(DWIstruct,'LB_NG') - NGIVIMDefaultBoundsTable = [{0} {0} {0} {0} {0}; {0.5} {0.005} {0.1} {1.0} {2.0}; {0.05} {0.0005} {0.01} {1.0} {0.5}]; - - DWIstruct.LB_NG = cell2mat(NGIVIMDefaultBoundsTable(1,:)); - DWIstruct.UB_NG = cell2mat(NGIVIMDefaultBoundsTable(2,:)); - DWIstruct.x0_NG = cell2mat(NGIVIMDefaultBoundsTable(3,:)); - end - end - - if ~isfield(DWIstruct,'Parallel') - DWIstruct.Parallel = 1; - end - - DWIstruct.bval_tf = ones(size(DWIstruct.bval_arr)); -% -% DWIstruct.previewAxes = handles.fittingAxes; -% - DWIstruct.nanopt = 1; - - if ~isfield(DWIstruct,'kernelsize') - DWIstruct.kernelsize = 1; - end - if ~isfield(DWIstruct,'smoothopt') - DWIstruct.smoothopt = 1; - end - - DWIstruct.previewMode = 0; - DWIstruct.previewAxes = 0; - -else - DWIstruct.runDWI = 0; -end - -batchStruct.DWIstruct = DWIstruct; -batchStruct.T1struct.runT1 = 0; -batchStruct.DCEstruct.runDCE = 0; -batchStruct.T2struct.runT2 = 0; \ No newline at end of file From b4b5bac879a02cdab7bca6e8fca83f8309118bcd Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:39:01 -0400 Subject: [PATCH 04/10] Create .gitignore for MRI_QAMPER --- .../ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore new file mode 100644 index 00000000..3e3461ae --- /dev/null +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore @@ -0,0 +1 @@ +*.asv From a16b99a066a50b92d58d8c0410d34f170535ee1e Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:39:38 -0400 Subject: [PATCH 05/10] Update getTestData.m to retrieve and use OSIPI Zenodo data --- .../MRI-QAMPER_IVIM/getTestData.m | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m index 3da2cf42..3bd0300d 100755 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m @@ -1,8 +1,16 @@ -function [img_nii,bval_file,roi_nii,save_folder] = getTestData +function [img_nii,bval_file,roi_nii] = getTestData [d,~,~] = fileparts(which('getTestData')); +test_data_folder = fullfile(d,'test_data'); +if ~exist(test_data_folder,'dir') + mkdir(test_data_folder); +end -img_nii = fullfile(d,'test_data','702-HN401D-D2019_10_08.nii.gz'); -bval_file = fullfile(d,'test_data','702-HN401D-D2019_10_08.bval'); -roi_nii = fullfile(d,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); -save_folder = fullfile(d,'test_data'); \ No newline at end of file +zenodo_url = 'https://zenodo.org/records/14605039/files/OSIPI_TF24_data_phantoms.zip'; +save_zip = fullfile(test_data_folder,'OSIPI_TF24_data_phantoms.zip'); +websave(save_zip,zenodo_url); +unzip(save_zip, test_data_folder); + +img_nii = fulllfile(test_data_folder,'Data','brain.nii.gz'); +bval_file = fulllfile(test_data_folder,'Data','brain.bval'); +roi_nii = fulllfile(test_data_folder,'Data','brain_mask_gray_matter.nii.gz'); From 7766addce9c2dd260084b94f2a218128c521efc6 Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:40:06 -0400 Subject: [PATCH 06/10] Delete src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv --- .../MRI-QAMPER_IVIM/README.asv | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv deleted file mode 100755 index 7fe62189..00000000 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.asv +++ /dev/null @@ -1,33 +0,0 @@ -# MRI-QAMPER_IVIM - -This is the codebase for intravoxel incoherent motion (IVIM) code from the MRI-QAMPER MATLAB package, developed by Dr. Shukla-Dave's lab at Memorial Sloan Kettering Cancer Center - -Authors: Eve LoCastro (locastre@mskcc.org), Dr. Ramesh Paudyal (paudyalr@mskcc.org), Dr. Amita Shukla-Dave (davea@mskcc.org) -Institution: Memorial Sloan Kettering Cancer Center -Department: Medical Physics -Address: 321 E 61st St, New York, NY 10022 - -The codebase and subdirectories should be added to the MATLAB path. An example usage script is provided in `demo_QAMPER_IVIM.m`. - -``` -% This is an example usage script for MSK Medical Physics Dave Lab QAMPER IVIM -% Please replace the variable names below with path values for -% qamper_path: path to MRI-QAMPER_IVIM folder -% img_nii: multi-b value DWI (4-D NIfTI) -% bval_file: b-value information (txt file) -% roi_nii: single-volume mask ROI image (NIfTI) - - - -qamper_path = 'path:\to\MRI-QAMPER_IVIM'; -addpath(genpath(qamper_path)); - -img_nii = 'dwi.nii'; -bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); -roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); - -save_folder = fullfile(qamper_path,'test_data'); - -batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); - -``` \ No newline at end of file From 187ee3514f9ba94e6fad258d4204b50d43528d2d Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:40:25 -0400 Subject: [PATCH 07/10] Delete src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv --- .../MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100755 src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv deleted file mode 100755 index 39c751c6..00000000 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.asv +++ /dev/null @@ -1,15 +0,0 @@ -% Example usage script for MSK Medical Physics Dave Lab QAMPER IVIM -% Please replace the variable names below with path values for -% img_nii: multi-b value DWI (4-D NIfTI) -% bval_file: b-value information (txt file) -% roi_nii: single-volume mask ROI -qamper_path = 'path:\to\MRI-QAMPER_IVIM'; -addpath(genpath(qamper_path)); - -img_nii = 'dwi.nii'; -bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); -roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); - -save_folder = fullfile(qamper_path,'test_data'); - -batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); \ No newline at end of file From 4c9aaff8f456ecbb4e286d723c0f53d814ef3497 Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Sat, 26 Apr 2025 15:32:54 -0400 Subject: [PATCH 08/10] Update demo_QAMPER_IVIM.m --- .../MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m index bb7f7c60..3ba2e439 100755 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m @@ -10,15 +10,21 @@ % roi_nii: single-volume mask ROI image (NIfTI) - - -qamper_path = 'path:\to\MRI-QAMPER_IVIM'; +qamper_path = 'path:\to\MRI-QAMPER_IVIM'; %path where the MRI-QAMPER folder is located addpath(genpath(qamper_path)); -img_nii = 'dwi.nii'; -bval_file = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08.bval'); -roi_nii = fullfile(qamper_path,'test_data','702-HN401D-D2019_10_08_BD_REDO_SV.nii.gz'); +% Uncomment below and substitute the names of your files +% img_nii = '702-D2019_10_08.nii'; +% bval_file = fullfile(qamper_path,'test_data','702-D2019_10_08.bval'); +% roi_nii = fullfile(qamper_path,'test_data','702-D2019_10_08_ROI.nii.gz'); + +% Optional function below: download the OSIPI test data from Zenodo +[img_nii, bval_file, roi_nii] = getTestData; -save_folder = fullfile(qamper_path,'test_data'); +save_folder = fullfile(qamper_path,'output_files'); %change this location to where you want the output files to be saved +if ~exist(save_folder,'dir') + mkdir(save_folder); +end -batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); \ No newline at end of file +% Run the analysis +batchResultsFolder = run_QAMPER_IVIM(img_nii,bval_file,roi_nii,save_folder); From f1f81bf2979192fb7995c3c2dbdb954b66fa3f70 Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Sat, 26 Apr 2025 15:34:51 -0400 Subject: [PATCH 09/10] Update runQAMPERBatch.m if no ROI is supplied, create ROI of entire image volume --- .../MRI-QAMPER_IVIM/runQAMPERBatch.m | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m index 3e4f5631..035243c2 100755 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m @@ -90,21 +90,21 @@ function runQAMPERBatch(batchStruct) %,csv_outdir) dwi_series_num = '' ; %getSeriesNum(dwifile); - roifile = DWIstruct.files{2}; copyfile(roifile,dwipath); - roinii = nii_load(roifile); - ROI = roinii.img; + roifile = DWIstruct.files{2}; + copyfile(roifile,dwipath); + if ~isempty(roifile) + roinii = nii_load(roifile); + ROI = roinii.img; + else + ROI = ones(size(dwi_img(:,:,:,1))); + end - roi_series_num = ''; %getSeriesNum(roifile); + roi_series_num = ''; roiType = DWIstruct.roiType; %getROIType(roifile); - [~,fname,~] = fileparts(dwifile); [~,fname,~] = fileparts(fname); + [~,fname,~] = fileparts(dwifile); + [~,fname,~] = fileparts(fname); save_prefix = [dwipath filesep fname]; -% save_prefix = [dwipath filesep dwi_series_num '-' roi_series_num '-' roiType]; -% -% if bval_arr(end) == 0 -% [bval_arr,dwi_img] = bvalZeroOrderFix(bval_arr,dwi_img); -% end - [bval_arr,dwi_img] = bvalOrderFix(bval_arr,dwi_img); if isfield(DWIstruct,'avgRepeatBvals') From bd3dbf63ce161bf2ce00df2645a32bf9eb99ed5d Mon Sep 17 00:00:00 2001 From: locastre <34068528+locastre@users.noreply.github.com> Date: Sat, 26 Apr 2025 16:04:19 -0400 Subject: [PATCH 10/10] Update runQAMPERBatch.m --- .../MRI-QAMPER_IVIM/runQAMPERBatch.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m index 035243c2..dc67aed6 100755 --- a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m +++ b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m @@ -91,8 +91,9 @@ function runQAMPERBatch(batchStruct) %,csv_outdir) dwi_series_num = '' ; %getSeriesNum(dwifile); roifile = DWIstruct.files{2}; - copyfile(roifile,dwipath); + if ~isempty(roifile) + copyfile(roifile,dwipath); roinii = nii_load(roifile); ROI = roinii.img; else

W~dPyX?Mfws=wNBGWv zX}drnCJl@PT2PrZ7#wJkWzrD4IJ@697#srJ2qq0F3*e2IG}JDV>vs(f2Ji6&IKUoX z;ILh$7Smb;6lj5E(&P|(G=wZ{&p8kPyp4G+0tUbXnf5~Z5CjCMUBBf6U|hlXXizYGk6q|4o04fQ6t&kb6oq1* z2SdU4$1p&Ye|C?P;4}h^~@4?9dbbS9^(&7K- zb^Py2ZR_FrcTq#g!oe_3j`Qa)8ESEI{Ik%>a&r8C_y#iau=fU_Xn;$g-S4@po^HSh P2pO}}U?dX!52ybFsv*mj literal 0 HcmV?d00001 diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf b/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf new file mode 100755 index 0000000000000000000000000000000000000000..4bcdf2182147abe1fbc322c76b91206559cde731 GIT binary patch literal 91343 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