├── +remmi ├── +dwi │ ├── adc.m │ ├── addbmatrix.m │ ├── dti.m │ ├── dtilin.m │ ├── dtinonlin.m │ └── dtiwlin.m ├── +ir │ ├── T1.m │ ├── ir.m │ ├── qmt.m │ └── sir.m ├── +mge │ └── b0.m ├── +mse │ ├── +MERA │ │ ├── MERA.m │ │ ├── maketestdata1D.m │ │ ├── nnlsMEX.c │ │ ├── test1D.m │ │ └── test2D.m │ ├── T2.m │ ├── analysis.m │ ├── calcGeometricMean.m │ ├── calcT2frac.m │ ├── mT2.m │ └── mT2options.m ├── +recon │ ├── ft.m │ ├── options.m │ └── pocs.m ├── +roi │ ├── copy.m │ └── draw.m ├── +util │ ├── apodize.m │ ├── githash.m │ ├── linfitfast.m │ ├── mp_denoise.m │ ├── selectexp.m │ ├── slice.m │ ├── snrCalc.m │ ├── strsplit.m │ └── thresholdmask.m ├── +vendors │ ├── Agilent.m │ ├── BrukerPV.m │ ├── autoVendor.m │ ├── bmatAgilent.m │ ├── bmatBruker.m │ ├── loadAgilent.m │ ├── loadBruker.m │ ├── loadBrukerGrase.m │ ├── parsAgilent.m │ └── parsBruker.m ├── Contents.m ├── dataset.m ├── loadImg.m ├── niftifile.m ├── niftispace.m ├── recon.m ├── version.m └── workspace.m ├── .gitignore ├── README.html ├── README.md ├── WHATSNEW ├── easy_dti.m ├── easy_mtir.m ├── easy_t2.m └── full_t2.m /+remmi/+dwi/adc.m: -------------------------------------------------------------------------------- 1 | function adcSet = adc(dset,varargin) 2 | % adcSet = remmi.dwi.adc(dset,name) calculates ADC maps from data 3 | % in the dset structure: 4 | % 5 | % dset.(name) = data to process for dti 6 | % dset.mask = mask for processing data 7 | % dset.bmat = condensed bmatrix 8 | % dset.labels = cell array of labels to dset.(name) dimensions 9 | % 10 | % if dset or dset.(name) is not given, default reconstruction and 11 | % thresholding methods are called 12 | % 13 | % name = name of field in dset to fit. Default is 'img' 14 | % 15 | % Returns a data set containing a mean B0 image and ADC parameter maps 16 | % for each non B0 image. 17 | % 18 | % Kevin Harkins & Mark Does, Vanderbilt University 19 | % for the REMMI Toolbox 20 | 21 | [name] = setoptions(varargin{:}); 22 | 23 | if ~exist('dset','var') 24 | dset = struct(); 25 | end 26 | 27 | if ~isfield(dset,name) || isempty(dset.(name)) || ... 28 | ~isfield(dset,'mask') || isempty(dset.mask) 29 | disp('Using default mask') % alert user that default mask in use 30 | dset = remmi.util.thresholdmask(dset); 31 | end 32 | 33 | if ~isfield(dset,'bmat') || isempty(dset.bmat) 34 | dset = remmi.dwi.addbmatrix(dset); 35 | end 36 | 37 | % size of the dataset 38 | sz = size(dset.(name)); 39 | 40 | % what dimension is DW encoding? 41 | dwLabels = {'DW','NR'}; 42 | dwDim = ismember(dset.labels,dwLabels); 43 | 44 | if ~any(dwDim) 45 | error('Data set does not contain multiple diffusion encodings'); 46 | end 47 | 48 | if isfield(dset,'mask') 49 | mask = dset.mask; 50 | 51 | % apply the mask across all non-DW dimensions 52 | mask = bsxfun(@times,mask,ones(sz(~dwDim))); 53 | else 54 | mask = true(sz(~dwDim)); 55 | end 56 | 57 | % average the B0 images 58 | isB0 = false(sz(ismember(dset.labels,'DW')),1); 59 | nB0 = dset.pars.methpars.PVM_DwAoImages; 60 | if isfield(dset.pars.methpars,'REMMI_DwAoImagesEnd') 61 | nB0end = dset.pars.methpars.REMMI_DwAoImagesEnd; 62 | isB0(1:nB0-nB0end) = true; 63 | isB0(end-nB0end+1:end) = true; 64 | else 65 | isB0(1:nB0) = true; 66 | end 67 | 68 | if ~any(isB0) 69 | % DwAo images = 0, look for b-value=0 70 | isB0 = dset.pars.methpars.PVM_DwBvalEach==0; 71 | 72 | if ~any(isB0) 73 | error('no b=0 images are given'); 74 | end 75 | end 76 | 77 | % initalize data set to appropriate sizes 78 | adcSet.adc = zeros([sz(~dwDim) sum(~isB0)]); 79 | 80 | % calculate the average "b0" image 81 | dwidx = find(ismember(dset.labels,'DW')); 82 | b0idx = cell(size(sz)); 83 | b0idx(:) = {':'}; 84 | b0idx{dwidx} = isB0; 85 | adcSet.b0 = mean(dset.(name)(b0idx{:}),dwidx).*mask; 86 | b0_bval = sum(dset.bmat(1:3,isB0),1); 87 | 88 | notB0 = find(~isB0); 89 | for n=1:length(notB0) 90 | idx = cell(size(sz)); 91 | idx(:) = {':'}; 92 | idx{dwidx} = notB0(n); 93 | idx_out = idx; 94 | idx_out{dwidx} = n; 95 | d_bval = sum(dset.bmat(1:3,notB0(n))) - b0_bval; 96 | adcSet.adc(idx_out{:}) = -log(abs(dset.(name)(idx{:})./adcSet.b0))/d_bval.*mask*1000; 97 | end 98 | 99 | end 100 | 101 | function [name] = setoptions(name) 102 | 103 | if ~exist('name','var') || isempty(name) 104 | name = 'img'; 105 | end 106 | 107 | end -------------------------------------------------------------------------------- /+remmi/+dwi/addbmatrix.m: -------------------------------------------------------------------------------- 1 | function dset = addbmatrix(dset) 2 | % dset = addbmatrix(dset) 3 | % adds bmatrix to a datset 4 | % 5 | % output: dset contains all the original contents, plus dset.bmat 6 | % 7 | % Kevin Harkins & Mark Does, Vanderbilt University 8 | % for the REMMI Toolbox 9 | 10 | dset.bmat = remmi.vendors.bmatBruker(dset.pars); 11 | 12 | end -------------------------------------------------------------------------------- /+remmi/+dwi/dti.m: -------------------------------------------------------------------------------- 1 | function dtiSet = dti(dset,varargin) 2 | % dtiSet = remmi.dwi.dti(dset,mode,name) performs diffusion tensor analysis 3 | % on the structure: 4 | % 5 | % dset.(name) = data to process for dti 6 | % dset.mask = mask for processing data 7 | % dset.bmat = condensed bmatrix in ms/um^2 8 | % dset.labels = cell array of labels to dset.(name) dimensions 9 | % 10 | % if dset or dset.(name) is not given, default reconstruction and 11 | % thresholding methods are called 12 | % 13 | % mode = fitting routine: {'weighedlinear'}, 'linear', 'nonlinear' 14 | % name = name of field in dset to fit. Default is 'img' 15 | % 16 | % Returns a data set containing dti parameter maps of ADC & FA. 17 | % 18 | % Kevin Harkins & Mark Does, Vanderbilt University 19 | % for the REMMI Toolbox 20 | 21 | [diffFun,name] = setoptions(varargin{:}); 22 | 23 | if ~exist('dset','var') 24 | dset = struct(); 25 | end 26 | 27 | % MDD changed to assume mask is at the same level as name, not a field in 28 | % name 29 | if ~isfield(dset,name) || isempty(dset.(name)) || ... 30 | ~isfield(dset,'mask') || isempty(dset.mask) 31 | % ~isfield(dset.(name),'mask') || isempty(dset.(name).mask) 32 | disp('Using default mask') % alert user that default mask in use 33 | dset = remmi.util.thresholdmask(dset); 34 | end 35 | 36 | if ~isfield(dset,'bmat') || isempty(dset.bmat) 37 | dset = remmi.dwi.addbmatrix(dset); 38 | end 39 | 40 | % size of the dataset 41 | sz = size(dset.(name)); 42 | 43 | % what dimension is DW encoding? 44 | dwLabels = {'DW','NR'}; 45 | dwDim = ismember(dset.labels,dwLabels); 46 | 47 | if ~any(dwDim) 48 | error('Data set does not contain multiple diffusion encodings'); 49 | end 50 | 51 | if isfield(dset,'mask') 52 | mask = dset.mask; 53 | 54 | % apply the mask across all non-DW dimensions 55 | mask = bsxfun(@times,mask,ones(sz(~dwDim))); 56 | else 57 | mask = true(sz(~dwDim)); 58 | end 59 | 60 | % initalize data set to appropriate sizes 61 | dtiSet.fa = zeros(sz(~dwDim)); 62 | dtiSet.adc = zeros(sz(~dwDim)); 63 | dtiSet.vec = zeros([3 3 sz(~dwDim)]); 64 | dtiSet.eig = zeros([3 sz(~dwDim)]); 65 | 66 | tot_evals = sum(mask(:)); 67 | evals = 0; 68 | 69 | % make the DW dimension the first index. 70 | idx = 1:numel(size(dset.(name))); 71 | data = permute(dset.(name),[idx(dwDim) idx(~dwDim)]); 72 | 73 | if sum(dwDim) == 2 74 | data = reshape(data,[prod(sz(dwDim)) sz(~dwDim)]); 75 | end 76 | 77 | fprintf('%3.0f %% done...',0); 78 | for n = 1:numel(mask) 79 | if mask(n) 80 | sig = abs(squeeze(data(:,n))); 81 | 82 | [adc,fa,vec,eig] = diffFun(sig,dset.bmat); 83 | 84 | dtiSet.fa(n) = fa; 85 | dtiSet.adc(n) = adc; 86 | dtiSet.vec(:,:,n) = vec; 87 | dtiSet.eig(:,n) = eig; 88 | evals = evals+1; 89 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...',evals/tot_evals*100); 90 | end 91 | end 92 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...\n',100); 93 | 94 | dtiSet.vec = permute(dtiSet.vec,[idx+2 1 2]); 95 | dtiSet.eig = permute(dtiSet.eig,[idx+1 1]); 96 | 97 | end 98 | 99 | function [diffFun,name] = setoptions(mode,name) 100 | 101 | if ~exist('mode','var') || isempty(mode) 102 | mode = 'weightedlinear'; 103 | end 104 | 105 | % what method are we fitting for dti? 106 | diffFun = @(x,bmat) remmi.dwi.dtiwlin(x,bmat); 107 | if exist('mode','var') 108 | % nonlinear model fitting 109 | if strcmpi(mode,'linear') 110 | disp('using linear least squares') 111 | diffFun = @(x,bmat) remmi.dwi.dtilin(x,bmat); 112 | elseif strcmpi(mode,'nonlinear') 113 | disp('using nonlinear least squares') 114 | diffFun = @(x,bmat) remmi.dwi.dtinonlin(x,bmat); 115 | else 116 | disp('using weighted linear least squares') 117 | end 118 | end 119 | 120 | if ~exist('name','var') || isempty(name) 121 | name = 'img'; 122 | end 123 | 124 | end -------------------------------------------------------------------------------- /+remmi/+dwi/dtilin.m: -------------------------------------------------------------------------------- 1 | function [adc,fa,vec,eign] = dtilin(sig,bmat) 2 | % function [adc,fa,vec,eign] = dtilin(sig,bmat,std_noise) performs 3 | % linearized diffusion tensor analysis 4 | % sig = diffusion-weighted signal 5 | % bmat = matrix given as [Dxx,Dyy,Dzz,Dxy,Dyz,Dxz,log(S0);...]' 6 | % 7 | % returns: 8 | % adc = apparent diffusion coefficient 9 | % fa = fractional anisotropy 10 | % vec = 3x3 matrix of eigenvectors 11 | % eign = 3 eigenvalues 12 | % 13 | % This function uses the weighted-least squares algorithm outlined in: 14 | % Kingsley P B 2006 Introduction to Diffusion Tensor Imaging Mathematics: 15 | % Part III. Tensor Calculation, Noise, Simulations, and Optimization 16 | % Concepts Magn. Reson. Part A 28A 155-79 17 | % 18 | % Note: dtilin tends to be more sensitive to noise than dtinonlin, but 19 | % much faster. 20 | 21 | % linearize the problem 22 | sig1 = -log(sig/sig(1)); 23 | 24 | % fit and make the D matrix 25 | D = (bmat*bmat')\(bmat)*sig1; 26 | D = diag(D(1:3)) + (diag(D(4:5),-1) + diag(D(4:5),1) + diag(D(6),-2) + diag(D(6),2))/2; 27 | 28 | % eigenvalue/vector decomposition 29 | [vec,val] = eig(D); 30 | eign = abs(diag(val)*1000); 31 | 32 | % calculate the apparent diffusion coefficient and fractional anisotropy 33 | adc = mean(eign); 34 | fa = sqrt(3/2*sum((eign-adc).^2)/sum(eign.^2)); -------------------------------------------------------------------------------- /+remmi/+dwi/dtinonlin.m: -------------------------------------------------------------------------------- 1 | function [adc,fa,vec,eign] = dtinonlin(sig,bmat) 2 | % function [adc,fa,vec,eign] = dtinonlin(sig,bmat) performes non-linear 3 | % diffusion tensor analysis 4 | % sig = diffusion-weighted signal 5 | % bmat = matrix given as [Dxx,Dyy,Dzz,Dxy,Dxz,Dyz;...]' 6 | % 7 | % returns: 8 | % adc = apparent diffusion coefficient 9 | % fa = fractional anisotropy 10 | % vec = 3x3 matrix of eigenvectors 11 | % eign = 3 eigenvalues 12 | % 13 | % Note: dtilin tends to be less stable than dtinonlin, but much faster. 14 | 15 | % initial guess 16 | x0 = [max(sig(:));ones(6,1)]; 17 | lb = [0;zeros(6,1)]; 18 | ub = [5*max(sig(:));5*ones(6,1)]; 19 | bmat = bmat(1:6,:,:); 20 | 21 | % fit and make D matrix 22 | D = lsqnonlin(@(x) sig-diff_sig(x,bmat),x0,lb,ub,optimset('display','off')); 23 | D = diag(D(2:4)) + diag(D(5:6),-1) + diag(D(5:6),1) + diag(D(7),-2) + diag(D(7),2); 24 | 25 | % eignevalue/vector decomposition 26 | [vec,val] = eig(D); 27 | eign = diag(val)*1000; 28 | 29 | % calculate the apparent diffusion coefficient and fractional anisotropy 30 | adc = mean(eign); 31 | fa = sqrt(3/2*sum((eign-adc).^2)/sum(eign.^2)); 32 | 33 | function sig = diff_sig(x,b) 34 | D = x(2:end); 35 | bD = bsxfun(@times,D,b); 36 | 37 | sig = x(1) * (exp(-permute(sum(bD(1:3,:,:))+2*sum(bD(4:6,:,:)),[2 3 1]))); 38 | -------------------------------------------------------------------------------- /+remmi/+dwi/dtiwlin.m: -------------------------------------------------------------------------------- 1 | function [adc,fa,vec,eign] = dtiwlin(sig,bmat,std_noise) 2 | % function [adc,fa,vec,eign] = dtiwlin(sig,bmat,std_noise) performs 3 | % linearized diffusion tensor analysis via weighted least squares 4 | % sig = diffusion-weighted signal 5 | % bmat = matrix given as [Dxx,Dyy,Dzz,Dxy,Dyz,Dxz,log(S0);...]' 6 | % std_noise = estimate of stdev of the noise. If not given, it is assumed 7 | % to be constant 8 | % 9 | % returns: 10 | % adc = apparent diffusion coefficient 11 | % fa = fractional anisotropy 12 | % vec = 3x3 matrix of eigenvectors 13 | % eign = 3 eigenvalues 14 | % 15 | % This function uses the weighted-least squares algorithm outlined in: 16 | % Kingsley P B 2006 Introduction to Diffusion Tensor Imaging Mathematics: 17 | % Part III. Tensor Calculation, Noise, Simulations, and Optimization 18 | % Concepts Magn. Reson. Part A 28A 155-79 19 | % 20 | % Note: dtiwlin tends to be more sensitive to noise than dtinonlin, but 21 | % much faster. 22 | 23 | if ~exist('std_noise','var') 24 | % assume uniform noise. When noise is uniform, the scaling should not 25 | % matter 26 | std_noise = 0.01*max(sig(:))*ones(size(sig)); 27 | end 28 | 29 | % linearize the problem 30 | sig1 = -log(sig/sig(1)); 31 | weight = diag(abs(sig./std_noise).^2); 32 | 33 | % fit and make the D matrix 34 | D = (bmat*weight*bmat')\(bmat*weight)*sig1; 35 | D = diag(D(1:3)) + (diag(D(4:5),-1) + diag(D(4:5),1) + diag(D(6),-2) + diag(D(6),2))/2; 36 | 37 | % eigenvalue/vector decomposition 38 | [vec,val] = eig(D); 39 | eign = abs(diag(val)*1000); 40 | 41 | % calculate the apparent diffusion coefficient and fractional anisotropy 42 | adc = mean(eign); 43 | fa = sqrt(3/2*sum((eign-adc).^2)/sum(eign.^2)); -------------------------------------------------------------------------------- /+remmi/+ir/T1.m: -------------------------------------------------------------------------------- 1 | function irSet = T1(dset,varargin) 2 | % irSet = remmi.ir.T1(dset,name) performs inversion recovery analysis on 3 | % the dataset: 4 | % 5 | % dset.(name) = data to process 6 | % dset.mask = mask for processing data 7 | % dset.pars = basic remmi parameter set including ti & td. 8 | % dset.labels = cell array of labels to dset.img dimensions 9 | % 10 | % if dset or dset.(name) is not given, default reconstruction and 11 | % thresholding methods are called 12 | % 13 | % name = name of field in dset to fit. Default is 'img' 14 | % 15 | % Returns a data set containing ir parameter maps of M0, T1 (same units 16 | % as dset.pars.ti) and flip angle (degrees) 17 | % 18 | % Kevin Harkins & Mark Does, Vanderbilt University 19 | % for the REMMI Toolbox 20 | 21 | name = setoptions(varargin{:}); 22 | 23 | if ~exist('dset','var') 24 | dset = struct(); 25 | end 26 | 27 | if ~isfield(dset,name) || isempty(dset.(name)) 28 | dset = remmi.util.thresholdmask(remmi.recon(dset)); 29 | end 30 | 31 | % load in the dataset 32 | sz = size(dset.(name)); 33 | 34 | % what dimension is MT encoding? 35 | irDim = ismember(dset.labels,'IR'); 36 | 37 | if ~any(irDim) 38 | error('Data set does not contain multiple inversion times'); 39 | end 40 | 41 | % load in the needed parameters 42 | ti = dset.pars.ti; 43 | td = dset.pars.td; 44 | 45 | if numel(ti)<3 46 | error('There are not enough inversion times in this datatset for IR analysis') 47 | end 48 | 49 | if numel(ti) ~= sz(irDim) 50 | error('The number of inversion times does not match the dataset dimenions') 51 | end 52 | 53 | % define a mask if one is not given 54 | if isfield(dset,'mask') 55 | mask = squeeze(dset.mask); 56 | else 57 | mask = squeeze(true(sz(~irDim))); 58 | end 59 | 60 | % initialize the mtir dataset 61 | irSet.M0 = zeros(size(mask)); 62 | irSet.T1 = zeros(size(mask)); 63 | irSet.flip_angle = zeros(size(mask)); 64 | irSet.nrmse = zeros(size(mask)); 65 | irSet.ci = cell(size(mask)); 66 | 67 | tot_evals = sum(mask(:)); 68 | evals = 0; 69 | 70 | % make the MT dimension the first index. 71 | idx = 1:numel(size(dset.(name))); 72 | data = permute(dset.(name),[idx(irDim) idx(~irDim)]); 73 | 74 | warning('off','MATLAB:singularMatrix') 75 | 76 | t1fun = @(x) abs(remmi.ir.ir(x,ti(:),ti(:)+td)); 77 | 78 | fprintf('%3.0f %% done...',0); 79 | for n=1:numel(mask) 80 | if mask(n) 81 | 82 | sig = squeeze(abs(data(:,n))); 83 | 84 | % initial guess & bounds 85 | b0 = [max(sig), 0.400, 150]; 86 | lb = [ 0, 0, 0]; 87 | ub = [ inf, inf, 180]; 88 | 89 | % fit the data 90 | opts = optimset('display','off'); 91 | [b,~,res,~,~,~,jac] = lsqnonlin(@(x) t1fun(x)-sig(:),b0,lb,ub,opts); 92 | 93 | % load the dataset 94 | irSet.M0(n)=b(1); 95 | irSet.T1(n)=b(2); 96 | irSet.flip_angle(n)=b(3); 97 | irSet.nrmse(n) = norm(res)/norm(sig); 98 | 99 | % save confidence intervals on the original parameters 100 | irSet.ci{n} = nlparci(b,res,'jacobian',jac); 101 | 102 | evals = evals+1; 103 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...',evals/tot_evals*100); 104 | end 105 | end 106 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...\n',100); 107 | 108 | warning('on','MATLAB:singularMatrix') 109 | 110 | end 111 | 112 | function [name] = setoptions(name) 113 | 114 | if ~exist('name','var') || isempty(name) 115 | name = 'img'; 116 | end 117 | 118 | end -------------------------------------------------------------------------------- /+remmi/+ir/ir.m: -------------------------------------------------------------------------------- 1 | function Mz = ir(x,ti,tr) 2 | % function Mz = ir(x,ti,tr) signal equation for longitudinal magnetization 3 | % after an inversion pulse 4 | % x(1) = magnitude of the free water pool 5 | % x(2) = T1 of free water (same units as ti,tr) 6 | % x(3) = estimated flip angle of the inversion pulse. Range is 0-180 7 | % degrees 8 | % 9 | % Kevin Harkins & Mark Does, Vanderbilt University 10 | % for the REMMI Toolbox 11 | 12 | Mz = x(1) * (1 - (1-cosd(x(3))) .* exp(-ti/x(2)) + exp(-tr/x(2))); 13 | -------------------------------------------------------------------------------- /+remmi/+ir/qmt.m: -------------------------------------------------------------------------------- 1 | function qMTout = qmt(dset,varargin) 2 | % mtirSet = remmi.ir.qmt(dset,name,init,output) performs MTIR analysis on 3 | % the data in dset: 4 | % 5 | % dset.(name) = data to process 6 | % dset.mask = mask for processing data 7 | % dset.pars = basic remmi parameter set including ti & td. 8 | % dset.labels = cell array of labels to dset.img dimensions 9 | % 10 | % **NB dset is often dset.images and the data to be processed is 11 | % often dset.images.img 12 | % 13 | % if dset or dset.(name) is not given, default reconstruction and 14 | % thresholding methods are called 15 | % 16 | % name = name of field in dset to fit. Default is 'img' 17 | % 18 | % init = inital values and lower & upper bounds used for fitting 19 | % [M0a,M0b,kmf,1/T1,inv_eff] 20 | % 21 | % output = a structure with field names matching those to be 22 | % returned. The default maps are M0a, M0b, BPF, T1, and inv_eff. 23 | % To select different outputs, pass in 'output' with any of 24 | % the following field names: 25 | % M0a: amplitude of the free water equilibrium magnetization 26 | % M0b: amplitude of the bound proton equilibrium magnetization 27 | % PSR: M0b/M0a 28 | % BPF: M0b/(M0a+M0b) 29 | % kmf: Exchange rate constant, bound-to-free pool 30 | % T1: Free water longitudinal relaxation time constant 31 | % inv_eff: cos of effective inversion pulse flip angle 32 | % 33 | % For example, "output = struct('M0a',[],'M0b',[],'BPF',[])", 34 | % then call "remmi.ir.qmt(dset.images,'img',init,output)" 35 | % 36 | % Kevin Harkins & Mark Does, Vanderbilt University 37 | % for the REMMI Toolbox 38 | 39 | % load in the dataset 40 | 41 | [name,init,output] = setoptions(varargin{:}); 42 | 43 | if ~exist('dset','var') 44 | dset = struct(); 45 | end 46 | 47 | if ~isfield(dset,name) || isempty(dset.(name)) 48 | dset = remmi.util.thresholdmask(remmi.recon(dset)); 49 | end 50 | 51 | sz = size(dset.(name)); 52 | 53 | % what dimension is MT encoding? 54 | mtDim = ismember(dset.labels,'IR'); 55 | 56 | if ~any(mtDim) 57 | error('Data set does not contain multiple inversion times'); 58 | end 59 | 60 | % load in the needed parameters 61 | ti = dset.pars.ti; % sec 62 | td = dset.pars.td; % sec 63 | 64 | if numel(ti)<6 65 | error('There are not enough inversion times in this datatset for MTIR analysis') 66 | end 67 | 68 | if numel(ti) ~= sz(mtDim) 69 | error('The number of inversion times does not match the dataset dimenions') 70 | end 71 | 72 | % define a mask if one is not given 73 | if isfield(dset,'mask') 74 | mask = squeeze(dset.mask); 75 | 76 | % apply the mask across all non-MT dimensions 77 | mask = bsxfun(@times,mask,ones(sz(~mtDim))); 78 | else 79 | mask = squeeze(true(sz(~mtDim))); 80 | end 81 | 82 | 83 | % initialize the output dimensions 84 | M0a = zeros(size(mask)); 85 | M0b = zeros(size(mask)); 86 | PSR = zeros(size(mask)); 87 | BPF = zeros(size(mask)); 88 | kmf = zeros(size(mask)); 89 | T1 = zeros(size(mask)); 90 | inv_eff = zeros(size(mask)); 91 | 92 | tot_evals = sum(mask(:)); 93 | evals = 0; 94 | 95 | % make the MT dimension the first index. 96 | idx = 1:ndims(dset.(name)); 97 | data = permute(dset.(name),[idx(mtDim) idx(~mtDim)]); 98 | 99 | warning('off','MATLAB:singularMatrix') 100 | 101 | fop = optimset('lsqnonlin'); 102 | fop = optimset(fop,'display','off','TolX',1e-6,'TolFun',1e-6); 103 | % MDD lowered Tol from 1e-3 and 1e-4 after some fitting issues 104 | 105 | fprintf('%3.0f %% done...',0); 106 | tic 107 | 108 | for n=1:1:numel(mask) % 109 | if mask(n) 110 | 111 | sig = squeeze(abs(data(:,n))); 112 | 113 | % MDD prior version did not allow user to pass in the init vals 114 | if exist('init','var') 115 | b0 = init.b0; 116 | lb = init.lb; 117 | ub = init.ub; 118 | else 119 | % initial guess & bounds 120 | b0 = init.b0(sig); 121 | lb = init.lb(sig); 122 | ub = init.ub(sig); 123 | end 124 | 125 | 126 | % fit the data 127 | [b,~,res,~,~,~,jac] = ... 128 | lsqnonlin(@(x) remmi.ir.sir(x,ti',td)-sig,b0,lb,ub,fop); 129 | 130 | % compute output parameters 131 | M0a(n) = b(1); 132 | M0b(n) = b(2); 133 | PSR(n) = b(2)/b(1); 134 | BPF(n) = b(2)/(b(1)+b(2)); 135 | kmf(n) = b(3); 136 | T1(n) = 1/b(4); 137 | inv_eff(n) = b(5); 138 | 139 | evals = evals+1; 140 | if ~mod(evals,round(tot_evals/20)) 141 | tstep = toc; 142 | fprintf('%3.0f %% done, 5%% step time = %4f s...\n',... 143 | evals/tot_evals*100,tstep); 144 | tic 145 | end 146 | end 147 | end 148 | 149 | warning('on','MATLAB:singularMatrix') 150 | 151 | outfields = fieldnames(output); 152 | Nout = length(outfields); 153 | 154 | if isfield(output,'M0a') 155 | qMTout.M0a = M0a; 156 | end 157 | if isfield(output,'M0b') 158 | qMTout.M0b = M0b; 159 | end 160 | if isfield(output,'PSR') 161 | qMTout.PSR = PSR; 162 | end 163 | if isfield(output,'BPF') 164 | qMTout.BPF = BPF; 165 | end 166 | if isfield(output,'kmf') 167 | qMTout.kmf = kmf; 168 | end 169 | if isfield(output,'T1') 170 | qMTout.T1 = T1; 171 | end 172 | if isfield(output,'inv_eff') 173 | qMTout.inv_eff = inv_eff; 174 | end 175 | 176 | end 177 | 178 | function [name,init,output] = setoptions(name,init,output) 179 | 180 | if ~exist('name','var') || isempty(name) 181 | name = 'img'; 182 | end 183 | 184 | if ~exist('init','var') || isempty(init) 185 | init.b0 = str2func('@(sig) [max(sig), max(sig)/10, 25, 2, max(-1,-sig(1)/sig(end))]'); 186 | init.lb = str2func('@(sig) [ 0, 0, 2, 0, -1]'); 187 | init.ub = str2func('@(sig) [ inf, inf, 200, inf, 1]'); 188 | end 189 | 190 | if ~exist('output','var') 191 | output = struct('M0a',[],'M0b',[],'BPF',[],'T1',[],'inv_eff',[]); 192 | end 193 | 194 | end -------------------------------------------------------------------------------- /+remmi/+ir/sir.m: -------------------------------------------------------------------------------- 1 | function Mz = sir(X,ti,td) 2 | % function Er = sir(X,ti,td) solves 2-compartment bloch equations 3 | % compatible with Selective Inversion Recovery qMT analysis 4 | % X(1) = signal of the free water pool 5 | % X(2) = signal of the bound water pool 6 | % X(3) = exchange rate from macro to water pools (Hz) 7 | % X(4) = R1 of free water (Hz) 8 | % X(5) = inversion efficiency 9 | % ti = list of inversion times 10 | % td = a single delay time. tr = ti+td 11 | % 12 | % R1 of the bound pool is assumed to be 1 Hz 13 | % 14 | % by Kevin Harkins & Mark Does, Vanderbilt University 15 | % for the REMMI Toolbox 16 | 17 | Moa = X(1); % free water pool 18 | Mob = X(2); % macro pool 19 | kba = X(3); % exchange rate from macro to water 20 | R1a = X(4); % R1 of free water (aka the measured R1) 21 | R1b = 1; % R1 of bound water, assumed to be 1 /s. 22 | alpha_a = X(5); % inv efficiency 23 | alpha_b = 0.83; 24 | 25 | Mo = [Moa;Mob]; 26 | kab = kba*Mob/Moa; 27 | L1 = [-(R1a+kab) kba; kab -(R1b+kba)]; 28 | 29 | N = length(ti); 30 | Mz = zeros(N,1); 31 | R = [alpha_a 0; 0 alpha_b]; 32 | 33 | % pre-computations. This is essentially the eigenvalue & eigenvector 34 | % calculation of expmdemo3. 'edit expmdemo3' for details 35 | [V0,D0] = eig(L1); 36 | expm_L1TD = V0 * diag(exp(diag(D0)*td)) / V0; 37 | 38 | M1 = Mo-R*(Mo-expm_L1TD*Mo); 39 | 40 | for kt = 1:N 41 | expm_L1t = V0 * diag(exp(diag(D0)*ti(kt))) / V0; 42 | Z = Mo-expm_L1t*M1; 43 | Mz(kt) = abs(Z(1)); 44 | end 45 | -------------------------------------------------------------------------------- /+remmi/+mge/b0.m: -------------------------------------------------------------------------------- 1 | function b0set = b0(dset,name) 2 | % b0set = remmi.b0(dset,name) calculates B0 from complex multi-gradient 3 | % echo images. 4 | % 5 | % dset.(name) = data to process for dti 6 | % dset.mask = mask for processing data 7 | % dset.bmat = condensed bmatrix in ms/um^2 8 | % dset.labels = cell array of labels to dset.(name) dimensions 9 | % dset.pars.te = list of echo times (in seconds) used to process. 10 | % 11 | % if dset or dset.(name) is not given, default reconstruction and 12 | % thresholding methods are called 13 | % 14 | % name = name of field in dset to fit. Default is 'img' 15 | % 16 | % Returns a data set containing B0 map (in units of Hz). B0 is calculated 17 | % via a weighted average of phase differences calculated from 18 | % multi-gradient echo images. The processing assumes alternating 19 | % positive and negative readout directions. 20 | % 21 | % Kevin Harkins & Mark Does, Vanderbilt University 22 | % for the REMMI Toolbox 23 | 24 | if ~exist('name','var') 25 | name = 'img'; 26 | end 27 | 28 | if ~exist('dset','var') 29 | dset = struct(); 30 | end 31 | 32 | if ~isfield(dset,name) || isempty(dset.(name)) || ... 33 | ~isfield(dset.(name),'mask') || isempty(dset.(name).mask) 34 | dset = remmi.util.thresholdmask(dset); 35 | end 36 | 37 | % size of the dataset 38 | sz = size(dset.(name)); 39 | 40 | % what dimension is DW encoding? 41 | echoLabels = {'NE'}; 42 | echoDim = ismember(dset.labels,echoLabels); 43 | 44 | if ~any(echoDim) 45 | error('Data set does not contain multiple echo images'); 46 | end 47 | 48 | msz = sz; 49 | msz(echoDim) = 1; 50 | if isfield(dset,'mask') 51 | mask = dset.mask; 52 | 53 | % apply the mask across all non-NE dimensions 54 | mask = bsxfun(@times,mask,ones(msz)); 55 | else 56 | mask = true(msz); 57 | end 58 | 59 | freq_sum = 0; 60 | mag_sum = 0; 61 | for n=1:numel(dset.pars.te)-2 62 | idx = cell(length(sz),1); 63 | idx(~echoDim) = {':'}; 64 | idx(echoDim) = {n}; 65 | idx2 = idx; 66 | idx2(echoDim) = {n+2}; 67 | delta_te = dset.pars.te(n+2)-dset.pars.te(n); 68 | 69 | % calculate the phase difference between subsequent echoes with the 70 | % same readout direction. As given in Bernstien Eq 13.112 71 | ph = angle(dset.(name)(idx2{:})./dset.(name)(idx{:})); 72 | freq_off = ph/delta_te/2/pi; 73 | 74 | % We need to average the phase change across the multiple echo times 75 | % acquired. Because image intensity can vary down the echo train (due 76 | % to T2* or B0 inhomogeneity), we use a weighted average. What weight 77 | % should we use? 78 | % 79 | % The phase difference is calculated between two image signals. The 80 | % error in the esimated phase depends upon the signal magnitude in both 81 | % images. High intensities in both signals means high accuracy in the 82 | % estimate of the phase. If one or both of those signals has a low 83 | % intensity, the estimate of the phase will be more susceptible to 84 | % noise. 85 | % 86 | % We weight the average using the harmonic mean of the two images used 87 | % to calculate the phase difference, which will be low when one/both 88 | % signals contain low signal levels. 89 | mag = 1./(1./abs(dset.(name)(idx{:})) + 1./abs(dset.(name)(idx2{:}))); 90 | 91 | % sum the weight, and the frequency times the weight. 92 | freq_sum = freq_sum + mag.*freq_off; 93 | mag_sum = mag_sum + mag; 94 | end 95 | 96 | % calculate the weighted average 97 | b0set.b0 = freq_sum./mag_sum.*mask; 98 | -------------------------------------------------------------------------------- /+remmi/+mse/+MERA/MERA.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remmi-toolbox/remmi-matlab/3ebd5a1e3c87a3348eff936c437c10d39618a77e/+remmi/+mse/+MERA/MERA.m -------------------------------------------------------------------------------- /+remmi/+mse/+MERA/maketestdata1D.m: -------------------------------------------------------------------------------- 1 | function [data] = maketestdata1D(T2,s,t,theta,Ntrials,SNR) 2 | 3 | rng('shuffle') 4 | 5 | N = length(t); 6 | TE = diff(t(1:2)); 7 | 8 | noise_sd = 1/SNR; 9 | noise = randn(N,Ntrials)*noise_sd; 10 | 11 | 12 | K = size(T2,1); 13 | A = zeros(N,K); 14 | 15 | % build A-matrix for a given theta 16 | if theta == 180 17 | A(:,1:K) = exp(-t*(1./T2')); 18 | else 19 | for kx = 1:K 20 | A(:,kx)=EPGdecaycurve(theta,T2(kx),TE,N); 21 | end 22 | end 23 | 24 | yx = A*s; 25 | data.t = t; 26 | data.D = bsxfun(@plus,yx,noise); 27 | 28 | end 29 | 30 | 31 | 32 | function EchoAmp = EPGdecaycurve(theta,T2,TE,N) 33 | % Assumes a CPMG condition 34 | % Arbitrarily set T1 = 1 ... should work well for most situations, but I might 35 | % want to make this a variable later. 36 | T1 = 1; 37 | 38 | % Compute rotation matrix in terms of coherence state 39 | Rot=[cosd(theta/2)^2,sind(theta/2)^2,-1i*sind(theta);... 40 | sind(theta/2)^2,cosd(theta/2)^2,1i*sind(theta);... 41 | -0.5i*sind(theta),0.5i*sind(theta),cosd(theta)]; 42 | 43 | % Initialize vector to track echo amplitude 44 | EchoAmp=zeros(N,1); 45 | 46 | % Initialize magnetization phase state vector (MPSV) and set all 47 | % magnetization in the F1 state. 48 | M=zeros(3*N,1); 49 | M(1,1)=exp(-(TE/2)/T2); 50 | 51 | % Compute relaxation and transition matrix, E 52 | % same as T*E in Prasloski et al. 53 | 54 | RelTrans=spalloc(3*N,3*N,3*N); % 3N x 3N, with 3N entries 55 | E2 = exp(-TE/T2); 56 | E1 = exp(-TE/T1); 57 | RelTrans(1,2)=E2; 58 | for ke=1:N-1 59 | RelTrans(3*ke-1,3*ke+2)=E2; 60 | RelTrans(3*ke+1,3*ke-2)=E2; 61 | RelTrans(3*ke,3*ke)=E1; 62 | end 63 | 64 | BigRot=spalloc(3*N,3*N,9*N); % 3N x 3N, with 9N entries 65 | for kn=1:N 66 | BigRot(3*kn-2:3*kn,3*kn-2:3*kn)=Rot; 67 | end 68 | 69 | % Apply first refocusing pulse and get first echo amplitude 70 | M(1:3)=Rot*M(1:3); 71 | EchoAmp(1)=abs(M(2,1))*exp(-(TE/2)/T2); 72 | 73 | % Apply relaxation matrix 74 | M=RelTrans*M; 75 | % Perform flip-relax sequence ETL-1 times 76 | for ke=2:N 77 | % Perform the flip 78 | M=BigRot*M; 79 | % Record the magnitude of the population of F1* as the echo amplitude 80 | % and allow for relaxation 81 | EchoAmp(ke)=abs(M(2,1))*exp(-(TE/2)/T2); 82 | % Allow time evolution of magnetization between pulses 83 | M=RelTrans*M; 84 | end 85 | 86 | end 87 | 88 | -------------------------------------------------------------------------------- /+remmi/+mse/+MERA/nnlsMEX.c: -------------------------------------------------------------------------------- 1 | /* Modified to interface with MATLAB - MEX (R. Dortch) */ 2 | 3 | /* $Id: nnls.c,v 1.7 2000/11/07 16:29:30 tgkolda Exp $ */ 4 | /* $Source: /usr/local/cvsroot/appspack/apps/src/nnls.c,v $ */ 5 | 6 | /* Distributed with ASYNCHRONOUS PARALLEL PATTERN SEARCH (APPS) */ 7 | 8 | /* The routines in this file have been translated from Fortran to C by 9 | f2c. Additional modifications have been made to remove the 10 | dependencies on the f2c header file and library. The original 11 | Fortran 77 code accompanies the SIAM Publications printing of 12 | "Solving Least Squares Problems," by C. Lawson and R. Hanson and is 13 | freely available at www.netlib.org/lawson-hanson/all. */ 14 | 15 | /* nnls.F -- translated by f2c (version 19970805). 16 | You must link the resulting object file with the libraries: 17 | -lf2c -lm (in that order) 18 | */ 19 | 20 | /* The next line was removed after the f2c translation */ 21 | /* #include "f2c.h" */ 22 | 23 | /* The next lines were added after the f2c translation. Also swapped 24 | abs for nnls_abs and max for nnls_max to avoid confusion with some 25 | compilers. */ 26 | #include 27 | #include 28 | #include "mex.h" /* Inlude MEX Library -- RDD */ 29 | 30 | #define nnls_max(a,b) ((a) >= (b) ? (a) : (b)) 31 | #define nnls_abs(x) ((x) >= 0 ? (x) : -(x)) 32 | typedef int integer; 33 | typedef double doublereal; 34 | 35 | /* The following subroutine was added after the f2c translation */ 36 | double d_sign(double *a, double *b) 37 | { 38 | double x; 39 | x = (*a >= 0 ? *a : - *a); 40 | return( *b >= 0 ? x : -x); 41 | } 42 | 43 | /* Table of constant values */ 44 | 45 | static integer c__1 = 1; 46 | static integer c__0 = 0; 47 | static integer c__2 = 2; 48 | 49 | /* SUBROUTINE NNLSMEX (A,MDA,M,N,B,X,RNORM,W,ZZ,INDEX,MODE) */ 50 | 51 | /* Algorithm NNLS: NONNEGATIVE LEAST SQUARES */ 52 | 53 | /* The original version of this code was developed by */ 54 | /* Charles L. Lawson and Richard J. Hanson at Jet Propulsion Laboratory */ 55 | /* 1973 JUN 15, and published in the book */ 56 | /* "SOLVING LEAST SQUARES PROBLEMS", Prentice-HalL, 1974. */ 57 | /* Revised FEB 1995 to accompany reprinting of the book by SIAM. */ 58 | 59 | /* GIVEN AN M BY N MATRIX, A, AND AN M-VECTOR, B, COMPUTE AN */ 60 | /* N-VECTOR, X, THAT SOLVES THE LEAST SQUARES PROBLEM */ 61 | 62 | /* A * X = B SUBJECT TO X .GE. 0 */ 63 | /* ------------------------------------------------------------------ */ 64 | /* Subroutine Arguments */ 65 | 66 | /* A(),MDA,M,N MDA IS THE FIRST DIMENSIONING PARAMETER FOR THE */ 67 | /* ARRAY, A(). ON ENTRY A() CONTAINS THE M BY N */ 68 | /* MATRIX, A. ON EXIT A() CONTAINS */ 69 | /* THE PRODUCT MATRIX, Q*A , WHERE Q IS AN */ 70 | /* M BY M ORTHOGONAL MATRIX GENERATED IMPLICITLY BY */ 71 | /* THIS SUBROUTINE. */ 72 | /* B() ON ENTRY B() CONTAINS THE M-VECTOR, B. ON EXIT B() CON- */ 73 | /* TAINS Q*B. */ 74 | /* X() ON ENTRY X() NEED NOT BE INITIALIZED. ON EXIT X() WILL */ 75 | /* CONTAIN THE SOLUTION VECTOR. */ 76 | /* RNORM ON EXIT RNORM CONTAINS THE EUCLIDEAN NORM OF THE */ 77 | /* RESIDUAL VECTOR. */ 78 | /* W() AN N-ARRAY OF WORKING SPACE. ON EXIT W() WILL CONTAIN */ 79 | /* THE DUAL SOLUTION VECTOR. W WILL SATISFY W(I) = 0. */ 80 | /* FOR ALL I IN SET P AND W(I) .LE. 0. FOR ALL I IN SET Z */ 81 | /* ZZ() AN M-ARRAY OF WORKING SPACE. */ 82 | /* INDEX() AN INTEGER WORKING ARRAY OF LENGTH AT LEAST N. */ 83 | /* ON EXIT THE CONTENTS OF THIS ARRAY DEFINE THE SETS */ 84 | /* P AND Z AS FOLLOWS.. */ 85 | 86 | /* INDEX(1) THRU INDEX(NSETP) = SET P. */ 87 | /* INDEX(IZ1) THRU INDEX(IZ2) = SET Z. */ 88 | /* IZ1 = NSETP + 1 = NPP1 */ 89 | /* IZ2 = N */ 90 | /* MODE THIS IS A SUCCESS-FAILURE FLAG WITH THE FOLLOWING */ 91 | /* MEANINGS. */ 92 | /* 1 THE SOLUTION HAS BEEN COMPUTED SUCCESSFULLY. */ 93 | /* 2 THE DIMENSIONS OF THE PROBLEM ARE BAD. */ 94 | /* EITHER M .LE. 0 OR N .LE. 0. */ 95 | /* 3 ITERATION COUNT EXCEEDED. MORE THAN 3*N ITERATIONS. */ 96 | 97 | /* ------------------------------------------------------------------ */ 98 | /* Subroutine */ int nnlsMEX(a, mda, m, n, b, x, rnorm, w, zz, index, mode) 99 | doublereal *a; 100 | integer *mda, *m, *n; 101 | doublereal *b, *x, *rnorm, *w, *zz; 102 | integer *index, *mode; 103 | { 104 | /* System generated locals */ 105 | integer a_dim1, a_offset, i__1, i__2; 106 | doublereal d__1, d__2; 107 | 108 | /* Builtin functions */ 109 | /* The following lines were commented out after the f2c translation */ 110 | /* double sqrt(); */ 111 | /* integer s_wsfe(), do_fio(), e_wsfe(); */ 112 | 113 | /* Local variables */ 114 | extern doublereal diff_(); 115 | static integer iter; 116 | static doublereal temp, wmax; 117 | static integer i__, j, l; 118 | static doublereal t, alpha, asave; 119 | static integer itmax, izmax, nsetp; 120 | extern /* Subroutine */ int g1_(); 121 | static doublereal dummy, unorm, ztest, cc; 122 | extern /* Subroutine */ int h12_(); 123 | static integer ii, jj, ip; 124 | static doublereal sm; 125 | static integer iz, jz; 126 | static doublereal up, ss; 127 | static integer rtnkey, iz1, iz2, npp1; 128 | 129 | /* Fortran I/O blocks */ 130 | /* The following line was commented out after the f2c translation */ 131 | /* static cilist io___22 = { 0, 6, 0, "(/a)", 0 }; */ 132 | 133 | 134 | /* ------------------------------------------------------------------ 135 | */ 136 | /* integer INDEX(N) */ 137 | /* double precision A(MDA,N), B(M), W(N), X(N), ZZ(M) */ 138 | /* ------------------------------------------------------------------ 139 | */ 140 | /* Parameter adjustments */ 141 | a_dim1 = *mda; 142 | a_offset = a_dim1 + 1; 143 | a -= a_offset; 144 | --b; 145 | --x; 146 | --w; 147 | --zz; 148 | --index; 149 | 150 | /* Function Body */ 151 | *mode = 1; 152 | if (*m <= 0 || *n <= 0) { 153 | *mode = 2; 154 | return 0; 155 | } 156 | iter = 0; 157 | itmax = *n * 3; 158 | 159 | /* INITIALIZE THE ARRAYS INDEX() AND X(). */ 160 | 161 | i__1 = *n; 162 | for (i__ = 1; i__ <= i__1; ++i__) { 163 | x[i__] = 0.; 164 | /* L20: */ 165 | index[i__] = i__; 166 | } 167 | 168 | iz2 = *n; 169 | iz1 = 1; 170 | nsetp = 0; 171 | npp1 = 1; 172 | /* ****** MAIN LOOP BEGINS HERE ****** */ 173 | L30: 174 | /* QUIT IF ALL COEFFICIENTS ARE ALREADY IN THE SOLUTION. 175 | */ 176 | /* OR IF M COLS OF A HAVE BEEN TRIANGULARIZED. */ 177 | 178 | if (iz1 > iz2 || nsetp >= *m) { 179 | goto L350; 180 | } 181 | 182 | /* COMPUTE COMPONENTS OF THE DUAL (NEGATIVE GRADIENT) VECTOR W(). 183 | */ 184 | 185 | i__1 = iz2; 186 | for (iz = iz1; iz <= i__1; ++iz) { 187 | j = index[iz]; 188 | sm = 0.; 189 | i__2 = *m; 190 | for (l = npp1; l <= i__2; ++l) { 191 | /* L40: */ 192 | sm += a[l + j * a_dim1] * b[l]; 193 | } 194 | w[j] = sm; 195 | /* L50: */ 196 | } 197 | /* FIND LARGEST POSITIVE W(J). */ 198 | L60: 199 | wmax = 0.; 200 | i__1 = iz2; 201 | for (iz = iz1; iz <= i__1; ++iz) { 202 | j = index[iz]; 203 | if (w[j] > wmax) { 204 | wmax = w[j]; 205 | izmax = iz; 206 | } 207 | /* L70: */ 208 | } 209 | 210 | /* IF WMAX .LE. 0. GO TO TERMINATION. */ 211 | /* THIS INDICATES SATISFACTION OF THE KUHN-TUCKER CONDITIONS. 212 | */ 213 | 214 | if (wmax <= 0.) { 215 | goto L350; 216 | } 217 | iz = izmax; 218 | j = index[iz]; 219 | 220 | /* THE SIGN OF W(J) IS OK FOR J TO BE MOVED TO SET P. */ 221 | /* BEGIN THE TRANSFORMATION AND CHECK NEW DIAGONAL ELEMENT TO AVOID */ 222 | /* NEAR LINEAR DEPENDENCE. */ 223 | 224 | asave = a[npp1 + j * a_dim1]; 225 | i__1 = npp1 + 1; 226 | h12_(&c__1, &npp1, &i__1, m, &a[j * a_dim1 + 1], &c__1, &up, &dummy, & 227 | c__1, &c__1, &c__0); 228 | unorm = 0.; 229 | if (nsetp != 0) { 230 | i__1 = nsetp; 231 | for (l = 1; l <= i__1; ++l) { 232 | /* L90: */ 233 | /* Computing 2nd power */ 234 | d__1 = a[l + j * a_dim1]; 235 | unorm += d__1 * d__1; 236 | } 237 | } 238 | unorm = sqrt(unorm); 239 | d__2 = unorm + (d__1 = a[npp1 + j * a_dim1], nnls_abs(d__1)) * .01; 240 | if (diff_(&d__2, &unorm) > 0.) { 241 | 242 | /* COL J IS SUFFICIENTLY INDEPENDENT. COPY B INTO ZZ, UPDATE Z 243 | Z */ 244 | /* AND SOLVE FOR ZTEST ( = PROPOSED NEW VALUE FOR X(J) ). */ 245 | 246 | i__1 = *m; 247 | for (l = 1; l <= i__1; ++l) { 248 | /* L120: */ 249 | zz[l] = b[l]; 250 | } 251 | i__1 = npp1 + 1; 252 | h12_(&c__2, &npp1, &i__1, m, &a[j * a_dim1 + 1], &c__1, &up, &zz[1], & 253 | c__1, &c__1, &c__1); 254 | ztest = zz[npp1] / a[npp1 + j * a_dim1]; 255 | 256 | /* SEE IF ZTEST IS POSITIVE */ 257 | 258 | if (ztest > 0.) { 259 | goto L140; 260 | } 261 | } 262 | 263 | /* REJECT J AS A CANDIDATE TO BE MOVED FROM SET Z TO SET P. */ 264 | /* RESTORE A(NPP1,J), SET W(J)=0., AND LOOP BACK TO TEST DUAL */ 265 | /* COEFFS AGAIN. */ 266 | 267 | a[npp1 + j * a_dim1] = asave; 268 | w[j] = 0.; 269 | goto L60; 270 | 271 | /* THE INDEX J=INDEX(IZ) HAS BEEN SELECTED TO BE MOVED FROM */ 272 | /* SET Z TO SET P. UPDATE B, UPDATE INDICES, APPLY HOUSEHOLDER */ 273 | /* TRANSFORMATIONS TO COLS IN NEW SET Z, ZERO SUBDIAGONAL ELTS IN */ 274 | /* COL J, SET W(J)=0. */ 275 | 276 | L140: 277 | i__1 = *m; 278 | for (l = 1; l <= i__1; ++l) { 279 | /* L150: */ 280 | b[l] = zz[l]; 281 | } 282 | 283 | index[iz] = index[iz1]; 284 | index[iz1] = j; 285 | ++iz1; 286 | nsetp = npp1; 287 | ++npp1; 288 | 289 | if (iz1 <= iz2) { 290 | i__1 = iz2; 291 | for (jz = iz1; jz <= i__1; ++jz) { 292 | jj = index[jz]; 293 | h12_(&c__2, &nsetp, &npp1, m, &a[j * a_dim1 + 1], &c__1, &up, &a[ 294 | jj * a_dim1 + 1], &c__1, mda, &c__1); 295 | /* L160: */ 296 | } 297 | } 298 | 299 | if (nsetp != *m) { 300 | i__1 = *m; 301 | for (l = npp1; l <= i__1; ++l) { 302 | /* L180: */ 303 | a[l + j * a_dim1] = 0.; 304 | } 305 | } 306 | 307 | w[j] = 0.; 308 | /* SOLVE THE TRIANGULAR SYSTEM. */ 309 | /* STORE THE SOLUTION TEMPORARILY IN ZZ(). 310 | */ 311 | rtnkey = 1; 312 | goto L400; 313 | L200: 314 | 315 | /* ****** SECONDARY LOOP BEGINS HERE ****** */ 316 | 317 | /* ITERATION COUNTER. */ 318 | 319 | L210: 320 | ++iter; 321 | if (iter > itmax) { 322 | *mode = 3; 323 | /* The following lines were replaced after the f2c translation */ 324 | /* s_wsfe(&io___22); */ 325 | /* do_fio(&c__1, " NNLS quitting on iteration count.", 34L); */ 326 | /* e_wsfe(); */ 327 | fprintf(stdout, "\n NNLS quitting on iteration count.\n"); 328 | fflush(stdout); 329 | goto L350; 330 | } 331 | 332 | /* SEE IF ALL NEW CONSTRAINED COEFFS ARE FEASIBLE. */ 333 | /* IF NOT COMPUTE ALPHA. */ 334 | 335 | alpha = 2.; 336 | i__1 = nsetp; 337 | for (ip = 1; ip <= i__1; ++ip) { 338 | l = index[ip]; 339 | if (zz[ip] <= 0.) { 340 | t = -x[l] / (zz[ip] - x[l]); 341 | if (alpha > t) { 342 | alpha = t; 343 | jj = ip; 344 | } 345 | } 346 | /* L240: */ 347 | } 348 | 349 | /* IF ALL NEW CONSTRAINED COEFFS ARE FEASIBLE THEN ALPHA WILL */ 350 | /* STILL = 2. IF SO EXIT FROM SECONDARY LOOP TO MAIN LOOP. */ 351 | 352 | if (alpha == 2.) { 353 | goto L330; 354 | } 355 | 356 | /* OTHERWISE USE ALPHA WHICH WILL BE BETWEEN 0. AND 1. TO */ 357 | /* INTERPOLATE BETWEEN THE OLD X AND THE NEW ZZ. */ 358 | 359 | i__1 = nsetp; 360 | for (ip = 1; ip <= i__1; ++ip) { 361 | l = index[ip]; 362 | x[l] += alpha * (zz[ip] - x[l]); 363 | /* L250: */ 364 | } 365 | 366 | /* MODIFY A AND B AND THE INDEX ARRAYS TO MOVE COEFFICIENT I */ 367 | /* FROM SET P TO SET Z. */ 368 | 369 | i__ = index[jj]; 370 | L260: 371 | x[i__] = 0.; 372 | 373 | if (jj != nsetp) { 374 | ++jj; 375 | i__1 = nsetp; 376 | for (j = jj; j <= i__1; ++j) { 377 | ii = index[j]; 378 | index[j - 1] = ii; 379 | g1_(&a[j - 1 + ii * a_dim1], &a[j + ii * a_dim1], &cc, &ss, &a[j 380 | - 1 + ii * a_dim1]); 381 | a[j + ii * a_dim1] = 0.; 382 | i__2 = *n; 383 | for (l = 1; l <= i__2; ++l) { 384 | if (l != ii) { 385 | 386 | /* Apply procedure G2 (CC,SS,A(J-1,L),A(J, 387 | L)) */ 388 | 389 | temp = a[j - 1 + l * a_dim1]; 390 | a[j - 1 + l * a_dim1] = cc * temp + ss * a[j + l * a_dim1] 391 | ; 392 | a[j + l * a_dim1] = -ss * temp + cc * a[j + l * a_dim1]; 393 | } 394 | /* L270: */ 395 | } 396 | 397 | /* Apply procedure G2 (CC,SS,B(J-1),B(J)) */ 398 | 399 | temp = b[j - 1]; 400 | b[j - 1] = cc * temp + ss * b[j]; 401 | b[j] = -ss * temp + cc * b[j]; 402 | /* L280: */ 403 | } 404 | } 405 | 406 | npp1 = nsetp; 407 | --nsetp; 408 | --iz1; 409 | index[iz1] = i__; 410 | 411 | /* SEE IF THE REMAINING COEFFS IN SET P ARE FEASIBLE. THEY SHOULD 412 | */ 413 | /* BE BECAUSE OF THE WAY ALPHA WAS DETERMINED. */ 414 | /* IF ANY ARE INFEASIBLE IT IS DUE TO ROUND-OFF ERROR. ANY */ 415 | /* THAT ARE NONPOSITIVE WILL BE SET TO ZERO */ 416 | /* AND MOVED FROM SET P TO SET Z. */ 417 | 418 | i__1 = nsetp; 419 | for (jj = 1; jj <= i__1; ++jj) { 420 | i__ = index[jj]; 421 | if (x[i__] <= 0.) { 422 | goto L260; 423 | } 424 | /* L300: */ 425 | } 426 | 427 | /* COPY B( ) INTO ZZ( ). THEN SOLVE AGAIN AND LOOP BACK. */ 428 | 429 | i__1 = *m; 430 | for (i__ = 1; i__ <= i__1; ++i__) { 431 | /* L310: */ 432 | zz[i__] = b[i__]; 433 | } 434 | rtnkey = 2; 435 | goto L400; 436 | L320: 437 | goto L210; 438 | /* ****** END OF SECONDARY LOOP ****** */ 439 | 440 | L330: 441 | i__1 = nsetp; 442 | for (ip = 1; ip <= i__1; ++ip) { 443 | i__ = index[ip]; 444 | /* L340: */ 445 | x[i__] = zz[ip]; 446 | } 447 | /* ALL NEW COEFFS ARE POSITIVE. LOOP BACK TO BEGINNING. */ 448 | goto L30; 449 | 450 | /* ****** END OF MAIN LOOP ****** */ 451 | 452 | /* COME TO HERE FOR TERMINATION. */ 453 | /* COMPUTE THE NORM OF THE FINAL RESIDUAL VECTOR. */ 454 | 455 | L350: 456 | sm = 0.; 457 | if (npp1 <= *m) { 458 | i__1 = *m; 459 | for (i__ = npp1; i__ <= i__1; ++i__) { 460 | /* L360: */ 461 | /* Computing 2nd power */ 462 | d__1 = b[i__]; 463 | sm += d__1 * d__1; 464 | } 465 | } else { 466 | i__1 = *n; 467 | for (j = 1; j <= i__1; ++j) { 468 | /* L380: */ 469 | w[j] = 0.; 470 | } 471 | } 472 | *rnorm = sqrt(sm); 473 | return 0; 474 | 475 | /* THE FOLLOWING BLOCK OF CODE IS USED AS AN INTERNAL SUBROUTINE */ 476 | /* TO SOLVE THE TRIANGULAR SYSTEM, PUTTING THE SOLUTION IN ZZ(). */ 477 | 478 | L400: 479 | i__1 = nsetp; 480 | for (l = 1; l <= i__1; ++l) { 481 | ip = nsetp + 1 - l; 482 | if (l != 1) { 483 | i__2 = ip; 484 | for (ii = 1; ii <= i__2; ++ii) { 485 | zz[ii] -= a[ii + jj * a_dim1] * zz[ip + 1]; 486 | /* L410: */ 487 | } 488 | } 489 | jj = index[ip]; 490 | zz[ip] /= a[ip + jj * a_dim1]; 491 | /* L430: */ 492 | } 493 | switch ((int)rtnkey) { 494 | case 1: goto L200; 495 | case 2: goto L320; 496 | } 497 | 498 | /* The next line was added after the f2c translation to keep 499 | compilers from complaining about a void return from a non-void 500 | function. */ 501 | return 0; 502 | 503 | } /* nnlsMEX_ */ 504 | 505 | /* Subroutine */ int g1_(a, b, cterm, sterm, sig) 506 | doublereal *a, *b, *cterm, *sterm, *sig; 507 | { 508 | /* System generated locals */ 509 | doublereal d__1; 510 | 511 | /* Builtin functions */ 512 | /* The following line was commented out after the f2c translation */ 513 | /* double sqrt(), d_sign(); */ 514 | 515 | /* Local variables */ 516 | static doublereal xr, yr; 517 | 518 | 519 | /* COMPUTE ORTHOGONAL ROTATION MATRIX.. */ 520 | 521 | /* The original version of this code was developed by */ 522 | /* Charles L. Lawson and Richard J. Hanson at Jet Propulsion Laboratory 523 | */ 524 | /* 1973 JUN 12, and published in the book */ 525 | /* "SOLVING LEAST SQUARES PROBLEMS", Prentice-HalL, 1974. */ 526 | /* Revised FEB 1995 to accompany reprinting of the book by SIAM. */ 527 | 528 | /* COMPUTE.. MATRIX (C, S) SO THAT (C, S)(A) = (SQRT(A**2+B**2)) */ 529 | /* (-S,C) (-S,C)(B) ( 0 ) */ 530 | /* COMPUTE SIG = SQRT(A**2+B**2) */ 531 | /* SIG IS COMPUTED LAST TO ALLOW FOR THE POSSIBILITY THAT */ 532 | /* SIG MAY BE IN THE SAME LOCATION AS A OR B . */ 533 | /* ------------------------------------------------------------------ 534 | */ 535 | /* ------------------------------------------------------------------ 536 | */ 537 | if (nnls_abs(*a) > nnls_abs(*b)) { 538 | xr = *b / *a; 539 | /* Computing 2nd power */ 540 | d__1 = xr; 541 | yr = sqrt(d__1 * d__1 + 1.); 542 | d__1 = 1. / yr; 543 | *cterm = d_sign(&d__1, a); 544 | *sterm = *cterm * xr; 545 | *sig = nnls_abs(*a) * yr; 546 | return 0; 547 | } 548 | if (*b != 0.) { 549 | xr = *a / *b; 550 | /* Computing 2nd power */ 551 | d__1 = xr; 552 | yr = sqrt(d__1 * d__1 + 1.); 553 | d__1 = 1. / yr; 554 | *sterm = d_sign(&d__1, b); 555 | *cterm = *sterm * xr; 556 | *sig = nnls_abs(*b) * yr; 557 | return 0; 558 | } 559 | *sig = 0.; 560 | *cterm = 0.; 561 | *sterm = 1.; 562 | return 0; 563 | } /* g1_ */ 564 | 565 | /* SUBROUTINE H12 (MODE,LPIVOT,L1,M,U,IUE,UP,C,ICE,ICV,NCV) */ 566 | 567 | /* CONSTRUCTION AND/OR APPLICATION OF A SINGLE */ 568 | /* HOUSEHOLDER TRANSFORMATION.. Q = I + U*(U**T)/B */ 569 | 570 | /* The original version of this code was developed by */ 571 | /* Charles L. Lawson and Richard J. Hanson at Jet Propulsion Laboratory */ 572 | /* 1973 JUN 12, and published in the book */ 573 | /* "SOLVING LEAST SQUARES PROBLEMS", Prentice-HalL, 1974. */ 574 | /* Revised FEB 1995 to accompany reprinting of the book by SIAM. */ 575 | /* ------------------------------------------------------------------ */ 576 | /* Subroutine Arguments */ 577 | 578 | /* MODE = 1 OR 2 Selects Algorithm H1 to construct and apply a */ 579 | /* Householder transformation, or Algorithm H2 to apply a */ 580 | /* previously constructed transformation. */ 581 | /* LPIVOT IS THE INDEX OF THE PIVOT ELEMENT. */ 582 | /* L1,M IF L1 .LE. M THE TRANSFORMATION WILL BE CONSTRUCTED TO */ 583 | /* ZERO ELEMENTS INDEXED FROM L1 THROUGH M. IF L1 GT. M */ 584 | /* THE SUBROUTINE DOES AN IDENTITY TRANSFORMATION. */ 585 | /* U(),IUE,UP On entry with MODE = 1, U() contains the pivot */ 586 | /* vector. IUE is the storage increment between elements. */ 587 | /* On exit when MODE = 1, U() and UP contain quantities */ 588 | /* defining the vector U of the Householder transformation. */ 589 | /* on entry with MODE = 2, U() and UP should contain */ 590 | /* quantities previously computed with MODE = 1. These will */ 591 | /* not be modified during the entry with MODE = 2. */ 592 | /* C() ON ENTRY with MODE = 1 or 2, C() CONTAINS A MATRIX WHICH */ 593 | /* WILL BE REGARDED AS A SET OF VECTORS TO WHICH THE */ 594 | /* HOUSEHOLDER TRANSFORMATION IS TO BE APPLIED. */ 595 | /* ON EXIT C() CONTAINS THE SET OF TRANSFORMED VECTORS. */ 596 | /* ICE STORAGE INCREMENT BETWEEN ELEMENTS OF VECTORS IN C(). */ 597 | /* ICV STORAGE INCREMENT BETWEEN VECTORS IN C(). */ 598 | /* NCV NUMBER OF VECTORS IN C() TO BE TRANSFORMED. IF NCV .LE. 0 */ 599 | /* NO OPERATIONS WILL BE DONE ON C(). */ 600 | /* ------------------------------------------------------------------ */ 601 | /* Subroutine */ int h12_(mode, lpivot, l1, m, u, iue, up, c__, ice, icv, ncv) 602 | integer *mode, *lpivot, *l1, *m; 603 | doublereal *u; 604 | integer *iue; 605 | doublereal *up, *c__; 606 | integer *ice, *icv, *ncv; 607 | { 608 | /* System generated locals */ 609 | integer u_dim1, u_offset, i__1, i__2; 610 | doublereal d__1, d__2; 611 | 612 | /* Builtin functions */ 613 | /* The following line was commented out after the f2c translation */ 614 | /* double sqrt(); */ 615 | 616 | /* Local variables */ 617 | static integer incr; 618 | static doublereal b; 619 | static integer i__, j; 620 | static doublereal clinv; 621 | static integer i2, i3, i4; 622 | static doublereal cl, sm; 623 | 624 | /* ------------------------------------------------------------------ 625 | */ 626 | /* double precision U(IUE,M) */ 627 | /* ------------------------------------------------------------------ 628 | */ 629 | /* Parameter adjustments */ 630 | u_dim1 = *iue; 631 | u_offset = u_dim1 + 1; 632 | u -= u_offset; 633 | --c__; 634 | 635 | /* Function Body */ 636 | if (0 >= *lpivot || *lpivot >= *l1 || *l1 > *m) { 637 | return 0; 638 | } 639 | cl = (d__1 = u[*lpivot * u_dim1 + 1], nnls_abs(d__1)); 640 | if (*mode == 2) { 641 | goto L60; 642 | } 643 | /* ****** CONSTRUCT THE TRANSFORMATION. ****** 644 | */ 645 | i__1 = *m; 646 | for (j = *l1; j <= i__1; ++j) { 647 | /* L10: */ 648 | /* Computing MAX */ 649 | d__2 = (d__1 = u[j * u_dim1 + 1], nnls_abs(d__1)); 650 | cl = nnls_max(d__2,cl); 651 | } 652 | if (cl <= 0.) { 653 | goto L130; 654 | } else { 655 | goto L20; 656 | } 657 | L20: 658 | clinv = 1. / cl; 659 | /* Computing 2nd power */ 660 | d__1 = u[*lpivot * u_dim1 + 1] * clinv; 661 | sm = d__1 * d__1; 662 | i__1 = *m; 663 | for (j = *l1; j <= i__1; ++j) { 664 | /* L30: */ 665 | /* Computing 2nd power */ 666 | d__1 = u[j * u_dim1 + 1] * clinv; 667 | sm += d__1 * d__1; 668 | } 669 | cl *= sqrt(sm); 670 | if (u[*lpivot * u_dim1 + 1] <= 0.) { 671 | goto L50; 672 | } else { 673 | goto L40; 674 | } 675 | L40: 676 | cl = -cl; 677 | L50: 678 | *up = u[*lpivot * u_dim1 + 1] - cl; 679 | u[*lpivot * u_dim1 + 1] = cl; 680 | goto L70; 681 | /* ****** APPLY THE TRANSFORMATION I+U*(U**T)/B TO C. ****** 682 | */ 683 | 684 | L60: 685 | if (cl <= 0.) { 686 | goto L130; 687 | } else { 688 | goto L70; 689 | } 690 | L70: 691 | if (*ncv <= 0) { 692 | return 0; 693 | } 694 | b = *up * u[*lpivot * u_dim1 + 1]; 695 | /* B MUST BE NONPOSITIVE HERE. IF B = 0., RETURN. 696 | */ 697 | 698 | if (b >= 0.) { 699 | goto L130; 700 | } else { 701 | goto L80; 702 | } 703 | L80: 704 | b = 1. / b; 705 | i2 = 1 - *icv + *ice * (*lpivot - 1); 706 | incr = *ice * (*l1 - *lpivot); 707 | i__1 = *ncv; 708 | for (j = 1; j <= i__1; ++j) { 709 | i2 += *icv; 710 | i3 = i2 + incr; 711 | i4 = i3; 712 | sm = c__[i2] * *up; 713 | i__2 = *m; 714 | for (i__ = *l1; i__ <= i__2; ++i__) { 715 | sm += c__[i3] * u[i__ * u_dim1 + 1]; 716 | /* L90: */ 717 | i3 += *ice; 718 | } 719 | if (sm != 0.) { 720 | goto L100; 721 | } else { 722 | goto L120; 723 | } 724 | L100: 725 | sm *= b; 726 | c__[i2] += sm * *up; 727 | i__2 = *m; 728 | for (i__ = *l1; i__ <= i__2; ++i__) { 729 | c__[i4] += sm * u[i__ * u_dim1 + 1]; 730 | /* L110: */ 731 | i4 += *ice; 732 | } 733 | L120: 734 | ; 735 | } 736 | L130: 737 | return 0; 738 | } /* h12_ */ 739 | 740 | doublereal diff_(x, y) 741 | doublereal *x, *y; 742 | { 743 | /* System generated locals */ 744 | doublereal ret_val; 745 | 746 | 747 | /* Function used in tests that depend on machine precision. */ 748 | 749 | /* The original version of this code was developed by */ 750 | /* Charles L. Lawson and Richard J. Hanson at Jet Propulsion Laboratory 751 | */ 752 | /* 1973 JUN 7, and published in the book */ 753 | /* "SOLVING LEAST SQUARES PROBLEMS", Prentice-HalL, 1974. */ 754 | /* Revised FEB 1995 to accompany reprinting of the book by SIAM. */ 755 | 756 | ret_val = *x - *y; 757 | return ret_val; 758 | } /* diff_ */ 759 | 760 | 761 | /* The following subroutine was added after the f2c translation */ 762 | int nnls_c(double* a, const int* mda, const int* m, const int* n, double* b, 763 | double* x, double* rnorm, double* w, double* zz, int* index, 764 | int* mode) 765 | { 766 | return (nnlsMEX(a, mda, m, n, b, x, rnorm, w, zz, index, mode)); 767 | } 768 | 769 | 770 | /* MEX Funtion Gateway - RDD */ 771 | void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[]) 772 | { 773 | double *a,*b,*x,*rnorm,*w,*zz; 774 | int *mda,*m,*n,*idx,*mode; 775 | mwSize row, col = 1; 776 | 777 | 778 | /* Set nnls input parmeters */ 779 | a = mxGetPr(prhs[0]); 780 | mda = (int *)mxGetPr(prhs[1]); 781 | m = (int *)mxGetPr(prhs[2]); 782 | n = (int *)mxGetPr(prhs[3]); 783 | b = mxGetPr(prhs[4]); 784 | rnorm = mxGetPr(prhs[5]); 785 | w = mxGetPr(prhs[6]); 786 | zz = mxGetPr(prhs[7]); 787 | idx = (int *)mxGetPr(prhs[8]); 788 | mode = (int *)mxGetPr(prhs[9]); 789 | 790 | /* Create a matrix for the output parameter */ 791 | row = mxGetN(prhs[0]); 792 | plhs[0] = mxCreateDoubleMatrix(row, col, mxREAL); 793 | x = mxGetPr(plhs[0]); 794 | 795 | /* Call nnls subroutine */ 796 | nnlsMEX(a,m,m,n,b,x,rnorm,w,zz,idx,mode); 797 | return; 798 | } 799 | -------------------------------------------------------------------------------- /+remmi/+mse/+MERA/test1D.m: -------------------------------------------------------------------------------- 1 | % demonstration/test code for MERA 2.0 2 | % 1D multiple exponential examples 3 | % 4 | clear variables, 5 | close all 6 | clc 7 | 8 | 9 | Ntrials = 10; % number of independent noise realizations 10 | SNR = 250; % signal to noise ratio at time t = 0 11 | 12 | 13 | 14 | % create 1D signals with time constants T2 = [T2_1, T2_2, ..., T1_K]' 15 | % and corresponding signal amplitudes f = [f_1, f_2, ..., f_K]' 16 | T2 = [20e-3, 100e-3]'; 17 | f = [2 8]'; 18 | 19 | % mimic CPMG measurement in NMR with refocussing pulse angle theta (in 20 | % degrees) 21 | theta = 155; 22 | 23 | % samples in time are uniformly space (not a requirement for MERA) with 24 | % period TE 25 | TE = 7.5e-3; 26 | t = (1:50)'*TE; 27 | 28 | % maketestdata1D returns a structure containing the signal data.D and 29 | % independent variable, data.t 30 | data = maketestdata1D(T2,f,t,theta,Ntrials,SNR); 31 | 32 | % now set fitting and analysis options 33 | 34 | % analysis.interactive = 'y' provide an interactive GUI. This is useful for 35 | % testing out different fitting options, but will only process one signal 36 | analysis.interactive = 'n'; 37 | 38 | % If your signal came from an NMR CPMG measurement (see literature 39 | % references in code) setting fitting.B1fit = 'y' will fit out the 40 | % refocussing pulse flip angle. This is useful when measuring NMR 41 | % transverse relaxation with imaging, where the RF transmit field likely 42 | % varies over the image field of view. This feature only works for 1D 43 | % signals. 44 | fitting.B1fit = 'y'; 45 | % If this option is used, you may choose a range and number of flip angles 46 | % (theta) to test. If you do not test a sufficient number or range, you may 47 | % not get the best fit. 48 | fitting.rangetheta = [130 180]; 49 | fitting.numbertheta = 10; 50 | 51 | 52 | % Choose the type of spectral regularization. 'none' is obvious. 53 | % 'me' (min amplitude energy) and 'mc' (min curvature energy') are very common 54 | % choices. 55 | % 'upen' is the "uniform penalty" regularization (see literature 56 | % references in code) which allows regularization weighting to vary across 57 | % the spectrum, which may be useful if varying degrees of smoothness is 58 | % expected across the spectrum.'upen' can be very slow. 59 | % 'mg' is not a conventional regularization, but rather uses non-linear 60 | % regression to fit a spectrum of relaxation time constants with a number of 61 | % guassian-shaped components in the log-time-constant domain. Because it is 62 | % a non-linear fitting, initial estimates for time-constants can be supplied 63 | % (fitting.T0guass) or will be generated automatically. Also, to avoid 64 | % local minima solutions, the fitting can be automatically repeated with 65 | % varied intial estimates (fitting.numberT0) and the lowest error solution is 66 | % selected 67 | fitting.regtyp = 'me'; 68 | % % optional inputs for 'mg' fitting 69 | % fitting.widthgauss = 5; 70 | % fitting.numbergauss = 2; 71 | % fitting.T0gauss = [0.02 0.07]'; 72 | % fitting.numberT0 = 5; 73 | 74 | % for conventional regularization, the regulization weighting must be 75 | % adjusted. This can be done manually (fitting.regadj = 'manual') or using 76 | % one of two automatic methods ('gcv' and 'erinc') -- see literature 77 | % references in code 78 | fitting.regadj = 'manual'; 79 | fitting.regweight = 0.001; 80 | 81 | % graph the results or not. This input is irrelevant if 82 | % analysis.interactive = 'y' 83 | 84 | analysis.graph = 'n'; 85 | 86 | % define the range of time constants to fit. Note that for 'mg' fitting, 87 | % this is the full range of the spectrum, but the lowest and highest mean 88 | % component time constants (echoed to display) cover a narrower domain 89 | fitting.rangeT = [2.5e-3 0.25]; 90 | 91 | % define the number of time constants in the spectrum 92 | fitting.numberT = 200; 93 | 94 | % set the non-negative least square (NNLS) code. In most cases, the supplied 95 | % nnlsmex fuction is the fastest NNLS solver. You may need to compile this 96 | % for your system. Or you can specify the MATLAB fuction, 97 | % fitting.nnlscode = 'lsqnonneg'. 98 | 99 | % You can automatically or manually extract a finite number of component 100 | % amplitudes and time constants. Test this out with the interactive GUI. 101 | analysis.extract = 'auto'; 102 | analysis.numberextract = 2; 103 | 104 | % If automatic component extraction is used, you can also automatically 105 | % disregard signals outside a specified T-domain, analysis.rangeA, or those 106 | % below a specified signal fraction, analysis.tolextract 107 | 108 | % analysis.rangeA = [10e-3,1]; 109 | % analysis.tolextract = 2; 110 | 111 | 112 | data = maketestdata1D(T2,f,t,theta,Ntrials,SNR); 113 | 114 | tic 115 | [out1D,fittingout]=MERA(data,fitting,analysis); 116 | toc 117 | 118 | 119 | 120 | 121 | 122 | 123 | %% fit with a variety of regularization options 124 | 125 | 126 | Ntrials = 100; % number of independent noise realizations 127 | SNR = 250; % signal to noise ratio at time t = 0 128 | T2 = [20e-3, 100e-3]'; 129 | f = [2 8]'; 130 | theta = 160; 131 | 132 | data = maketestdata1D(T2,f,t,theta,Ntrials,SNR); 133 | 134 | 135 | % loop through all regulatization types and fit data 136 | regtyp_list = {'none','me','mc','upen','mg'}; 137 | analysis.interactive = 'n'; 138 | fitting.B1fit = 'y'; 139 | analysis.graph = 'n'; 140 | fitting.numbergauss = 2; 141 | fitting.widthgauss = 5; 142 | runtime = zeros(1,length(regtyp_list)); 143 | close all 144 | clear FvM FvS TvM TvS AvM 145 | 146 | for kr = 1:length(regtyp_list) 147 | clear Av Fv Tv 148 | fitting.regtyp = regtyp_list{kr}; 149 | tic 150 | [out1D] = MERA(data,fitting,analysis); 151 | runtime(kr) = toc; 152 | % calculate mean and SD of signal component fractions and T2 153 | AvM{kr} = mean(out1D.Av,2); 154 | FvM{kr} = mean(out1D.Fv,2); 155 | FvS{kr} = std(out1D.Fv,[],2); 156 | TvM{kr} = mean(out1D.Tv,2); 157 | TvS{kr} = std(out1D.Tv,[],2); 158 | 159 | outtest{kr} = out1D; 160 | end 161 | 162 | % Test results with SNR = 250, Nt = 100 163 | % T2 = [20e-3, 100e-3]'; 164 | % f = [2 8]'; 165 | % means and coef of variation, each column is a different 'regtyp' 166 | % 167 | % show benchmarks for each regtyp 168 | % 'none' 'me' 'mc' 'mg' 'upen' 169 | % Mac Pro, Early 2008, 2.8 GHz quad core Intel Xenon 170 | % %bi-exponential, theta = 180, B1fit = 'n' 171 | % 0.5380 1.7323 1.6742 3.7294 9.9274 172 | % %bi-exponential, theta = 160, B1fit = 'y' 173 | % 16.5054 30.4711 30.8034 54.6984 143.4539 174 | % iMac 21.5 inch, mid 2011, 2.8 GHz Intel quad core i7 175 | % %bi-exponential, theta = 180, B1fit = 'n' 176 | % 0.5068 1.6009 1.5481 4.4234 8.5202 177 | % %bi-exponential, theta = 160, B1fit = 'y' 178 | % 12.4144 25.9889 24.8830 45.8386 122.6775 179 | 180 | -------------------------------------------------------------------------------- /+remmi/+mse/+MERA/test2D.m: -------------------------------------------------------------------------------- 1 | % two-dimensional demonstration code for MERA 2.0 2 | 3 | clear all 4 | close all 5 | 6 | % Create Example 2D Data 7 | % Two Components, Bi-exp in two dimensions 8 | 9 | 10 | SNR = 1000; % signal to noise ratio 11 | 12 | % amplitude and time constants for signal component 1 13 | M1 = .4; Ta1 = .020; Tb1 = 0.35; 14 | % amplitude and time constants for signal component 2 15 | M2 = .6; Ta2 = .0750; Tb2 = .25; 16 | % amplitude and time constants for signal component 3 17 | M3 = .04; Ta3 = .0750; Tb3 = 1; 18 | 19 | % sampling times in first dimension (rows) 20 | Na = 128; 21 | ta = .001*(1:Na)'; 22 | % sampling times in second dim (columns), 23 | Nb = 64; 24 | tb = logspace(log10(0.02),log10(5),Nb)'; 25 | 26 | D = zeros(Na,Nb); 27 | 28 | for i=1:length(tb) 29 | for j=1:length(ta) 30 | D(j,i) = M1 * exp(-(tb(i))./Tb1) * exp(-(ta(j))./Ta1) ... 31 | + M2 * exp(-(tb(i))./Tb2) * exp(-(ta(j))./Ta2) ... 32 | + M3 * exp(-(tb(i))./Tb3) * exp(-(ta(j))./Ta3); 33 | end 34 | end 35 | 36 | SNR = 1000; 37 | 38 | v = randn(size(D))./SNR;% Gaussian random noise 39 | Dv = (D+v);% Noisy signal 40 | data.D = Dv; 41 | data.t = ta; 42 | data.t2 = tb; 43 | 44 | % 45 | 46 | %% Calling MERA 47 | % profile -memory on 48 | clear fitting 49 | close all 50 | fitting.twoD = 'y'; 51 | analysis.interactive = 'y' 52 | fitting.regtyp = 'me'; 53 | fitting.rangeT = [10e-3,.25]; 54 | fitting.rangeT2 = [20e-3,1]; 55 | fitting.numbergauss = 2 56 | fitting.regadj = 'manual'; 57 | analysis.graph = 'y'; 58 | fitting.regweight = 0.002; 59 | fitting.numberT = 25; 60 | fitting.numberT2 = 25; 61 | analysis.extract = 'auto' 62 | 63 | [out2D,fitting_out]=MERA(data,fitting,analysis); 64 | -------------------------------------------------------------------------------- /+remmi/+mse/T2.m: -------------------------------------------------------------------------------- 1 | function t2Set = T2(dset,varargin) 2 | % irSet = remmi.ir.T2(dset,name) performs T2 analysis on data in dset. 3 | % 4 | % dset.(name) = data to process 5 | % dset.mask = mask for processing data 6 | % dset.pars = basic remmi parameter set, including te 7 | % dset.labels = cell array of labels to dset.img dimensions 8 | % 9 | % if dset or dset.(name) is not given, default reconstruction and 10 | % thresholding methods are called 11 | % 12 | % name = name of field in dset to fit. Default is 'img' 13 | % 14 | % Returns a data set containing parameter maps of M0, T2 (same units 15 | % as dset.pars.te) 16 | % 17 | % Note: this analysis assumes 180 deg refocusing pulses, and neglects 18 | % stimulated echo pathways (for now). 19 | % 20 | % Kevin Harkins & Mark Does, Vanderbilt University 21 | % for the REMMI Toolbox 22 | 23 | name = setoptions(varargin{:}); 24 | 25 | if ~exist('dset','var') 26 | dset = struct(); 27 | end 28 | 29 | if ~isfield(dset,name) || isempty(dset.(name)) 30 | dset = remmi.util.thresholdmask(remmi.recon(dset)); 31 | end 32 | 33 | % load in the dataset 34 | sz = size(dset.(name)); 35 | 36 | % what dimension is multi-echo encoding? 37 | echoDim = ismember(dset.labels,'NE'); 38 | 39 | if ~any(echoDim) 40 | error('Data set does not contain multiple echo times'); 41 | end 42 | 43 | % load in the needed parameters 44 | te = dset.pars.te; 45 | 46 | if numel(te)<2 47 | error('There are not enough echo times in this datatset for T2 analysis') 48 | end 49 | 50 | if numel(te) ~= sz(echoDim) 51 | error('The number of echo times does not match the dataset dimenions') 52 | end 53 | 54 | % define a mask if one is not given 55 | if isfield(dset,'mask') 56 | mask = squeeze(dset.mask); 57 | else 58 | mask = squeeze(true(sz(~echoDim))); 59 | end 60 | 61 | % initialize the mtir dataset 62 | t2Set.M0 = zeros(size(mask)); 63 | t2Set.T2 = zeros(size(mask)); 64 | t2Set.nrmse = zeros(size(mask)); 65 | t2Set.ci = cell(size(mask)); 66 | 67 | tot_evals = sum(mask(:)); 68 | evals = 0; 69 | 70 | % make the NE dimension the first index. 71 | idx = 1:numel(size(dset.(name))); 72 | data = permute(dset.(name),[idx(echoDim) idx(~echoDim)]); 73 | 74 | warning('off','MATLAB:singularMatrix') 75 | 76 | t2fun = @(x) abs(x(1)*exp(-te/x(2))); 77 | 78 | fprintf('%3.0f %% done...',0); 79 | for n=1:numel(mask) 80 | if mask(n) 81 | 82 | sig = squeeze(abs(data(:,n))); 83 | 84 | % initial guess & bounds 85 | b0 = [max(sig), 5*te(2)-te(1);]; 86 | lb = [ 0, 0]; 87 | ub = [ inf, inf]; 88 | 89 | % fit the data 90 | opts = optimset('display','off'); 91 | [b,~,res,~,~,~,jac] = lsqnonlin(@(x) t2fun(x)-sig(:),b0,lb,ub,opts); 92 | 93 | % load the dataset 94 | t2Set.M0(n)=b(1); 95 | t2Set.T2(n)=b(2); 96 | t2Set.nrmse(n) = norm(res)/norm(sig); 97 | 98 | % save confidence intervals on the original parameters 99 | t2Set.ci{n} = nlparci(b,res,'jacobian',jac); 100 | 101 | evals = evals+1; 102 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...',evals/tot_evals*100); 103 | end 104 | end 105 | fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%3.0f %% done...\n',100); 106 | 107 | warning('on','MATLAB:singularMatrix') 108 | 109 | end 110 | 111 | function [name] = setoptions(name) 112 | 113 | if ~exist('name','var') || isempty(name) 114 | name = 'img'; 115 | end 116 | 117 | end -------------------------------------------------------------------------------- /+remmi/+mse/analysis.m: -------------------------------------------------------------------------------- 1 | function t2set = analysis(dset,varargin) 2 | % t2set = remmi.mse.analysis(dset,metrics,fitting,mera_analysis,name) 3 | % performs generalized relaxometry analysis with MERA on data in dset: 4 | % 5 | % dset.(name) = data to process 6 | % dset.mask = mask for processing data 7 | % dset.pars = basic remmi parameter set including te 8 | % dset.labels = cell array of labels to dset.img dimensions 9 | % 10 | % metrics = a optional structure of function handles that operate on 11 | % the output structure of MERA. 12 | % 13 | % fitting & mera_analysis = passed directly to MERA for multi- 14 | % exponential T2/EPG analysis 15 | % 16 | % name = name of field in dset to fit. Default is 'img' 17 | % 18 | % If not provided, default values for metrics, fitting, mera_analysis, 19 | % and name are taken from remmi.mse.mT2options 20 | % 21 | % Returns a dataset which contains parameter maps defined in the metrics 22 | % structure 23 | % 24 | % Kevin Harkins & Mark Does, Vanderbilt University 25 | % for the REMMI Toolbox 26 | 27 | % by default, use epg options 28 | [metrics,fitting,manalysis,name] = remmi.mse.mT2options(varargin{:}); 29 | 30 | if ~exist('dset','var') 31 | dset = struct(); 32 | end 33 | 34 | if ~isfield(dset,name) || isempty(dset.(name)) 35 | dset = remmi.util.thresholdmask(remmi.recon(dset)); 36 | end 37 | 38 | sz = size(dset.(name)); 39 | if length(sz) < length(dset.labels) 40 | sz(length(dset.labels)) = 1; 41 | end 42 | seg_sz = 20000; % number of multi-echo measurements to process at one time 43 | 44 | % what dimension is multiple echoes? 45 | echoDim = ismember(dset.labels,'NE'); 46 | 47 | if ~any(echoDim) 48 | error('Data set does not contain multiple echo times'); 49 | end 50 | 51 | % get the echo times 52 | in.t = dset.pars.te; % sec 53 | 54 | % define a mask if one is not given 55 | if isfield(dset,'mask') 56 | mask = squeeze(dset.mask); 57 | 58 | % apply the mask across all non-echo dimensions 59 | msz = sz(~echoDim); 60 | if numel(msz)==1 61 | msz(2) = 1; 62 | end 63 | mask = bsxfun(@times,mask,ones(msz)); 64 | else 65 | msz = sz(~echoDim); 66 | if numel(msz)==1 67 | msz(2) = 1; 68 | end 69 | mask = squeeze(true(prod(msz,1))); 70 | end 71 | 72 | names = fieldnames(metrics); 73 | maps = cell([length(names) sz(~echoDim)]); 74 | 75 | % put the NE dim first 76 | idx = 1:numel(size(dset.(name))); 77 | if numel(idx) < length(dset.labels) 78 | idx(length(dset.labels)) = length(dset.labels); 79 | end 80 | data = permute(dset.(name),[idx(echoDim) idx(~echoDim)]); 81 | 82 | % linear index to all the vectors to process 83 | mask_idx = find(mask); 84 | 85 | % take the absolute value of complex data 86 | makereal = str2func('@(x) abs(x)'); 87 | if isreal(data) 88 | % if the data is already real, allow negative values to remain negative 89 | makereal = str2func('@(x) x'); 90 | end 91 | 92 | % split the calls to MERA into segments of size seg_sz 93 | nseg = ceil(numel(mask_idx)/seg_sz); 94 | metlen = zeros(size(names)); 95 | for seg=1:nseg 96 | fprintf('Processing segment %d of %d.\n',seg,nseg); 97 | 98 | % segment the mask 99 | segmask = (seg_sz*(seg-1)+1):min(seg_sz*seg,numel(mask_idx)); 100 | 101 | in.D = makereal(data(:,mask_idx(segmask))); 102 | 103 | % process the data in MERA 104 | [out,fout] = remmi.mse.MERA.MERA(in,fitting,manalysis); 105 | 106 | % compute all of the metrics required 107 | for m=1:length(names) 108 | % calculate the metric 109 | met = metrics.(names{m})(out); 110 | metlen(m) = max(size(met,1),metlen(m)); % save the size for later 111 | 112 | % store the maps for later 113 | maps(m,mask_idx(segmask)) = num2cell(met,1); 114 | end 115 | end 116 | 117 | % set the maps into the dataset, keeping proper dimensions 118 | t2set = struct(); 119 | for m=1:length(names) 120 | 121 | len = unique(cellfun(@length,maps(m,:))); 122 | 123 | if numel(len) > 2 124 | % the vectors in maps vary in length. The import is a bit involved 125 | maxlen = max(len); 126 | for n = 1:numel(maps(m,:)) 127 | if length(maps{m,n}) ~= maxlen 128 | maps{m,n}(maxlen,1) = 0; 129 | end 130 | end 131 | 132 | val = cell2mat(maps(m,:)); 133 | val = reshape(val,[metlen(m) sz(~echoDim)]); 134 | else 135 | % all the vectors in maps are the same length. Simple import 136 | 137 | % index to empty cells 138 | ix=cellfun(@isempty, maps(m,:)); 139 | 140 | % create matrix to hold the metric 141 | val = zeros([metlen(m) sz(~echoDim)]); 142 | val(:,~ix) = cell2mat(maps(m,:)); 143 | end 144 | 145 | nd = ndims(val); 146 | 147 | % rearrange 148 | if nd>1 149 | val = permute(val,[2:nd 1]); 150 | end 151 | 152 | % place into the return structure 153 | t2set.(names{m}) = val; 154 | end 155 | 156 | t2set.fitting = fout; 157 | t2set.metrics = metrics; 158 | t2set.labels = dset.labels(~echoDim); 159 | 160 | end 161 | -------------------------------------------------------------------------------- /+remmi/+mse/calcGeometricMean.m: -------------------------------------------------------------------------------- 1 | function gmT2 = calcGeometricMean(out) 2 | % sig = remmi.mse.calcGeometricMean(out) calculates the geometric mean T2 3 | % from a T2 spectrum 4 | % 5 | % out = output from remmi.mse.MERA.MERA(...) 6 | % 7 | % gmT2 = geometric mean T2 8 | % 9 | % example: 10 | % metrics.gmT2 = @remmi.mse.calcGeometricMean; 11 | % epg_struct = remmi.mse.mT2(img_struct,metrics); 12 | % 13 | % Kevin Harkins & Mark Does, Vanderbilt University 14 | % for the REMMI Toolbox 15 | 16 | lTv = log(out.Tv); 17 | lTv(isinf(lTv)) = 0; 18 | 19 | gmT2 = exp(sum(out.Fv.*lTv,1)./sum(out.Fv,1)); 20 | 21 | end -------------------------------------------------------------------------------- /+remmi/+mse/calcT2frac.m: -------------------------------------------------------------------------------- 1 | function frac = calcT2frac(out,lb,ub) 2 | % sig = remmi.mse.calcT2frac(out,lb,ub) calculates a T2 signal fraction 3 | % 4 | % out = output from remmi.mse.MERA.MERA(...) 5 | % lb,ub = lower and upper bounds on T2 peaks to include in the 6 | % calculation 7 | % 8 | % frac = fraction of signal exhibiting T2s between lb and ub 9 | % 10 | % example: 11 | % metrics.MWF = @(out) remmi.mse.calcT2frac(out,0.005,0.020); 12 | % epg_struct = remmi.mse.mT2(img_struct,metrics); 13 | % 14 | % Kevin Harkins & Mark Does, Vanderbilt University 15 | % for the REMMI Toolbox 16 | 17 | frac = sum(out.Fv.*(out.Tv>lb&out.Tv 1 32 | img = ifft(img,reconmatrix(3),3); 33 | end 34 | img = fftshift(fftshift(fftshift(img,1),2),3); 35 | 36 | end 37 | 38 | function dat = pad(dat,sz) 39 | % add zeros or remove data points so that dat is the size sz 40 | 41 | for n=1:length(sz) 42 | dsz = size(dat); 43 | 44 | if numel(dsz) < numel(sz) 45 | dsz(numel(sz)) = 1; 46 | end 47 | 48 | if dsz(n) < sz(n) 49 | % add zeros onto both sides 50 | dim = dsz; 51 | dim(n) = (sz(n)-dsz(n))/2; 52 | dat = cat(n,zeros(ceil(dim)),dat,zeros(floor(dim))); 53 | elseif dsz(n) > sz(n) 54 | % trim data from both sides. # of values to trim from both sides 55 | ntrim = (dsz(n) - sz(n))/2; 56 | 57 | %Set up dynamic indexing 58 | idx = cell(1, ndims(dat)); 59 | idx(:) = {':'}; 60 | idx(n) = {ceil(ntrim)+1:dsz(n)-floor(ntrim)}; 61 | 62 | dat = dat(idx{:}); 63 | end % else dsz(n) == sz(n). do nothing 64 | end 65 | 66 | end -------------------------------------------------------------------------------- /+remmi/+recon/options.m: -------------------------------------------------------------------------------- 1 | function opts = options(opts) 2 | % opts = remmi.recon.options(opts) sets default reconstruction options 3 | % 4 | % opts.apodize_fn = function to apodize fourier data 5 | % opts.matrix_sz = matrix size for reconstruction 6 | % 7 | % this function does not overwrite already set options 8 | % 9 | % Kevin Harkins & Mark Does, Vanderbilt University 10 | % for the REMMI Toolbox 11 | 12 | if ~exist('opts','var') 13 | opts = struct(); 14 | end 15 | 16 | if ~isfield(opts,'apodize_fn') || isempty(opts.apodize_fn) 17 | % by default, no apodization...? 18 | opts.apodize_fn = str2func('@(x) x'); 19 | 20 | %opts.apodize_fn = str2func('@(x)remmi.util.apodize(x,0.25)'); 21 | end 22 | 23 | if ~isfield(opts,'matrix_sz') 24 | opts.matrix_sz = []; 25 | end -------------------------------------------------------------------------------- /+remmi/+recon/pocs.m: -------------------------------------------------------------------------------- 1 | function [sig,img] = pocs(zf_sig,niter) 2 | % [sig,img] = pocs(zf_sig,niter) fills in zeros from 2d or 3d partial 3 | % aquired Fourier data using a low pass filter of the image phase 4 | % 5 | % zf_sig = partially acquried Fourier encoded signals. Unacquired signals 6 | % are = 0. The method assumes the first three dimensions are Fourier 7 | % encoded, and all other dimensions contain the same missing signals. 8 | % niter = number of iterations. Default is 50 9 | % 10 | % sig = k-space signal, with 0s filled in via the POCS algorithm 11 | % img = images reconstructed from these signals 12 | % 13 | % The iterative POCS algorithm is implemented as described in John Pauly's 14 | % notes, "Partial k-Space Recconstruction" dated Sept 26, 2007. 15 | % 16 | % by Kevin Harkins & Mark Does, Vanderbilt University 17 | % for the REMMI Toolbox 18 | 19 | if ~any(zf_sig==0) 20 | % this is not partial Fourier data, no need to move forward 21 | sig = zf_sig; 22 | return 23 | end 24 | 25 | if ~exist('niter','var') 26 | niter = 50; % default # of iterations 27 | end 28 | 29 | % If more than one set of image data is present, evaluate missing signals 30 | % from only the first image 31 | ksp = zf_sig(:,:,:,1); 32 | 33 | % where is the center of k-space? 34 | [~,i] = max(ksp(:)); 35 | idx = cell(ndims(ksp),1); 36 | [idx{:}] = ind2sub(size(ksp),i); 37 | 38 | % calculate the filter for the low-pass image based upon the distance from 39 | % the center of k-space to the un-acquired signals 40 | dist = bwdist(ksp==0); 41 | wnd = floor(dist(idx{:})); 42 | fn = wnd - abs(dist-wnd); 43 | fn(fn<0) = 0; 44 | lp_filt = sin(fn/wnd*pi/2); % sin filter 45 | 46 | % calculate the filter used to update k-space data for each iteration 47 | dist(dist>2*wnd) = 2*wnd; 48 | it_filt = dist/2/wnd;%sin(dist/wnd*pi/2); % ramp filter 49 | 50 | % calculate the zero-filled image 51 | zf_img = ift(zf_sig); 52 | 53 | % calculate the low pass image 54 | lp_sig = bsxfun(@times,zf_sig,lp_filt); 55 | lp_img = ift(lp_sig); 56 | 57 | % use the zero filed image as the inital guess 58 | img = zf_img; 59 | 60 | for n=1:niter 61 | % estimate the image as the magnitude of the high-pass homodyne 62 | % recon times the phase of the low-pass homodyne recon 63 | i2 = abs(img).*exp(1i*angle(lp_img)); 64 | i2fft = ft(i2); 65 | 66 | % combine estimated + acquired signals 67 | sig = bsxfun(@times,i2fft,1-it_filt) + bsxfun(@times,zf_sig,it_filt); 68 | img = ift(sig); 69 | end 70 | 71 | end 72 | 73 | function x = ift(x) 74 | % inverse fourier transform 75 | x = fftshift(fftshift(fftshift(x,1),2),3); 76 | x = ifft(ifft2(x),[],3); 77 | x = fftshift(fftshift(fftshift(x,1),2),3); 78 | end 79 | 80 | function x = ft(x) 81 | % fourier transform 82 | x = fftshift(fftshift(fftshift(x,1),2),3); 83 | x = fft(fft2(x),[],3); 84 | x = fftshift(fftshift(fftshift(x,1),2),3); 85 | end -------------------------------------------------------------------------------- /+remmi/+roi/copy.m: -------------------------------------------------------------------------------- 1 | function rois = copy(dest_set,src_set) 2 | % rois = remmi.roi.copy(dest_set,src_set) copys ROIs from src_set to dset_set 3 | % 4 | % dest_set.(strname) = data to process 5 | % dest_set.mask = mask for processing data 6 | % dest_set.labels = cell array of labels to dset.(strname) dimensions 7 | % 8 | % src_set.x{:} & str_set.yi{:} contain polygon coordinates for 9 | % previously drawn ROIs, provided by remmi.roi.draw() 10 | % src_set.roiopts contains options provided by remmi.roi.draw() 11 | % 12 | % Returns a data set containing reduced image data from the ROIs 13 | % 14 | % Kevin Harkins & Mark Does, Vanderbilt University 15 | % for the REMMI Toolbox 16 | 17 | % by default, ROIs are drawn from dset.img 18 | strname = src_set.roiopts.strname; 19 | roifun = src_set.roiopts.roifun; 20 | 21 | nROIs = src_set.roiopts.nROIs; 22 | data = dest_set.(strname); 23 | 24 | % what dimensions? 25 | roidim = ismember(dest_set.labels,src_set.roiopts.labels); 26 | if sum(roidim) ~= 2 27 | error('ROIs must be drawn in exactly two dimensions. Please specify labels'); 28 | end 29 | 30 | % rearrainge to make things easier 31 | sz =size(data); 32 | dims = 1:ndims(data); 33 | data = permute(data,[dims(roidim) dims(~roidim)]); 34 | sz = [sz(roidim) sz(~roidim)]; 35 | 36 | % set up the structure that will be returned 37 | rois = dest_set; 38 | rois.imgsize = [nROIs sz(~roidim)]; 39 | if numel(rois.imgsize) == 1 40 | rois.imgsize(2) = 1; 41 | end 42 | rois.(strname) = zeros(rois.imgsize); 43 | rois.labels = {'ROI',dest_set.labels{~roidim}}; 44 | rois.mask = true(nROIs,1); 45 | 46 | rois.xi = src_set.xi; 47 | rois.yi = src_set.yi; 48 | 49 | for n=1:nROIs 50 | bw = roipoly(dest_set.(strname),rois.xi{n},rois.yi{n}); 51 | 52 | for m=1:prod(sz(~roidim)) 53 | d = data(:,:,m); 54 | d = abs(d(bw)); 55 | rois.(strname)(n,m) = roifun(d); 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /+remmi/+roi/draw.m: -------------------------------------------------------------------------------- 1 | function rois = draw(dset,varargin) 2 | % rois = remmi.roi.draw(dset,options) prompts to draw ROIs on data in 3 | % dset 4 | % 5 | % dset.(strname) = data to process 6 | % dset.labels = cell array of labels to dset.img dimensions 7 | % 8 | % if dset or dset.(strname) is not given, default reconstruction and 9 | % thresholding methods are called 10 | % 11 | % options.nROIs = number of ROIs or a cell array of ROI names. 12 | % default = 1 13 | % 14 | % options.labels = a cell array of strings (with length 2) specifying 15 | % the two dimensions over which ROIs will be drawn. 16 | % default = {'RO','PE1'} 17 | % 18 | % options.strname = name of field in dset to process. Default is 'img' 19 | % 20 | % options.roifun = function handle on how to combine roi results. The 21 | % function must take a vector and return a single value. default = @mean 22 | % 23 | % Returns a data set containing reduced image data from the ROIs 24 | % 25 | % Kevin Harkins & Mark Does, Vanderbilt University 26 | % for the REMMI Toolbox 27 | 28 | options = setoptions(varargin{:}); 29 | 30 | data = dset.(options.strname); 31 | 32 | % which dimensions are we drawing ROIs? 33 | roidim = ismember(dset.labels,options.labels); 34 | sz =size(data); 35 | roidim = roidim(sz>1); 36 | 37 | % check that we are drawing ROIs for exactly 2 dimensions 38 | if sum( roidim & (sz>1) ) ~=2 39 | error('ROIs must be drawn in exactly two dimensions. Please specify labels') 40 | end 41 | 42 | % rearrainge to make things easier 43 | dims = 1:ndims(data); 44 | data = permute(data,[dims(roidim) dims(~roidim)]); 45 | sz = [sz(roidim) sz(~roidim)]; 46 | 47 | % what indexes are we drawing over? For now, just the slice with the 48 | % maximum signal. Is it possible to be smarter about this? Certainly... 49 | [~,idx] = max(abs(data(:))); 50 | slidx = cell(1,length(sz)); 51 | [slidx{:}] = ind2sub(sz,idx); 52 | slidx = slidx(~roidim); 53 | 54 | % set up the structure that will be returned 55 | rois = dset; 56 | rois.imgsize = [options.nROIs sz(~roidim)]; 57 | if numel(rois.imgsize) == 1 58 | rois.imgsize(2) = 1; 59 | end 60 | rois.(options.strname) = zeros(rois.imgsize); 61 | rois.labels = {'ROI',dset.labels{~roidim}}; 62 | rois.mask = true(options.nROIs,1); 63 | rois.roiopts = options; 64 | 65 | hf = singlefig(); 66 | for n=1:options.nROIs 67 | figure(hf) 68 | imagesc(abs(data(:,:,slidx{:}))); 69 | colormap('gray'); 70 | axis('image','off'); 71 | colorbar(); 72 | title(['Draw ROI: ' num2str(options.ROIs{n})]); 73 | 74 | [bw,xi,yi] = roipoly(); 75 | 76 | rois.xi{n} = xi; 77 | rois.yi{n} = yi; 78 | 79 | for m=1:prod(sz(~roidim)) 80 | d = data(:,:,m); 81 | d = abs(d(bw)); 82 | rois.(options.strname)(n,m) = options.roifun(d); 83 | end 84 | end 85 | 86 | end 87 | 88 | function hf = singlefig() 89 | % allows ROIs to be drawn over the same figure on subsequent calls of 90 | % remmi.roi.draw 91 | 92 | persistent handle 93 | 94 | if isempty(handle) || ~ishandle(handle) 95 | handle = figure(); 96 | end 97 | 98 | hf = handle; 99 | 100 | end 101 | 102 | function opts = setoptions(opts) 103 | 104 | if ~exist('opts','var') || ~isstruct(opts) 105 | opts = struct(); 106 | end 107 | 108 | if ~isfield(opts,'nROIs') || isempty(opts.nROIs) 109 | opts.nROIs = 1; 110 | end 111 | 112 | if iscell(opts.nROIs) 113 | opts.ROIs = opts.nROIs; 114 | opts.nROIs = numel(opts.ROIs); 115 | else 116 | opts.ROIs = cellfun(@num2str,num2cell(1:opts.nROIs),'UniformOutput',false); 117 | end 118 | 119 | % by default, draw ROIs in the RO & PE1 directions 120 | if ~isfield(opts,'labels') || isempty(opts.labels) 121 | opts.labels = {'RO','PE1'}; 122 | end 123 | 124 | % by default, ROIs are drawn from dset.img 125 | if ~isfield(opts,'strname') || isempty(opts.strname) 126 | opts.strname = 'img'; 127 | end 128 | 129 | if ~isfield(opts,'roifun') || isempty(opts.roifun) 130 | opts.roifun = @mean; 131 | end 132 | 133 | end -------------------------------------------------------------------------------- /+remmi/+util/apodize.m: -------------------------------------------------------------------------------- 1 | function sf = apodize(s,alpha) 2 | % sf = apodize(s,alpha) is 1D, 2D and 3D tukey apodization 3 | % s = data to be apodized 4 | % alpha = ratio of the length of the taper to the total length of the 5 | % window 6 | % sf = apodized data 7 | % 8 | % by Kevin Harkins & Mark Does, Vanderbilt University 9 | % for the REMMI Toolbox 10 | 11 | if ~exist('alpha','var'), 12 | alpha = 0.5; 13 | end 14 | 15 | % window in each of the three dimensions 16 | win1 = tukeywindow(size(s,1),alpha); 17 | win2 = tukeywindow(size(s,2),alpha)'; 18 | win3 = reshape(tukeywindow(size(s,3),alpha),1,1,[]); 19 | 20 | sf = bsxfun(@times,bsxfun(@times,bsxfun(@times,s,win1),win2),win3); 21 | 22 | end 23 | 24 | function win = tukeywindow(sz,r) 25 | 26 | n = round(r*sz/2); 27 | 28 | win = ones(sz,1); 29 | win(1:n) = 0.5*(1-cos(pi*(0:n-1)/n)); 30 | win(end:-1:end-n+1) = 0.5*(1-cos(pi*(0:n-1)/n)); 31 | 32 | end -------------------------------------------------------------------------------- /+remmi/+util/githash.m: -------------------------------------------------------------------------------- 1 | function hash = githash() 2 | % hash = githash() 3 | % 4 | % returns the short hash for the current git commited version of REMMI. 5 | % A warning is thrown if no hash is found 6 | % 7 | % Kevin Harkins & Mark Does, Vanderbilt University 8 | % for the REMMI Toolbox 9 | 10 | rpath = which('remmi'); 11 | rpath = fileparts(rpath); 12 | 13 | cmd = ['git --git-dir ' fullfile(rpath,'.git')]; 14 | cmd = [cmd ' rev-parse --short HEAD']; 15 | 16 | [stat,hash] = system(cmd); 17 | 18 | if stat 19 | warning('You are not using REMMI under version control.'); 20 | hash = '0000000'; 21 | end -------------------------------------------------------------------------------- /+remmi/+util/linfitfast.m: -------------------------------------------------------------------------------- 1 | function par = linfitfast(x,y,w) 2 | 3 | M = [x, ones(size(x))]; 4 | par = (M'*diag(w)*M)\(M'*diag(w)*y); -------------------------------------------------------------------------------- /+remmi/+util/mp_denoise.m: -------------------------------------------------------------------------------- 1 | function [denoised,S2,P] = mp_denoise(dset,win_size,name) 2 | % function [denoised,S2,P] = mp_denoise(image,win_size,name) 3 | % 4 | % INPUT: 5 | % dset.(name) = images to be denoised. The first dimensions must 6 | % discriminate between pixels, while the last dimension corresponds 7 | % to encoding variation (diffusion, echo time, inverion-time, or other). 8 | % Thus, could be structured as [X,Y,Z,N] or [X,Y,N]. 9 | % dset.mask = mask for processing data 10 | 11 | % win_size = the size of the sliding window for PCA analysis for each 12 | % image dimension (e.g., [5 5 5] for a 3D image. 13 | % If not specified, the default is Nw*ones(1,Nd), where 14 | % Nd = ndims(img)-1; dimension of the image, and 15 | % Nw = ceil(size(dset.images.img,Nd+1).^(1/Nd)); 16 | % name = name of field in dset to fit. Default is 'img' 17 | % 18 | % OUTPUT: denoisedImage - contains denoised image with same structure as 19 | % input. 20 | % 21 | % S2 - contains estimate of variance in each pixel. 22 | % 23 | % P - specifies the found number of principal components. 24 | % 25 | % CREDIT: 26 | % Denoising implementation by Jonas Olesen and Sune Jespersen for diffusion 27 | % MRI data based on the algorithm presented by Veraart et al. (2016) 142, p 28 | % 394-406 https://doi.org/10.1016/j.neuroimage.2016.08.016. 29 | % 30 | % MDD minor modifications 31 | % - to remove mean across voxels (compute principal components of 32 | % the covariance not correlation matrix) 33 | % - to use MATLAB's svd--slightly slower but more accurate 34 | % - to conform to remmi conventions 35 | 36 | 37 | %% check and set variables 38 | 39 | if ~exist('name','var') || isempty(name) 40 | name = 'img'; 41 | end 42 | 43 | img = dset.(name); 44 | 45 | Nd = ndims(img)-1; % dimension of the image, 46 | % If images is 2D, extend to 3D with size of 3rd dim = 1 47 | if Nd==2 48 | img = permute(img,[1,2,4,3]); 49 | end 50 | 51 | image_size = size(img,1:3); 52 | M = size(img,4); % length of contrast encoding dimension 53 | Nw = ceil(M.^(1/Nd)); % default size of moving window along each dim 54 | 55 | if ~exist('win_size','var') || isempty(win_size) 56 | win_size = Nw*ones(1,Nd); 57 | else % check win_size 58 | assert((length(win_size)==Nd),... 59 | 'length of win_size must match dim of images') 60 | assert(all(win_size>0),'window values must be strictly positive') 61 | assert(all(win_size<=image_size),... 62 | 'window values exceed image dimensions') 63 | end 64 | 65 | if length(win_size)==2 66 | win_size(3) = 1; 67 | end 68 | 69 | N = prod(win_size); 70 | 71 | if isfield(dset,'mask') 72 | mask = dset.mask; 73 | else 74 | disp('No mask') 75 | mask = []; 76 | end 77 | 78 | 79 | 80 | %% denoise image 81 | denoised = zeros([image_size,M]); 82 | P = zeros(image_size); 83 | S2 = zeros(image_size); 84 | counter = zeros(image_size); 85 | m = image_size(1)-win_size(1)+1; 86 | n = image_size(2)-win_size(2)+1; 87 | o = image_size(3)-win_size(3)+1; 88 | 89 | for index = 1:m*n*o 90 | k = floor((index-1)/m/n)+1; 91 | j = floor((index-1-(k-1)*m*n)/m)+1; 92 | i = index-(k-1)*m*n-(j-1)*m; 93 | rows = i:i-1+win_size(1); 94 | cols = j:j-1+win_size(2); 95 | slis = k:k-1+win_size(3); 96 | % Check mask 97 | maskCheck = reshape(mask(rows,cols,slis),[N 1])'; 98 | if all(~maskCheck), continue, end 99 | 100 | % Create X data matrix 101 | X = reshape(img(rows,cols,slis,:),[N M])'; 102 | 103 | % Remove voxels not contained in mask 104 | X(:,~maskCheck) = []; 105 | if size(X,2)==1, continue, end % skip if only one voxel of window in mask 106 | % Perform denoising 107 | newX=zeros(M,N); sigma2=zeros(1,N); p=zeros(1,N); 108 | [newX(:,maskCheck),sigma2(maskCheck),p(maskCheck)] = denoiseMatrix(X); 109 | 110 | % Assign newX to correct indices in denoisedImage 111 | denoised(rows,cols,slis,:) = denoised(rows,cols,slis,:) ... 112 | + reshape(newX',[win_size M]); 113 | P(rows,cols,slis) = P(rows,cols,slis) + reshape(p,win_size); 114 | S2(rows,cols,slis) = S2(rows,cols,slis) + reshape(sigma2,win_size); 115 | counter(rows,cols,slis) = counter(rows,cols,slis)+1; 116 | end 117 | skipCheck = mask & counter==0; 118 | counter(counter==0) = 1; 119 | denoised = bsxfun(@rdivide,denoised,counter); 120 | P = bsxfun(@rdivide,P,counter); 121 | S2 = bsxfun(@rdivide,S2,counter); 122 | 123 | 124 | %% adjust output to match input dimensions 125 | % Assign original data to denoisedImage outside of mask and at skipped voxels 126 | original = bsxfun(@times,img,~mask); 127 | denoised = denoised + original; 128 | original = bsxfun(@times,img,skipCheck); 129 | denoised = denoised + original; 130 | 131 | % Shape denoisedImage as orginal image 132 | if Nd==2 133 | denoised = reshape(denoised,[image_size(1:2),M]); 134 | S2 = reshape(S2,image_size(1:2)); 135 | P = reshape(P,image_size(1:2)); 136 | end 137 | 138 | end 139 | 140 | function [newX,sigma2,p] = denoiseMatrix(X) 141 | % helper function to denoise.m 142 | % Takes as input matrix X with dimension MxN with N corresponding to the 143 | % number of pixels and M to the number of data points. The output consists 144 | % of "newX" containing a denoised version of X, "sigma2" an approximation 145 | % to the data variation, "p" the number of signal carrying components. 146 | 147 | [M,N] = size(X); 148 | minMN = min([M N]); 149 | Xm = mean(X,2); % MDD added Jan 2018; mean added back to signal below; 150 | X = X-Xm; 151 | % [U,S,V] = svdecon(X); MDD replaced with MATLAB svd vvv 3Nov2017 152 | [U,S,V] = svd(X,'econ'); 153 | 154 | lambda = diag(S).^2/N; 155 | 156 | p = 0; 157 | pTest = false; 158 | scaling = (M-(0:minMN))/N; 159 | scaling(scaling<1) = 1; 160 | while ~pTest 161 | sigma2 = (lambda(p+1)-lambda(minMN))/(4*sqrt((M-p)/N)); 162 | pTest = sum(lambda(p+1:minMN))/scaling(p+1) >= (minMN-p)*sigma2; 163 | if ~pTest, p = p+1; end 164 | end 165 | sigma2 = sum(lambda(p+1:minMN))/(minMN-p)/scaling(p+1); 166 | 167 | newX = U(:,1:p)*S(1:p,1:p)*V(:,1:p)'+Xm; 168 | 169 | end 170 | 171 | 172 | -------------------------------------------------------------------------------- /+remmi/+util/selectexp.m: -------------------------------------------------------------------------------- 1 | function exps = selectexp(study) 2 | % exps = selectexp(study) 3 | % provide a basic UI to select experiments contained in study 4 | % 5 | % study = char array containg the path to a valid study or a vendor 6 | % specific study class 7 | % 8 | % exps = list of selected experiments within the study 9 | % 10 | % Kevin Harkins & Mark Does, Vanderbilt University 11 | % for the REMMI Toolbox 12 | 13 | if ischar(study) 14 | study = remmi.vendors.autoVendor(study); 15 | end 16 | 17 | disp('Select the experiment(s)'); 18 | exps = study.list(); 19 | sel = listdlg('ListString',exps.name); 20 | exps = exps.id(sel); -------------------------------------------------------------------------------- /+remmi/+util/slice.m: -------------------------------------------------------------------------------- 1 | function out = slice(in,dim,i,name) 2 | % out = slice(in,dim,i,name) dyanmic slicing of input matrix 3 | % 4 | % slice(in,dim,i,name) 5 | % in = input matrix, or structure 6 | % dim = index or name of thedimension to be sliced 7 | % i = scalar in dim to be included in the output 8 | % name = if 'in' is a structure, this is the character string of the 9 | % field to be sliced. by defalt, name = 'img' 10 | % 11 | % 12 | % Kevin Harkins & Mark Does, Vanderbilt University 13 | % for the REMMI Toolbox 14 | 15 | if isstruct(in) 16 | if ischar(dim) 17 | dim = {dim}; 18 | end 19 | 20 | if iscell(dim) 21 | dim = find(ismember(in.labels,dim)); 22 | end 23 | 24 | if ~exist('name','var') || isempty(name) 25 | name = 'img'; 26 | end 27 | 28 | data = in.(name); 29 | else 30 | data = in; 31 | end 32 | 33 | % set up dynamic indexing 34 | idx = cell(1, ndims(data)); 35 | idx(:) = {':'}; 36 | idx(dim) = {i}; 37 | 38 | data = data(idx{:}); 39 | 40 | if isstruct(in) 41 | out = in; 42 | catidx = 1:ndims(data); 43 | out.(name) = permute(data,[catidx(catidx~=dim) catidx(catidx==dim)]); 44 | out.labels = out.labels(catidx(catidx~=dim)); 45 | out.imgsize = size(out.(name)); 46 | else 47 | out = data; 48 | end -------------------------------------------------------------------------------- /+remmi/+util/snrCalc.m: -------------------------------------------------------------------------------- 1 | function snr = snrCalc(img) 2 | % snr = snrCalc(img) estimates SNR on a 2D magntidue image after asking 3 | % for ROIs in regions in signal and in noise. 4 | % 5 | % by Kevin Harkins & Mark Does, Vanderbilt University 6 | % for the REMMI Toolbox 7 | 8 | % Display image 9 | figure(); 10 | imagesc(abs(img)); 11 | colormap jet 12 | colorbar 13 | axis image off 14 | 15 | % draw signal ROI 16 | title('Draw signal ROI','Fontsize',20); 17 | disp('Draw signal ROI'); 18 | sig = roipoly(); 19 | 20 | % draw noise ROI 21 | disp('Draw noise ROI') 22 | title('Draw noise ROI','Fontsize',20); 23 | noise = roipoly(); 24 | 25 | snr = mean(abs(img(sig)))/mean(abs(img(noise)))*sqrt(pi/2); 26 | 27 | disp(['SNR = ' num2str(snr)]); -------------------------------------------------------------------------------- /+remmi/+util/strsplit.m: -------------------------------------------------------------------------------- 1 | function splitstr = strsplit(str,delim) 2 | % splitstr = strsplit(str,delim) 3 | % 4 | % Since Matlab's strsplit was introduced in R2013a, this version is 5 | % included to improve backwards compatbility for REMMI. 6 | % 7 | % Kevin Harkins & Mark Does, Vanderbilt University 8 | % for the REMMI Toolbox 9 | 10 | splitstr = regexp(str,regexptranslate('escape',delim),'split'); 11 | 12 | end -------------------------------------------------------------------------------- /+remmi/+util/thresholdmask.m: -------------------------------------------------------------------------------- 1 | function dset = thresholdmask(dset,varargin) 2 | % dset = remmi.util.thresholdmask(dset,val,label,name) 3 | % adds a threshold mask to dset 4 | % 5 | % dset = structure or remmi data containing: 6 | % dset.(name) = image data 7 | % dset.labels = cell array of labels to dset.img dimensions 8 | % val = relative threshold value as a fraction of 9 | % max(abs(dset.(name)(:))). Default is 0.1 10 | % label = cell array of labels to create the mask from. Default is 11 | % {'RO','PE1','PE2'} 12 | % name = name of field in dset to threshold. Default is 'img' 13 | % 14 | % output: dset contains all of the original contents, plus dset.mask 15 | % 16 | % Kevin Harkins & Mark Does, Vanderbilt University 17 | % for the REMMI Toolbox 18 | 19 | [val,label,name] = setoptions(varargin{:}); 20 | 21 | if ~exist('dset','var') 22 | dset = struct(); 23 | end 24 | 25 | if ~isfield(dset,name) || isempty(dset.(name)) 26 | dset = remmi.recon(dset); 27 | end 28 | 29 | % which dimension(s) to slice? 30 | i = ~ismember(dset.labels,label); 31 | img = remmi.util.slice(dset.(name),i,1); 32 | 33 | % create a threshold mask 34 | dset.mask = abs(img)./max(abs(img(:))) > val; 35 | 36 | end 37 | 38 | function [val,label,name] = setoptions(val,label,name) 39 | 40 | if ~exist('val','var') || isempty(val) 41 | val = 0.1; 42 | end 43 | if ~exist('label','var') || isempty(label) 44 | label = {'RO','PE1','PE2','NS'}; 45 | end 46 | if ~exist('name','var') || isempty(name) 47 | name = 'img'; 48 | end 49 | 50 | end -------------------------------------------------------------------------------- /+remmi/+vendors/Agilent.m: -------------------------------------------------------------------------------- 1 | classdef Agilent < handle 2 | % Agilent is a class used to identify and reconstruct Agilent 3 | % VNMRJ datasets and images. 4 | properties 5 | path 6 | labels= {'RO','PE1','PE2','NE','NS','DW','IR','MT','NR'}; 7 | end 8 | 9 | methods(Static) 10 | function bool = isValid(spath) 11 | bool = exist(fullfile(spath,'study.xml'),'file') == 2; 12 | end 13 | 14 | function time = parsetime(val) 15 | try 16 | time = datenum(val,'yyyymmddTHHMMSS'); 17 | catch 18 | warning(['Could not parse time: ' val]); 19 | time = 0; 20 | end 21 | end 22 | end 23 | 24 | methods 25 | function obj = Agilent(spath) 26 | % initiates a study 27 | 28 | % ask for a directory if one isn't given 29 | if exist('spath','var') ~= 1 || isempty(spath) 30 | obj.path = uigetdir('Pick a study'); 31 | else 32 | obj.path = spath; 33 | end 34 | 35 | % is this a valid study? 36 | if ~obj.isValid(spath) 37 | warning('Study is not valid: %s',spath); 38 | end 39 | end 40 | 41 | function studies = list(obj) 42 | % find a list of all the experiments in this study, and sort 43 | % them by experiment ime 44 | % 45 | % the function returns a structure of study experiment names 46 | % and ids to identify each experiment. 47 | exps = dir(obj.path); 48 | time = zeros(size(exps))-1; 49 | sid = {exps.name}; 50 | name = cell(size(sid)); 51 | for n=1:length(exps) 52 | try 53 | acqpars = remmi.vendors.parsAgilent(fullfile(obj.path,sid{n},'procpar')); 54 | time(n) = remmi.vendors.Agilent.parsetime(acqpars.time_complete); 55 | name{n} = sid{n}; 56 | catch 57 | end 58 | end 59 | 60 | % remove experiments that didn't finish 61 | mask = time>=0; 62 | sid = sid(mask); 63 | name = name(mask); 64 | time = time(mask); 65 | 66 | % sort by completion time 67 | [time,i] = sort(time); 68 | sid = sid(i); 69 | name = name(i); 70 | 71 | % if experiments have the same name, they are pre-scans. Take 72 | % the last one 73 | [name,idx] = unique(name,'last'); 74 | sid = sid(idx); 75 | time = time(idx); 76 | 77 | % the order was sorted by 'unique'. Put it back in it's 78 | % original order 79 | [~,i] = sort(time); 80 | name = name(i); 81 | sid = sid(i); 82 | 83 | % set up the return structure 84 | studies = struct; 85 | studies.name = name; 86 | studies.id = sid; 87 | end 88 | 89 | function pars = loadPars(obj,exp) 90 | expPath = fullfile(obj.path,exp); 91 | 92 | % load parameter files 93 | procpath = fullfile(expPath,'procpar'); 94 | procpar = remmi.vendors.parsAgilent(procpath); 95 | 96 | % set the basic remmi experimental parameters 97 | % echo time 98 | if isfield(procpar,'TE') 99 | pars.te = procpar.TE(:)/1000; % s 100 | else 101 | pars.te = procpar.te(:); % s 102 | end 103 | pars.nte = numel(pars.te); 104 | 105 | % mess sequences enocdes the echo time differently 106 | if strcmpi(strtrim(procpar.seqfil),'mess') 107 | pars.te = [procpar.te1 (1:procpar.ne-procpar.ne_late-1)*procpar.te]; 108 | pars.te = [pars.te pars.te(end)+(1:procpar.ne_late)*.050]'; 109 | end 110 | 111 | % TR 112 | pars.tr = procpar.tr(:); % s 113 | 114 | % FOV 115 | if procpar.nD == 3 116 | pars.fov = [procpar.lro procpar.lpe procpar.lpe2]'*10; % mm 117 | else 118 | pars.fov = [procpar.lro*10 procpar.lpe*10 procpar.sl]'; % mm 119 | end 120 | 121 | if isfield(procpar,'ti') 122 | pars.ti = procpar.ti; 123 | end 124 | if isfield(procpar,'td') 125 | pars.td = procpar.td; 126 | end 127 | 128 | % % Load parameters if this is an MTIR dataset 129 | % if isfield(procpar,'REMMI_MtIrOnOff') && ... 130 | % strcmp(procpar.REMMI_MtIrOnOff,'Yes'); 131 | % if isfield(procpar,'REMMI_MtIrTimeArr') 132 | % pars.ti = procpar.REMMI_MtIrTimeArr(:)/1000; % s 133 | % elseif isfield(procpar,'InversionTime') 134 | % pars.ti = procpar.InversionTime(:)/1000; % s 135 | % end 136 | % 137 | % % TD, if exists 138 | % if isfield(procpar,'REMMI_MtTDTime') 139 | % pars.td = procpar.REMMI_MtTDTime(:)/1000; % s 140 | % elseif isfield(procpar,'RepetitionDelayTime') 141 | % pars.td = procpar.RepetitionDelayTime(:)/1000; % s 142 | % end 143 | % end 144 | 145 | % vendor and sequence 146 | pars.vendor = 'Agilent'; 147 | pars.sequence = procpar.seqfil; 148 | 149 | % time the data was acquired 150 | val = procpar.time_complete; 151 | if ~strcmp(val,'na') 152 | val = remmi.vendors.Agilent.parsetime(val); 153 | end 154 | pars.time = val; 155 | pars.timestr = datestr(val); 156 | 157 | % store all the other parameters, in case someone needs them 158 | pars.procpar = procpar; 159 | end 160 | 161 | function [img,labels,pars] = load(obj,exp,opts) 162 | % load parameters 163 | pars = obj.loadPars(exp); 164 | 165 | % load images 166 | expPath = fullfile(obj.path,exp); 167 | raw = remmi.vendors.loadAgilent(expPath,pars.procpar); 168 | img = remmi.recon.ft(raw,opts); 169 | 170 | sz = size(img); 171 | if size(img,length(obj.labels))==1 172 | sz(length(obj.labels)) = 1; 173 | end 174 | 175 | labels = obj.labels(sz>1); 176 | img = squeeze(img); 177 | end 178 | end 179 | end -------------------------------------------------------------------------------- /+remmi/+vendors/BrukerPV.m: -------------------------------------------------------------------------------- 1 | classdef BrukerPV < handle 2 | % BrukerPV is a class used to identify and reconstruct Bruker 3 | % Paravision datasets and images. 4 | properties 5 | path 6 | labels= {'RO','PE1','PE2','NE','NC','NS','DW','IR','MT','BS','NR'}; 7 | end 8 | 9 | methods(Static) 10 | function bool = isValid(spath) 11 | bool = exist(fullfile(spath,'subject'),'file') == 2; 12 | end 13 | 14 | function time = parsetime(val) 15 | try 16 | time = datenum(val,'HH:MM:SSddmmmyyyy'); 17 | catch 18 | try 19 | i1 = strfind(val,'<'); 20 | i2 = strfind(val,'>'); 21 | val = remmi.util.strsplit(val(i1+1:i2-1),','); 22 | time = datenum(val{1},'yyyy-mm-ddTHH:MM:SS'); 23 | catch 24 | time = 0; 25 | end 26 | end 27 | end 28 | end 29 | 30 | methods 31 | function obj = BrukerPV(spath) 32 | % initiates a study 33 | 34 | % ask for a directory if one isn't given 35 | if exist('spath','var') ~= 1 || isempty(spath) 36 | obj.path = uigetdir('Pick a study'); 37 | else 38 | obj.path = spath; 39 | end 40 | 41 | % is this a valid study? 42 | if ~obj.isValid(spath) 43 | warning('Study is not valid: %s',spath); 44 | end 45 | end 46 | 47 | function studies = list(obj) 48 | % find a list of all the experiments in this study, and sort 49 | % them by experiment ime 50 | % 51 | % the function returns a structure of study experiment names 52 | % and ids to identify each experiment. 53 | exps = dir(obj.path); 54 | time = zeros(size(exps))-1; 55 | sid = {exps.name}; 56 | name = cell(size(sid)); 57 | for n=1:length(exps) 58 | try 59 | acqpars = remmi.vendors.parsBruker(fullfile(obj.path,sid{n},'acqp')); 60 | time(n) = acqpars.ACQ_abs_time(1); %remmi.vendors.BrukerPV.parsetime(acqpars.ACQ_time); 61 | if acqpars.ACQ_sw_version(4) == '5' 62 | name{n} = [acqpars.ACQ_scan_name ' (E' sid{n} ')']; 63 | else 64 | name{n} = acqpars.ACQ_scan_name; 65 | end 66 | catch 67 | end 68 | end 69 | 70 | % remove experiments that didn't finish 71 | mask = time>=0; 72 | sid = sid(mask); 73 | name = name(mask); 74 | time = time(mask); 75 | 76 | % sort by completion time 77 | [time,i] = sort(time); 78 | sid = sid(i); 79 | name = name(i); 80 | 81 | % if experiments have the same name, they are pre-scans. Take 82 | % the last one 83 | [name,idx] = unique(name,'last'); 84 | sid = sid(idx); 85 | time = time(idx); 86 | 87 | % the order was sorted by 'unique'. Put it back in it's 88 | % original order 89 | [~,i] = sort(time); 90 | name = name(i); 91 | sid = sid(i); 92 | 93 | % set up the return structure 94 | studies = struct; 95 | studies.name = name; 96 | studies.id = sid; 97 | end 98 | 99 | function pars = loadPars(obj,exp) 100 | expPath = fullfile(obj.path,exp); 101 | 102 | % load parameter files 103 | methpath = fullfile(expPath,'method'); 104 | methpars = remmi.vendors.parsBruker(methpath); 105 | 106 | acqpath = fullfile(expPath,'acqp'); 107 | acqpars = remmi.vendors.parsBruker(acqpath); 108 | 109 | % set the basic remmi experimental parameters 110 | % echo time 111 | if isfield(methpars,'EffectiveTE') 112 | pars.te = methpars.EffectiveTE(:)/1000; % s 113 | else 114 | pars.te = methpars.PVM_EchoTime(:)/1000; % s 115 | end 116 | pars.nte = methpars.PVM_NEchoImages; 117 | 118 | % TR 119 | pars.tr = methpars.PVM_RepetitionTime(:)/1000; % s 120 | 121 | % FOV 122 | pars.fov = methpars.PVM_Fov; % mm 123 | 124 | % Load parameters if this is an MTIR dataset 125 | if isfield(methpars,'REMMI_MtIrOnOff') && ... 126 | strcmp(methpars.REMMI_MtIrOnOff,'Yes'); 127 | if isfield(methpars,'REMMI_MtIrTimeArr') 128 | pars.ti = methpars.REMMI_MtIrTimeArr(:)/1000; % s 129 | elseif isfield(methpars,'InversionTime') 130 | pars.ti = methpars.InversionTime(:)/1000; % s 131 | end 132 | 133 | % TD, if exists 134 | if isfield(methpars,'REMMI_MtTDTime') 135 | pars.td = methpars.REMMI_MtTDTime(:)/1000; % s 136 | elseif isfield(methpars,'RepetitionDelayTime') 137 | pars.td = methpars.RepetitionDelayTime(:)/1000; % s 138 | end 139 | end 140 | 141 | % for offset MT sequences, what is the range in frequence 142 | % offsets, and rf power? 143 | if isfield(methpars,'PVM_MagTransOnOff') 144 | if strcmp(methpars.PVM_MagTransOnOff,'On') 145 | pars.mt_offset = methpars.PVM_MagTransFL; 146 | pars.mt_power = methpars.PVM_MagTransPower; 147 | end 148 | end 149 | 150 | % vendor and sequence 151 | pars.vendor = 'Bruker'; 152 | pars.sequence = methpars.Method; 153 | 154 | % time the data was acquired 155 | pars.time = datetime(acqpars.ACQ_abs_time,'convertfrom','posixtime'); 156 | 157 | % store all the other parameters, in case someone needs them 158 | pars.methpars = methpars; 159 | pars.acqpars = acqpars; 160 | end 161 | 162 | function [img,labels,pars] = load(obj,exp,opts) 163 | % load parameters 164 | pars = obj.loadPars(exp); 165 | 166 | % load images 167 | expPath = fullfile(obj.path,exp); 168 | if contains(pars.methpars.Method,'remmiGRASE')%strcmp(pars.methpars.Method,'') 169 | % do the GRASE reconstruction 170 | raw = remmi.vendors.loadBrukerGrase(expPath,pars.methpars,pars.acqpars); 171 | else 172 | raw = remmi.vendors.loadBruker(expPath,pars.methpars); 173 | end 174 | 175 | % partial fourier acquisitions have a large percent of 176 | % signals==0 177 | if sum(raw(:)==0)/numel(raw)>0.01 178 | % this is a partial fourier acquisition. Fill in with POCS 179 | raw = remmi.recon.pocs(raw); 180 | end 181 | 182 | img = remmi.recon.ft(raw,opts); 183 | 184 | % coil combinations 185 | coil_idx = find(ismember(obj.labels,{'NC'})); 186 | if size(img,coil_idx) > 1 187 | % for now, just sum of squares coil combination 188 | img = sqrt(sum(img.*conj(img),coil_idx)); 189 | end 190 | 191 | sz = size(img); 192 | if size(img,length(obj.labels))==1 193 | sz(length(obj.labels)) = 1; 194 | end 195 | 196 | labels = obj.labels(sz>1); 197 | img = squeeze(img); 198 | end 199 | 200 | function [img,labels,pars] = loadImg(obj,exp1,opts) 201 | % load parameters 202 | pars = obj.loadPars(exp1); 203 | 204 | % custom hack for PE reversed EPI 205 | rid = '1'; 206 | if exist('opts','var') 207 | if isfield(opts,'id') 208 | rid = opts.id; 209 | end 210 | end 211 | 212 | pars.recopars = remmi.vendors.parsBruker(fullfile(obj.path,exp1,... 213 | 'pdata',rid,'reco')); 214 | 215 | fileName = fullfile(obj.path,exp1,'pdata',rid,'2dseq'); 216 | fid=fopen(fileName); 217 | if fid < 0 218 | error('cannot open %s',fileName) 219 | end 220 | % 2dseq is formated 16 bit signed integer 221 | raw=fread(fid,'int16'); 222 | fclose(fid); 223 | 224 | img = reshape(raw,pars.recopars.RECO_size(1),... 225 | pars.recopars.RECO_size(2),... 226 | pars.recopars.RecoObjectsPerRepetition,[]); 227 | 228 | if isfield(pars.methpars,'PhaseEncodeReversed') 229 | if isfield(pars.methpars,'PVM_EffPhase1Offset') 230 | if strcmp(pars.methpars.PhaseEncodeReversed,'Yes') 231 | proj = fftshift(fft(fftshift(img,2),[],2),2); 232 | % reverse the PE2 direction x2 233 | np = pars.methpars.PVM_Matrix(2); 234 | line = reshape((1:np) - 1 - round(np/2),1,[]); 235 | ph1_offset = reshape(pars.methpars.PVM_EffPhase1Offset,1,1,[]); 236 | phroll = exp(2*1i*2*pi*bsxfun(@times,line,ph1_offset)/pars.methpars.PVM_Fov(2)); 237 | proj = bsxfun(@times,proj,phroll); 238 | 239 | img = abs(ifftshift(fft(ifftshift(proj,2),[],2),2)); 240 | end 241 | end 242 | end 243 | 244 | img = squeeze(img); 245 | labels = {}; % no labels yet 246 | end 247 | end 248 | end -------------------------------------------------------------------------------- /+remmi/+vendors/autoVendor.m: -------------------------------------------------------------------------------- 1 | function loader = autoVendor(spath) 2 | % loader = autoVendor(spath) 3 | % 4 | % spath = the path name to the study being analyzed 5 | % loader = a vendor-specific class to load image & parameter set 6 | % 7 | % Kevin Harkins & Mark Does, Vanderbilt University 8 | % for the REMMI Toolbox 9 | 10 | loader = []; 11 | 12 | if ~exist(spath,'dir') 13 | error('Directory does not exist: %s',spath); 14 | elseif remmi.vendors.BrukerPV.isValid(spath) 15 | loader = remmi.vendors.BrukerPV(spath); 16 | elseif remmi.vendors.Agilent.isValid(spath) 17 | loader = remmi.vendors.Agilent(spath); 18 | elseif false 19 | % todo: add new vendors 20 | end 21 | 22 | if isempty(loader) 23 | error('Vendor data format not recognized: %s', spath); 24 | end 25 | -------------------------------------------------------------------------------- /+remmi/+vendors/bmatAgilent.m: -------------------------------------------------------------------------------- 1 | function bm = bmatAgilent(pp) 2 | 3 | % for now, just use the bmatrix provided by procpar 4 | bm = [pp.bvalrr pp.bvalpp pp.bvalss -pp.bvalrp -pp.bvalsp pp.bvalrs]; 5 | bm(:,7) = 1000; -------------------------------------------------------------------------------- /+remmi/+vendors/bmatBruker.m: -------------------------------------------------------------------------------- 1 | function bm = bmatBruker(pp) 2 | 3 | 4 | dt = .012; % time resolution, ms 5 | gamma = 42.576; % MHz/T 6 | te = pp.te(1)*1000; % ms 7 | tm = 0; % ms 8 | gmax = pp.methpars.PVM_GradCalConst/gamma; % mT/m 9 | 10 | % # of B0 images 11 | nB0 = pp.methpars.PVM_DwAoImages; 12 | nB0end = pp.methpars.REMMI_DwAoImagesEnd; 13 | nB0begin = nB0 - nB0end; 14 | 15 | ref = remmi.util.strsplit(pp.methpars.RefPulse1(2:end-1),','); 16 | refPulseDur = str2double(ref{1}); 17 | tssr = 2*pp.methpars.REMMI_EddyDelay + refPulseDur; % ms 18 | gssr = pp.methpars.RefSliceGrad*gmax/100; % mT/m 19 | 20 | tcrush = pp.methpars.EncGradDur; % ms 21 | gcrush = pp.methpars.REMMI_ModSpoilerAmp(1)*gmax/100; % mT/m 22 | 23 | % obtain normalized diffusion directions 24 | dwdir = reshape(pp.methpars.PVM_DwGradVec,3,[]); 25 | dro = dwdir(1,:); 26 | dpe = -dwdir(2,:); 27 | dsl = dwdir(3,:); 28 | 29 | if strcmpi(pp.methpars.REMMI_DwGradPolarity,'Yes') 30 | dro = [dro, -dro]; 31 | dpe = [dpe, -dpe]; 32 | dsl = [dsl, -dsl]; 33 | 34 | dro = repmat(dro,[1 pp.methpars.PVM_NRepetitions/2]); 35 | dpe = repmat(dpe,[1 pp.methpars.PVM_NRepetitions/2]); 36 | dsl = repmat(dsl,[1 pp.methpars.PVM_NRepetitions/2]); 37 | else 38 | dro = repmat(dro,[1 pp.methpars.PVM_NRepetitions]); 39 | dpe = repmat(dpe,[1 pp.methpars.PVM_NRepetitions]); 40 | dsl = repmat(dsl,[1 pp.methpars.PVM_NRepetitions]); 41 | end 42 | 43 | % diffusion gradients 44 | gdiff = gmax; % pp.methpars.PVM_DwGradAmp*gmax/100; % mT/m 45 | bigD = pp.methpars.PVM_DwGradSep; % ms 46 | litD = pp.methpars.PVM_DwGradDur; % ms 47 | 48 | if ~isfield(pp.methpars,'REMMI_DwWaveType') || strcmp(pp.methpars.REMMI_DwWaveType,'Pulsed_Gradient') 49 | rwave = ones(1,round(litD/dt)); 50 | pwave = ones(1,round(litD/dt)); 51 | swave = ones(1,round(litD/dt)); 52 | else 53 | rwave = pp.methpars.REMMI_DwWaveR; 54 | pwave = pp.methpars.REMMI_DwWaveP; 55 | swave = pp.methpars.REMMI_DwWaveS; 56 | end 57 | 58 | for n=1:length(dro) 59 | t = 0:dt:(te+tm); 60 | Gr = zeros(size(t)); 61 | Gp = zeros(size(t)); 62 | Gs = zeros(size(t)); 63 | 64 | % 180 slice select and crusher 65 | mask = (t>te/2-tssr/2-tcrush) & (t<=te/2); 66 | Gs(mask) = gcrush; 67 | mask = (t>te/2-tssr/2) & (t<=te/2); 68 | Gs(mask) = gssr; 69 | 70 | mask = (t=te/2+tm); 71 | Gs(mask) = -gcrush; 72 | mask = (t=te/2+tm); 73 | Gs(mask) = -gssr; 74 | 75 | % read out direction 76 | % mask = (t>te/2-bigD/2-litD/2+tm/2) & (tte/2+bigD/2-litD/2+tm/2) & (t outblocks 229 | break 230 | end 231 | end % done reading one block 232 | 233 | 234 | if nargout > 2 235 | NP = np/2; 236 | end 237 | if nargout > 3 238 | NB = nblocks; 239 | end 240 | if nargout > 4 241 | NT = ntraces; 242 | end 243 | if nargout > 5 244 | HDR = [nblocks, ntraces, np, ebytes, tbytes, bbytes, vers_id, status, nbheaders]; 245 | HDR = [HDR, scale, bstatus, index, mode, ctcount, lpval, rpval, lvl, tlt]; 246 | end 247 | fclose(fid); 248 | 249 | end -------------------------------------------------------------------------------- /+remmi/+vendors/loadBruker.m: -------------------------------------------------------------------------------- 1 | function datai = loadBruker(dataPath,methpars) 2 | % img = loadBruker(dataPath) 3 | % loads raw data from Bruker acquisitions & reconstructs images 4 | % 5 | % Kevin Harkins & Mark Does, Vanderbilt University 6 | % for the REMMI Toolbox 7 | 8 | if ~exist('methpars','var') 9 | % load a few parameters 10 | methpath = fullfile(dataPath,'method'); 11 | methpars = remmi.vendors.parsBruker(methpath); 12 | end 13 | 14 | if isfield(methpars,'PVM_RareFactor') 15 | rarefactor = methpars.PVM_RareFactor; 16 | else 17 | rarefactor = 1; 18 | end 19 | 20 | if isfield(methpars,'EffectiveTE') 21 | echotimes = methpars.EffectiveTE; % ms 22 | else 23 | echotimes = methpars.PVM_EchoTime; 24 | end 25 | 26 | % Number of diffusion images 27 | diffImgs = 1; 28 | if isfield(methpars,'REMMI_DwiOnOff') 29 | if strcmp(methpars.REMMI_DwiOnOff,'Yes') 30 | diffImgs = methpars.PVM_DwNDiffExp; 31 | end 32 | end 33 | 34 | % Number of IR images (including MTIR) 35 | irImgs = 1; 36 | if isfield(methpars,'REMMI_MtIrOnOff') 37 | if strcmp(methpars.REMMI_MtIrOnOff,'Yes') 38 | irImgs = methpars.REMMI_NMtIr; 39 | end 40 | end 41 | 42 | % Number of MT images (MT offset) 43 | mtImgs = 1; 44 | if isfield(methpars,'REMMI_NMagTrans') 45 | if strcmp(methpars.PVM_MagTransOnOff,'On') 46 | mtImgs = methpars.REMMI_NMagTrans; 47 | end 48 | end 49 | 50 | % Number of BsB1 images 51 | bsImgs = 1; 52 | if isfield(methpars,'REMMI_BsB1OnOff') 53 | if strcmp(methpars.REMMI_BsB1OnOff,'On') 54 | bsImgs = 2; 55 | end 56 | end 57 | 58 | % Number of recieve coils 59 | ncoil = 1; 60 | if isfield(methpars,'PVM_EncNReceivers') 61 | ncoil = methpars.PVM_EncNReceivers; 62 | end 63 | 64 | nslice = sum(methpars.PVM_SPackArrNSlices); 65 | nreps = methpars.PVM_NRepetitions; 66 | 67 | encmatrix = methpars.PVM_EncMatrix; 68 | matrix = methpars.PVM_Matrix; 69 | if length(encmatrix) < 3 70 | encmatrix(3) = 1; 71 | end 72 | if length(matrix) < 3 73 | matrix(3) = 1; 74 | end 75 | 76 | pe1table = methpars.PVM_EncSteps1; 77 | pe1table = pe1table+floor(encmatrix(2)/2)+1; 78 | 79 | if encmatrix(3) > 1 80 | pe2table = methpars.PVM_EncSteps2; 81 | pe2table = pe2table+floor(encmatrix(3)/2)+1; 82 | else 83 | pe2table = 1; 84 | end 85 | 86 | fid=fopen([dataPath,'/fid']); 87 | 88 | if (fid == -1) 89 | fid=fopen([dataPath,'/rawdata.job0']); % PV360 and others (?) 90 | end 91 | 92 | if (fid == -1) 93 | error('Cannot open fid file in %s', dataPath); 94 | end 95 | 96 | raw=fread(fid,'bit32'); %long=32-bit unsigned integer, signed=bit32 97 | fclose(fid); 98 | 99 | % Bruker requires reaodut lines to have memory alignment of 2^n 100 | roalign=length(raw)/ncoil/length(echotimes)/encmatrix(2)/encmatrix(3)/2/... 101 | diffImgs/irImgs/mtImgs/bsImgs/nreps/nslice; 102 | 103 | % if reference phase data exists, read it in 104 | ph_ref0 = zeros(1,rarefactor,1,1,length(echotimes),ncoil,nslice,diffImgs,1,1,1,nreps); 105 | ph_ref1 = zeros(1,rarefactor,1,1,length(echotimes),ncoil,nslice,diffImgs,1,1,1,nreps); 106 | if isfield(methpars,'REMMI_ProcnoResult') 107 | vals = remmi.util.strsplit(methpars.REMMI_ProcnoResult(2:end-1),','); 108 | [fstudy,~] = fileparts(dataPath); 109 | if ~strcmp(strtrim(vals{end-1}),'0') 110 | ph_path = fullfile(fstudy,strtrim(vals{end-1})); 111 | ph_fid = fopen(fullfile(ph_path,'fid')); 112 | if ph_fid < 0 113 | ph_fid = fopen(fullfile(fstudy,strtrim(vals{end-1}),'rawdata.job0')); 114 | end 115 | if ph_fid < 0 116 | warning('Phase reference scan not found'); 117 | else 118 | ph_raw = fread(ph_fid,'bit32'); 119 | fclose(ph_fid); 120 | %ph_pars = remmi.vendors.parsBruker(fullfile(ph_path,'method')); 121 | 122 | % real + imaginary 123 | ph_raw = reshape(ph_raw,2,length(ph_raw)/2); 124 | ph_raw = ph_raw(1,:) + 1i*ph_raw(2,:); 125 | 126 | % set format to [readout, echo, etc] 127 | % [ro,ncoil,rarefactor,echoes,slices,pe1,pe2,diff,mtir,nreps] 128 | ph_raw = reshape(ph_raw,roalign,ncoil,rarefactor*length(echotimes),[],nslice, ... 129 | diffImgs,1,1,1,nreps); 130 | 131 | ph_raw = permute(ph_raw,[1 3 4 2 5 6 7 8:12]); 132 | % order is now [ro, echo train, navg, ncoil, nslice, ndiff, ?, 133 | % ?, ?, rep] 134 | 135 | % bruker encoding workaround 136 | ph_raw = sum(ph_raw,3); 137 | ph_raw = reshape(ph_raw,[],rarefactor*length(echotimes),ncoil,nslice, ... 138 | diffImgs,1,1,1,nreps); 139 | 140 | ph_raw = ph_raw(1:encmatrix(1),:); 141 | 142 | % phase correction 143 | [ph_ref0,ph_ref1] = dwi_phase_corr(ph_raw); 144 | 145 | ph_ref0 = reshape(ph_ref0,[1 rarefactor 1 1 length(echotimes) ... 146 | ncoil nslice diffImgs 1 1 nreps]); 147 | ph_ref1 = reshape(ph_ref1,[1 rarefactor 1 1 length(echotimes) ... 148 | ncoil nslice diffImgs 1 1 nreps]); 149 | end 150 | end 151 | end 152 | 153 | % combine real/imaginary 154 | data = reshape(raw,2,length(raw)/2); 155 | data = data(1,:) + 1i*data(2,:); 156 | 157 | % at this point, the array index is : 158 | % [ro,ncoil,rarefactor,echoes,slices,pe1,pe2,diff,mtir,,mt,BS,nreps] 159 | data = reshape(data,roalign,ncoil,rarefactor,length(echotimes),nslice,... 160 | encmatrix(2)/rarefactor,encmatrix(3),diffImgs,irImgs,mtImgs,bsImgs,nreps); 161 | 162 | % reorder 163 | data = permute(data,[1,3,6,7,4,2,5,8:ndims(data)]); 164 | data = remmi.util.slice(data,1,1:encmatrix(1)); 165 | % format is now [ro,rare,pe1,pe2,echoes,coils,slices,diffImgs,irImgs,mtImgs,nreps] 166 | 167 | % move to projection space 168 | proj = fftshift(fft(fftshift(data,1)),1); 169 | 170 | % correct for 0th & first order phase 171 | proj = bsxfun(@times,proj,exp(-1i*(... 172 | bsxfun(@plus, ph_ref0, bsxfun(@times,(1:encmatrix(1))',ph_ref1))))); 173 | 174 | % back into full fourier space 175 | data = ifftshift(ifft(ifftshift(proj,1)),1); 176 | 177 | % use phase encode tables 178 | data = reshape(data,encmatrix(1),encmatrix(2),encmatrix(3),length(echotimes),... 179 | ncoil,nslice,diffImgs,irImgs,mtImgs,bsImgs,nreps); 180 | datai = zeros([encmatrix length(echotimes),ncoil,nslice,diffImgs,irImgs,mtImgs,bsImgs,nreps]); 181 | datai(:,pe1table,pe2table,:,:,:,:,:,:,:,:,:) = data; 182 | 183 | % reorder slices 184 | sl_order = methpars.PVM_ObjOrderList + 1; 185 | datai(:,:,:,:,:,sl_order,:,:,:,:,:,:) = datai; 186 | 187 | % flip odd echoes in gradient echo sequences 188 | if methpars.PVM_NEchoImages>1 && ~isfield(methpars,'RefPulse1') 189 | % no refocusing pulse. This must be a gradient echo sequence. 190 | if isfield(methpars,'EchoAcqMode') 191 | if strcmp(methpars.EchoAcqMode,'allEchoes') 192 | disp('fliping...') 193 | datai(:,:,:,2:2:end,:,:,:,:,:,:,:) = datai(end:-1:1,:,:,2:2:end,:,:,:,:,:,:,:); 194 | end 195 | else 196 | disp('fliping...') 197 | datai(:,:,:,2:2:end,:,:,:,:,:,:,:) = datai(end:-1:1,:,:,2:2:end,:,:,:,:,:,:,:); 198 | end 199 | end 200 | 201 | % phase encode off isocenter shift 202 | ph1_offset = 0; 203 | ph2_offset = 0; 204 | if isfield(methpars,'PVM_EffPhase1Offset') 205 | ph1_offset = reshape(methpars.PVM_EffPhase1Offset,1,1,1,1,1,[]); 206 | end 207 | if isfield(methpars,'PVM_EffPhase2Offset') 208 | ph2_offset = reshape(methpars.PVM_EffPhase2Offset,1,1,1,1,1,[]); 209 | end 210 | 211 | % PE1 shift 212 | np = methpars.PVM_EncMatrix(2); 213 | line = reshape((1:np) - 1 - round(np/2),1,[]); 214 | datai = bsxfun(@times,datai,exp(-1i*2*pi*bsxfun(@times,line,ph1_offset)/methpars.PVM_Fov(2)/methpars.PVM_AntiAlias(2))); 215 | 216 | % PE2 shift 217 | if length(methpars.PVM_EncMatrix) > 2 218 | np = methpars.PVM_EncMatrix(3); 219 | line = reshape((1:np) - 1 - round(np/2),1,1,[]); 220 | datai = bsxfun(@times,datai,exp(-1i*2*pi*bsxfun(@times,line,ph2_offset)/methpars.PVM_Fov(3)/methpars.PVM_AntiAlias(3))); 221 | end 222 | 223 | end 224 | 225 | function [ph0,ph1] = dwi_phase_corr(raw) 226 | 227 | proj = fftshift(fft(fftshift(raw,1)),1); 228 | 229 | ph0 = zeros(size(raw,2),1); 230 | ph1 = zeros(size(raw,2),1); 231 | 232 | p1 = proj(:,1); 233 | for n=2:size(proj,2) 234 | pn = proj(:,n); 235 | mask = abs(pn)/max(abs(pn)) > 0.1; 236 | ph_diff = pn./p1; 237 | 238 | idx = 1:length(pn); 239 | phw = unwrap(angle(ph_diff(mask))); 240 | ph = remmi.util.linfitfast(idx(mask)',phw,abs(pn(mask))); 241 | ph0(n) = ph(2); 242 | ph1(n) = ph(1); 243 | end 244 | 245 | end 246 | 247 | -------------------------------------------------------------------------------- /+remmi/+vendors/loadBrukerGrase.m: -------------------------------------------------------------------------------- 1 | function [sigi,methpars,acqpars] = loadBrukerGrase(dataPath,methpars,acqpars) 2 | % img = loadBrukerGrase(dataPath) 3 | % loads & sorts raw data from remmiGRASE acquisitions 4 | % 5 | % Kevin Harkins & Mark Does, Vanderbilt University 6 | % for the REMMI Toolbox 7 | 8 | isGRASE2 = contains(methpars.Method,'remmiGRASE2'); 9 | % isGRASE2 = true means the readout is bipolar 10 | 11 | if isfield(methpars,'PVM_RareFactor') 12 | rarefactor = methpars.PVM_RareFactor; 13 | else 14 | rarefactor = 1; 15 | end 16 | 17 | if isfield(methpars,'EffectiveTE') 18 | echotimes = methpars.EffectiveTE; % ms 19 | else 20 | echotimes = methpars.PVM_EchoTime; 21 | end 22 | 23 | % Number of diffusion images 24 | diffImgs = 1; 25 | if isfield(methpars,'REMMI_DwiOnOff') 26 | if strcmp(methpars.REMMI_DwiOnOff,'Yes') 27 | diffImgs = methpars.PVM_DwNDiffExp; 28 | end 29 | end 30 | 31 | % Number of IR images (including MTIR) 32 | irImgs = 1; 33 | if isfield(methpars,'REMMI_MtIrOnOff') 34 | if strcmp(methpars.REMMI_MtIrOnOff,'Yes') 35 | irImgs = methpars.REMMI_NMtIr; 36 | end 37 | end 38 | 39 | % Number of MT images (MT offset) 40 | mtImgs = 1; 41 | if isfield(methpars,'REMMI_NMagTrans') 42 | if strcmp(methpars.PVM_MagTransOnOff,'On') 43 | mtImgs = methpars.REMMI_NMagTrans; 44 | end 45 | end 46 | 47 | % Number of recieve coils 48 | ncoil = 1; 49 | if isfield(methpars,'PVM_EncNReceivers') 50 | ncoil = methpars.PVM_EncNReceivers; 51 | end 52 | 53 | nslice = sum(methpars.PVM_SPackArrNSlices); 54 | nreps = methpars.PVM_NRepetitions; 55 | bsImgs = 1; 56 | 57 | encmatrix = methpars.PVM_EncMatrix; 58 | matrix = methpars.PVM_Matrix; 59 | acqsize = acqpars.ACQ_size; 60 | if length(encmatrix) < 3 61 | encmatrix(3) = 1; 62 | end 63 | if length(matrix) < 3 64 | matrix(3) = 1; 65 | end 66 | if length(acqsize) < 3 67 | acqsize(3) = 1; 68 | end 69 | 70 | pe1table = methpars.PVM_EncSteps1; 71 | pe1table = pe1table+floor(encmatrix(2)/2)+1; 72 | 73 | if encmatrix(3) > 1 74 | pe2table = methpars.PVM_EncSteps2; 75 | pe2table = pe2table+floor(encmatrix(3)/2)+1; 76 | else 77 | pe2table = 1; 78 | end 79 | 80 | 81 | fid=fopen([dataPath,'/fid']); 82 | if (fid == -1) 83 | error('Cannot open fid file in %s', dataPath); 84 | end 85 | 86 | raw=fread(fid,'bit32'); %long=32-bit unsigned integer, signed=bit32 87 | fclose(fid); 88 | 89 | raw = reshape(raw,2,[]); 90 | raw = squeeze(raw(1,:) + 1i*raw(2,:)); 91 | 92 | % Bruker requires reaodut lines to have memory alignment of 2^n 93 | roalign=length(raw)/ncoil/length(echotimes)/encmatrix(2)/encmatrix(3)/2/... 94 | diffImgs/irImgs/mtImgs/bsImgs/nreps/nslice; 95 | 96 | ne = methpars.PVM_RareFactor*length(echotimes)+methpars.REMMI_NavEchoes; 97 | n1 = ceil(acqsize(1)/2/128)*128; 98 | 99 | sig = reshape(raw,n1,ncoil,ne,nslice,acqsize(2)/ne,acqsize(3),... 100 | diffImgs,irImgs,mtImgs,bsImgs,nreps); 101 | 102 | % order now is [ro&grase ncoil echoTrain nslice pe1 pe2 diff ir 1, 1, nr] 103 | 104 | % crop readout and reorder GRASE 105 | sig = sig(1:acqsize(1)/2,:,:,:,:,:,:,:,:,:); 106 | 107 | risetm = 0.145; 108 | 109 | gfact = 1; 110 | trueGraseFactor = methpars.REMMI_GraseFactor; 111 | if isGRASE2 112 | ph0=(0:size(sig,1)-1)'; 113 | ph0_off_corr = exp(1i*2*pi*ph0*methpars.PVM_ReadOffset/methpars.PVM_Fov(1)/2); 114 | sig = sig.*ph0_off_corr; 115 | gfact = 2; 116 | trueGraseFactor = 2*methpars.REMMI_GraseFactor-1; 117 | else 118 | ph0_off_corr = 1; 119 | end 120 | 121 | nro_line = methpars.PVM_EncMatrix(1); 122 | nstart = round(risetm/(1000/methpars.PVM_EffSWh/methpars.PVM_AntiAlias(1))); 123 | nstart = nstart + round((0:trueGraseFactor-1)... 124 | *methpars.REMMI_GraseEchoSpacing/gfact/(1000/methpars.PVM_EffSWh/methpars.PVM_AntiAlias(1))); 125 | nstart = nstart + (0:nro_line-1)'; 126 | sig = sig(nstart(:),:,:,:,:,:,:,:,:,:,:); 127 | ne = methpars.PVM_RareFactor+methpars.REMMI_NavEchoes; 128 | sig = reshape(sig,nro_line,trueGraseFactor,ncoil,... 129 | ne,nslice,acqsize(2)/ne,acqsize(3),diffImgs,irImgs,mtImgs,bsImgs,nreps); 130 | 131 | if isGRASE2 132 | % reverse odd grase lines for bi polar readout 133 | sig(:,2:2:end,:) = sig(end:-1:1,2:2:end,:); 134 | end 135 | 136 | % order now is [ro grase ncoil echoTrain nslice pe1, pe2, diff, ir, 1, 1, nr] 137 | proj = fftshift(fft(fftshift(sig,1)),1); 138 | 139 | 140 | %% correct for phase variations down the echo train, if reference data exists 141 | vals = remmi.util.strsplit(methpars.REMMI_ProcnoResult(2:end-1),','); 142 | [fstudy,~] = fileparts(dataPath); 143 | if ~strcmp(strtrim(vals{end-1}),'0') 144 | ph_fid = fullfile(fstudy,strtrim(vals{end-1}),'fid'); 145 | fid=fopen(ph_fid); 146 | if (fid == -1) 147 | error('Cannot open fid file in %s', ph_fid); 148 | end 149 | 150 | raw_p=fread(fid,'bit32'); %long=32-bit unsigned integer, signed=bit32 151 | fclose(fid); 152 | 153 | raw_p = reshape(raw_p,2,[]); 154 | raw_p = squeeze(raw_p(1,:) + 1i*raw_p(2,:)); 155 | 156 | 157 | sig_p = reshape(raw_p,n1,[]); 158 | sig_p = sig_p(1:acqpars.ACQ_size(1)/2,:,:,:); 159 | 160 | sig_p = sig_p.*ph0_off_corr; 161 | 162 | sig_p = sig_p(nstart(:),:); 163 | sig_p = reshape(sig_p,nro_line,trueGraseFactor,ncoil,... 164 | ne,nslice,[],1,diffImgs); 165 | sig_p = mean(sig_p,6); 166 | if isGRASE2 167 | sig_p(:,2:2:end,:) = sig_p(end:-1:1,2:2:end,:); 168 | end 169 | proj_p = fftshift(fft(fftshift(sig_p,1)),1); 170 | projcc = proj.*conj(proj_p)./abs(proj_p); 171 | else 172 | projcc = proj; 173 | end 174 | 175 | %% navigator correction 176 | 177 | projc = zeros(size(proj)); 178 | projc(:,:,:,1:2:end,:,:,:,:,:,:,:) = ... 179 | projcc(:,:,:,1:2:end,:,:,:,:,:,:,:);%.*exp(-1i*angle(projcc(:,:,:,end-1,:,:,:,:,:,:,:))); 180 | projc(:,:,:,2:2:end,:,:,:,:,:,:,:) = ... 181 | projcc(:,:,:,2:2:end,:,:,:,:,:,:,:);%.*exp(-1i*angle(projcc(:,:,:,end ,:,:,:,:,:,:,:))); 182 | 183 | sig = ifftshift(ifft(ifftshift(projc,1)),1); 184 | sig = sig(:,:,:,1:rarefactor*length(echotimes),:,:,:,:,:,:,:); 185 | 186 | %% split rare & pe 187 | 188 | % order now is [ro grase ncoil echoTrain nslice pe1, pe2, diff, ir, 1, 1, nr] 189 | sig = reshape(sig,nro_line,trueGraseFactor,ncoil,... 190 | rarefactor,length(echotimes),nslice,acqsize(2)/ne,acqsize(3),... 191 | diffImgs,irImgs,mtImgs,bsImgs,nreps); 192 | % labels = {'ro', 'grase', 'ncoil', 'rare', 'ne', 'ns', 'pe1', 'pe2', ... 193 | % 'diff', 'ir', '1', '1', 'nr'}; 194 | 195 | % order now is [1 2 3 4 5 6 7, 8, diff, ir, 1, 1, nr] 196 | ordr = [1 4 7 8 2 5 3 6 9 10 11 12 13]; 197 | sig = permute(sig,ordr); 198 | % labels = labels(ordr); 199 | 200 | % order now is [ro rare pe1 grase pe2 nslice ne ncoil diff, ir, 1, 1, nr] 201 | 202 | sig = reshape(sig,[encmatrix,length(echotimes),ncoil,nslice,... 203 | diffImgs,irImgs,mtImgs,bsImgs,nreps]); 204 | 205 | % use phase encode tables 206 | sigi = zeros([encmatrix length(echotimes),ncoil,nslice,diffImgs,irImgs,mtImgs,bsImgs,nreps]); 207 | sigi(:,pe1table,pe2table,:,:,:,:,:,:,:,:,:) = sig; 208 | 209 | % reorder slices 210 | sl_order = methpars.PVM_ObjOrderList + 1; 211 | sigi(:,:,:,:,:,sl_order,:,:,:,:,:,:) = sigi; 212 | 213 | % phase encode off isocenter shift 214 | ph1_offset = 0; 215 | ph2_offset = 0; 216 | if isfield(methpars,'PVM_EffPhase1Offset') 217 | ph1_offset = reshape(methpars.PVM_EffPhase1Offset,1,1,1,1,1,[]); 218 | end 219 | if isfield(methpars,'PVM_EffPhase2Offset') 220 | ph2_offset = reshape(methpars.PVM_EffPhase2Offset,1,1,1,1,1,[]); 221 | end 222 | 223 | if isGRASE2 224 | % RO shift for GRASE2 225 | ph0=(0:size(sigi,1)-1)'; 226 | ph0_off_corr = exp(-1i*2*pi*ph0*methpars.PVM_ReadOffset/methpars.PVM_Fov(1)/2); 227 | sigi = sigi.*ph0_off_corr; 228 | end 229 | 230 | % PE1 shift 231 | np = encmatrix(2); 232 | line = reshape((1:np) - 1 - round(np/2),1,[]); 233 | sigi = bsxfun(@times,sigi,exp(-1i*2*pi*bsxfun(@times,line,ph1_offset)/methpars.PVM_Fov(2)/methpars.PVM_AntiAlias(2))); 234 | 235 | % PE2 shift 236 | if length(methpars.PVM_EncMatrix) > 2 237 | np = encmatrix(3); 238 | line = reshape((1:np) - 1 - round(np/2),1,1,[]); 239 | sigi = bsxfun(@times,sigi,exp(-1i*2*pi*bsxfun(@times,line,ph2_offset)/methpars.PVM_Fov(3)/methpars.PVM_AntiAlias(3))); 240 | end 241 | -------------------------------------------------------------------------------- /+remmi/+vendors/parsAgilent.m: -------------------------------------------------------------------------------- 1 | function procpar = parsAgilent(pathname) 2 | %parsePP: Returns the contents of a Agilent procpar file in a structure 3 | % 4 | %Usage: procpar = parsePP(experimentName); 5 | % 6 | %NOTES: Float parameters are converted from strings with str2num. This 7 | % function will cast the values to double precision. The 8 | % function str2double will only accept a single value, which is 9 | % incompatible with some parameter entries, so str2num is used 10 | % throughout for consistency. 11 | % This file was derived from queryPP, written by Maj Hedehus. 12 | % Although this function bears little resemblance to queryPP, 13 | % the control structure was taken directly from it, and as such, 14 | % Maj gets top billing. 15 | %---------------------------------------- 16 | % Maj Hedehus, Varian, Inc., Oct 2001. 17 | % modified by J. Luci for more general use. 18 | %---------------------------------------- 19 | 20 | 21 | 22 | %fullname = [pathname, '\procpar']; 23 | fullname=pathname; 24 | fid = fopen(fullname,'r'); 25 | if fid == -1 26 | str = sprintf('Can not open file %s',fullname); 27 | error(str); 28 | end 29 | 30 | while ~feof(fid) 31 | par = fscanf(fid,'%s',1); 32 | type = fscanf(fid,'%d',1); 33 | if strcmp(par,'com$string') 34 | type=8; 35 | end 36 | fgetl(fid); %skip rest of line 37 | nvals = fscanf(fid,'%d',1); 38 | 39 | switch type 40 | case 1 % float 41 | eval(['procpar.' par '= fscanf(fid,''%f'',nvals);']); 42 | skip=fgetl(fid); 43 | skip=fgetl(fid); 44 | case 2 % string 45 | fullstr=[]; 46 | for ii = 1:nvals, 47 | str = uint8(fgetl(fid)); 48 | if str(1) == 32, 49 | str=str(2:end); %strip off leading space if it exists 50 | end 51 | str = str(2:size(str,2)-1); %strip off double quotes from the string 52 | fullstr = [fullstr str 10]; %10 is the ASCII code for LF 53 | end 54 | fullstr=char(fullstr); 55 | eval(['procpar.' par '= fullstr;']); 56 | skip=fgetl(fid); 57 | case 3 % delay 58 | eval(['procpar.' par '= fscanf(fid,''%f'',nvals);']); 59 | skip=fgetl(fid); 60 | skip=fgetl(fid); 61 | case 4 % flag 62 | L = uint8(fgetl(fid)); 63 | if L(1) == 32, 64 | L=L(2:end); %strip off leading space if it exists 65 | end 66 | L = L(2:size(L,2)-1); %strip off double quotes from the string 67 | L=char(L); 68 | eval(['procpar.' par '= L;']); 69 | skip=fgetl(fid); 70 | case 5 % frequency 71 | eval(['procpar.' par '= fscanf(fid,''%f'',nvals);']); 72 | skip=fgetl(fid); 73 | skip=fgetl(fid); 74 | case 6 % pulse 75 | L = str2double(fgetl(fid)); 76 | eval(['procpar.' par '= L;']); 77 | skip=fgetl(fid); 78 | case 7 % integer 79 | num = str2num(fgetl(fid)); 80 | eval(['procpar.' par '= num;']); 81 | skip=fgetl(fid); 82 | case 8 83 | fgetl(fid); 84 | fgetl(fid); 85 | end 86 | end 87 | fclose(fid); -------------------------------------------------------------------------------- /+remmi/+vendors/parsBruker.m: -------------------------------------------------------------------------------- 1 | function pars = parsBruker(filename) 2 | % pars = parsBruker(filename) 3 | % filename = any parameter file from Bruker scan 4 | % pars: list of all parameters & their values contained within the file 5 | % 6 | % Kevin Harkins & Mark Does, Vanderbilt University 7 | % for the REMMI Toolbox 8 | 9 | fid = fopen(filename,'r'); 10 | if fid == -1 11 | error('Could not open method file: %s',filename); 12 | end % if 13 | 14 | 15 | line = fgetl(fid); % read the first line 16 | while line ~= -1 17 | if strcmp(line(1:min(end,3)),'##$') 18 | % this line describes a parameter 19 | line = line(4:end); 20 | idx = strfind(line,'='); 21 | if length(idx) ~= 1 22 | warning('Unable to parse line: %s',line); 23 | end 24 | 25 | parID = line(1:idx-1); 26 | value = line(idx+1:end); 27 | 28 | if strcmp(value(1),'(') 29 | % This is an array of values. How many values should be in this array? 30 | nval = str2num(value(2:end-1)); 31 | if isempty(nval) 32 | % Read the whole thing in, and save as a string 33 | all_lines = value; 34 | this_line = strtrim(fgetl(fid)); 35 | 36 | while ~strcmp(this_line(1),'#') && ~strcmp(this_line(1),'$') 37 | all_lines = [all_lines ' ' this_line]; 38 | this_line = strtrim(fgetl(fid)); 39 | pars.(parID) = all_lines; 40 | end 41 | 42 | line = this_line; 43 | continue; 44 | end 45 | 46 | all_lines = ''; 47 | this_line = strtrim(fgetl(fid)); 48 | while ~strcmp(this_line(1),'#') && ~strcmp(this_line(1),'$') 49 | all_lines = [all_lines ' ' this_line]; 50 | this_line = strtrim(fgetl(fid)); 51 | end 52 | 53 | % read the array of values 54 | all_lines = strtrim(all_lines); 55 | [val, status] = str2num(all_lines); 56 | if status % read as a number 57 | pars.(parID) = val; 58 | elseif isempty(all_lines) 59 | pars.(parID) = nval; 60 | else % read as a string 61 | if strcmp(all_lines(1),'<') && strcmp(all_lines(end),'>') 62 | % remove brackets 63 | all_lines = all_lines(2:end-1); 64 | end 65 | 66 | pars.(parID) = all_lines; 67 | end 68 | 69 | line = this_line; 70 | else 71 | % read a single value 72 | [val, status] = str2num(value); 73 | if status 74 | % read as a number 75 | pars.(parID) = val; 76 | else 77 | % read as a string 78 | pars.(parID) = value; 79 | end 80 | 81 | line = fgetl(fid); % read the whole line 82 | end % if 83 | else 84 | line = fgetl(fid); % read the whole line 85 | end 86 | end 87 | fclose(fid); 88 | 89 | 90 | -------------------------------------------------------------------------------- /+remmi/Contents.m: -------------------------------------------------------------------------------- 1 | % 2 | % MAIN FUNCTIONS: 3 | % 4 | % General and Data Storage 5 | % remmi.dataset - a generalized container for automatic saving any kind of data 6 | % remmi.recon - loads raw data and reconstructs images from recognized 7 | % vendor formats 8 | % remmi.version - returns the current version number 9 | % 10 | % Multiple Spin Echo 11 | % remmi.mse.MERA - MERA toolbox 12 | % remmi.mse.mT2 - processes multi-exponential T2 in MERA 13 | % remmi.mse.analysis - processes generalized relaxation with MERA 14 | % 15 | % Diffusion 16 | % remmi.dwi.dti - processes DTI 17 | % 18 | % Inversion Recovery 19 | % remmi.ir.qmt - processes quantitative magnetization transfer 20 | % remmi.ir.T1 - processes T1 21 | % 22 | % Utilities 23 | % remmi.util.apodize - 1d, 2d or 3d tukey apodization 24 | % remmi.util.slice - slice through multi-dimensional data 25 | % remmi.util.snrCalc - calculate SNR in a 2D image 26 | % remmi.util.thresholdmask - create a mask based upon % image intensity 27 | % 28 | % Region of Interest 29 | % remmi.roi.draw - basic ROI processing of a dataset 30 | % remmi.roi.copy - copies and applies ROIs from one dataset into another 31 | % 32 | % Vendors 33 | % remmi.vendors.BrukerPV - class for loading Bruker data 34 | % remmi.vendors.loadBruker - function to load and order raw Bruker data 35 | % remmi.vendors.parsBruker - function to load a Bruker parameter file 36 | % remmi.vendors.bmatBruker - calculates the b-matrix for remmiRARE 37 | % sequence 38 | % 39 | % remmi.vendors.Agilent - WIP class for loading Agilent data 40 | % remmi.vendors.loadAgilent - WIP. loads and orders Agilent raw data 41 | % remmi.vendors.parsAgilent - Loads a Agilent parameter file 42 | % 43 | % 44 | % HELPER FUNCTIONS: 45 | % 46 | % remmi.dwi.addbmatrix - adds a b-matrix to a structure 47 | % remmi.dwi.dtilin - simple linear tensor fitting 48 | % remmi.dwi.dtinonlin - nonlinear tensor fitting (slow!!!) 49 | % remmi.ir.ir - signal equation for longitudinal relaxation 50 | % remmi.ir.sir - Solves bloch equations for 2 compartment selective 51 | % inversion recovery 52 | % remmi.mse.calcGeometricMean - returns a function handle to calculate 53 | % the geometric mean on a spectrum output by MERA 54 | % remmi.mse.calcT2frac - returns a function handle to calculate a signal 55 | % fraction between lower & upper T2 bounds from a spectrum output by 56 | % MERA 57 | % remmi.mse.mT2options - default options for mT2 processing 58 | % remmi.recon.ft - basic fourier transform reconstruction 59 | % remmi.recon.options - default options for fourier transform 60 | % reconstruction 61 | % remmi.util.githash - returns the git hash of the current version of 62 | % REMMI-Matlab 63 | % remmi.util.strsplit - clone of matlab's 'strsplit' to improve backwards 64 | % compatibility 65 | % 66 | -------------------------------------------------------------------------------- /+remmi/dataset.m: -------------------------------------------------------------------------------- 1 | function ws = dataset(varargin) 2 | 3 | warning(['remmi.dataset has been renamed to remmi.workspace, ' ... 4 | 'and will be depreciated in later version. Please update ' ... 5 | 'your code to use remmi.workspace()']); 6 | 7 | ws = remmi.workspace(varargin{:}); -------------------------------------------------------------------------------- /+remmi/loadImg.m: -------------------------------------------------------------------------------- 1 | function dset = loadImg(dset,options) 2 | % dset = remmi.loadImg(dset,options) loads saved images from recognized 3 | % pre-clinical MRI data formats, and returns reconstructed images and 4 | % parameters. 5 | % 6 | % optional inputs: 7 | % dset.spath = string path to a study stored in a recognized vender format 8 | % dset.exps = list of experiments to reconstruct within spath 9 | % options.id = recon ID to load, default is '1' 10 | % 11 | % output: 12 | % dset = a structure (or array of structures) containing: 13 | % dset.img = reconstructed images 14 | % dset.pars = list of experimental parameters used during image 15 | % acquisition 16 | % 17 | % Kevin Harkins & Mark Does, Vanderbilt University 18 | % for the REMMI Toolbox 19 | 20 | if nargin<1 21 | dset = struct(); 22 | end 23 | 24 | persistent rpath 25 | 26 | if ~exist('options','var') 27 | options = struct(); 28 | end 29 | 30 | if ~isfield(dset,'spath') || isempty(dset.spath) 31 | disp('Select the study directory'); 32 | dset.spath = uigetdir(rpath,'Select study directory'); 33 | if dset.spath == 0 34 | error('No study path given'); 35 | end 36 | rpath = dset.spath; 37 | disp(['The study path is: ' dset.spath]); 38 | end 39 | 40 | study = remmi.vendors.autoVendor(dset.spath); 41 | 42 | if ~isfield(dset,'exps') || isempty(dset.exps) 43 | disp('Select the experiment(s)'); 44 | exps = study.list(); 45 | sel = listdlg('ListString',exps.name); 46 | exps = exps.id(sel); 47 | if isempty(exps) 48 | error('No experiments specified in %s',spath); 49 | end 50 | else 51 | exps = dset.exps; 52 | end 53 | 54 | if ~iscell(exps) 55 | if ischar(exps) 56 | exps = {exps}; 57 | else 58 | exps = num2cell(exps); 59 | end 60 | end 61 | 62 | dset.exps = exps; 63 | 64 | % load all the datasets 65 | for n=1:length(exps) 66 | [dset.img{n},dset.labels{n},dset.pars{n}] = study.loadImg(num2str(exps{n}),options); 67 | dset.imgsize{n} = size(dset.img{n}); 68 | end 69 | 70 | 71 | % check to see if we should combine these datasets 72 | combine_datasets = false; 73 | 74 | % Is there only one dataset? 75 | if length(exps)>1 76 | 77 | % Do the parameters have the same structure? 78 | sameStructure = true; 79 | try 80 | pp = [dset.pars{:}]; 81 | catch 82 | % the structures here are disimilar. These data sets should not be 83 | % combined. 84 | sameStructure = false; 85 | end 86 | 87 | if sameStructure 88 | % Does each dataset use the same sequence? 89 | seq = unique({pp.sequence}); 90 | 91 | if (numel(seq) == 1) 92 | 93 | % Does each dataset have the same label order? 94 | if isequal(dset.labels{:}) 95 | 96 | % Do all datasets have the same size? 97 | sz = cellfun(@(x) size(x),dset.img,'UniformOutput',false); 98 | if isequal(sz{:}) 99 | combine_datasets = true; 100 | end 101 | end 102 | end 103 | end 104 | else 105 | % There is only one experiment. Reduce the cell array. 106 | names = fieldnames(dset); 107 | for n=1:length(names) 108 | if iscell(dset.(names{n})) 109 | dset.(names{n}) = dset.(names{n}){1}; 110 | end 111 | end 112 | end 113 | 114 | if combine_datasets 115 | % Based upon similarity between the datasets, we have decided to 116 | % combine them into a single dataset 117 | 118 | % concatenate the image data 119 | catdim = length(sz{1})+1; 120 | img = cat(catdim,dset.img{:}); 121 | labels = [dset.labels{1} 'EXP']; 122 | 123 | % concatenate parameters 124 | pars = [dset.pars{:}]; 125 | names = fieldnames(pars(1)); 126 | for n=1:length(names) 127 | if length(pars(1).(names{n})) > 1 128 | sz = size(pars(1).(names{n})); 129 | par.(names{n}) = cat(length(sz)+1,{pars.(names{n})}); 130 | else 131 | % array concatenation 132 | try 133 | par.(names{n}) = [pars.(names{n})]; 134 | catch 135 | % this must be a structure, or something else that doesn't 136 | % match in size. concatenation it as a cell array 137 | par.(names{n}) = {pars.(names{n})}; 138 | end 139 | end 140 | 141 | if ~isstruct(par.(names{n})(1)) 142 | % if all of the parameter values are identical, replace them 143 | % with a single value. 144 | try 145 | un = unique(par.(names{n})); 146 | if length(un) == 1; 147 | par.(names{n}) = un; 148 | end 149 | catch 150 | % this must be a cell array of a matrix/vector. 151 | if isequal(par.(names{n}){:}) 152 | par.(names{n}) = par.(names{n}){1}; 153 | end 154 | end 155 | end 156 | end 157 | 158 | dset = struct(); 159 | dset.img = img; 160 | dset.imgsize = size(dset.img); 161 | dset.pars = par; 162 | dset.labels = labels; 163 | end 164 | -------------------------------------------------------------------------------- /+remmi/niftifile.m: -------------------------------------------------------------------------------- 1 | classdef niftifile < dynamicprops 2 | % niftifile is a minimalist class container to read/write nifti-1 files 3 | % 4 | % Example: 5 | % nf = niftifile('file.nii'); 6 | % 7 | % nf.img = image data in single or double format. real/complex are 8 | % supported 9 | % 10 | % or: 11 | % nf = niftifile('file.nii',data); 12 | % where data is any struct-like matlab oject containting a field 'img' 13 | % of numeric image data. All other fields in the structure are 14 | % converted to json and saved in the nifti extension header 15 | % 16 | % by Kevin Harkins (kevin.harkins@vanderbilt.edu) 17 | 18 | properties 19 | filepath % the name used to store the data 20 | img = []; 21 | end 22 | 23 | properties (Access = private) 24 | parscode = 2984; % random integer ? 25 | dirty = false; 26 | typecode; 27 | bitcode; 28 | end 29 | 30 | methods 31 | function obj = niftifile(fname,data) 32 | % niftifile(fname,data), constructor 33 | % fname = a required alphanumeric string, where the first 34 | % character must be a letter. 35 | 36 | % for now, only floats & doubles (real or complex) are used 37 | obj.typecode = containers.Map; 38 | obj.typecode('uint8') = 2; 39 | obj.typecode('int16') = 4; 40 | obj.typecode('int32') = 8; 41 | obj.typecode('single') = 16; 42 | obj.typecode('single complex') = 32; 43 | obj.typecode('double') = 64; 44 | obj.typecode('double complex') = 1792; 45 | 46 | obj.bitcode = containers.Map; 47 | obj.bitcode('uint8') = 8; 48 | obj.bitcode('int16') = 16; 49 | obj.bitcode('int32') = 32; 50 | obj.bitcode('single') = 32; 51 | obj.bitcode('single complex') = 64; 52 | obj.bitcode('double') = 64; 53 | obj.bitcode('double complex') = 128; 54 | 55 | if ~exist('fname','var') || isempty(fname) 56 | error('niftifile() requires a filename'); 57 | elseif ischar(fname) 58 | obj.filepath = fname; 59 | else 60 | error('"fname" must be a character string'); 61 | end 62 | 63 | % check to make sure we aren't writing over any saved data 64 | if exist('fname','file') && exist('data','var') 65 | error('file %s already exists',fname) 66 | end 67 | 68 | if exist('data','var') 69 | if isnumeric(data.img) 70 | obj.img = data.img; 71 | else 72 | error('img must be numeric') 73 | end 74 | 75 | fns = fieldnames(data); 76 | for n = 1:length(fns) 77 | if strcmp(fns{n},'img') 78 | continue; 79 | end 80 | obj.addprop(fns{n}); 81 | obj.(fns{n}) = data.(fns{n}); 82 | end 83 | 84 | obj.dirty = true; 85 | 86 | obj.write(); 87 | elseif exist(obj.filepath,'file') 88 | % load the current properties 89 | obj.readimg(); 90 | obj.readpars(); 91 | obj.dirty = false; 92 | end 93 | end 94 | 95 | function delete(obj) 96 | if (obj.dirty) 97 | obj.write(); 98 | end 99 | end 100 | 101 | function js = jsondynprops(obj) 102 | props = properties(obj); 103 | save_struc = struct; 104 | for n=1:length(props) 105 | if strcmp(props{n},'filepath') || strcmp(props{n},'img') 106 | continue; 107 | end 108 | save_struc.(props{n}) = obj.(props{n}); 109 | end 110 | 111 | % get size of the header extension 112 | js = jsonencode(save_struc); 113 | end 114 | 115 | 116 | function obj = subsasgn(obj,a,val) 117 | obj.dirty = true; 118 | 119 | % basic type checking 120 | if numel(a) == 1 121 | if strcmp(a(1).subs,'img') 122 | if isnumeric(val) 123 | obj.img = val; 124 | else 125 | error('img must be numeric') 126 | end 127 | elseif ~isprop(obj,a(1).subs) 128 | % we need to create this property 129 | obj.addprop(a(1).subs); 130 | obj.(a(1).subs) = val; 131 | else 132 | % carry on 133 | obj = builtin('subsasgn',obj,a,val); 134 | end 135 | else 136 | % carry on 137 | obj = builtin('subsasgn',obj,a,val); 138 | end 139 | 140 | obj.write(); 141 | end 142 | 143 | function hdr = generateHeader(obj) 144 | hdr = struct(); 145 | 146 | hdr.sizeof_hdr = int32(348); 147 | 148 | % unused by nifti-1 149 | hdr.data_type = char(zeros(10,1)); 150 | hdr.db_name = char(zeros(18,1)); 151 | hdr.extents = int32(0); 152 | hdr.session_error = int16(0); 153 | hdr.regular = char(0); 154 | 155 | % 156 | sz = size(obj.img); 157 | if length(sz)>7 158 | error('image data must have 7 or less dimensions') 159 | end 160 | sz = [length(sz) sz]; 161 | sz(length(sz)+1:8) = 1; % pad to 8 values 162 | hdr.dim_info = char(0); 163 | hdr.dim = int16(sz); 164 | hdr.intent_p1 = single(0); 165 | hdr.intent_p2 = single(0); 166 | hdr.intent_p3 = single(0); 167 | hdr.intent_code = int16(0); 168 | 169 | cl = class(obj.img); 170 | if ~isreal(obj.img) 171 | cl = strcat(cl,' complex'); 172 | end 173 | 174 | if ~any(strcmp(keys(obj.bitcode),cl)) 175 | error('data type is not recognized') 176 | end 177 | 178 | hdr.datatype = int16(obj.typecode(cl)); 179 | hdr.bitpix = int16(obj.bitcode(cl)); 180 | hdr.slice_start = int16(0); 181 | 182 | hdr.pixdim = single([1 1 1 1 1 1 1 1]); 183 | js = jsondynprops(obj); 184 | if ~isempty(js) 185 | hdre_sz = length(js) + 8; 186 | hdre_sz = ceil(hdre_sz/16)*16; % must be a multiple of 16 187 | else 188 | hdre_sz = 0; 189 | end 190 | hdr.vox_offset = 352 + hdre_sz; 191 | 192 | hdr.scl_slope = single(0); 193 | hdr.scl_inter = single(0); 194 | hdr.slice_end = int16(0); 195 | hdr.slice_code = char(0); 196 | hdr.xyzt_units = char(2+16); 197 | hdr.cal_max = single(0); 198 | hdr.cal_min = single(0); 199 | hdr.slice_duration = single(0); 200 | hdr.toffset = single(0); 201 | hdr.glmax = int32(0); 202 | hdr.glmin = int32(0); 203 | hdr.descrip = char(zeros(1,80)); 204 | hdr.aux_file = char(zeros(1,24)); 205 | hdr.qform_code = int16(0); 206 | hdr.sform_code = int16(0); 207 | hdr.quatern_b = single(0); 208 | hdr.quatern_c = single(0); 209 | hdr.quatern_d = single(0); 210 | hdr.qoffset_x = single(0); 211 | hdr.qoffset_y = single(0); 212 | hdr.qoffset_z = single(0); 213 | hdr.srow_x = single(zeros(4,1)); 214 | hdr.srow_y = single(zeros(4,1)); 215 | hdr.srow_z = single(zeros(4,1)); 216 | hdr.intent_name = char(zeros(1,16)); 217 | hdr.magic = ['n+1' 0]; 218 | hdr.ext = char([1 0 0 0]); 219 | end 220 | 221 | function write(obj) 222 | 223 | hdr = obj.generateHeader(); 224 | 225 | % open the file 226 | fid = fopen(obj.filepath,'w','ieee-le'); 227 | 228 | if fid<0 229 | error('could not open %s', obj.filepath); 230 | end 231 | 232 | % write the header 233 | fwrite(fid,hdr.sizeof_hdr,'int32'); 234 | fwrite(fid,hdr.data_type,'char'); 235 | fwrite(fid,hdr.db_name,'char'); 236 | fwrite(fid,hdr.extents,'int32'); 237 | fwrite(fid,hdr.session_error,'int16'); 238 | fwrite(fid,hdr.regular,'char'); 239 | fwrite(fid,hdr.dim_info,'char'); 240 | fwrite(fid,hdr.dim,'int16'); 241 | fwrite(fid,hdr.intent_p1,'single'); 242 | fwrite(fid,hdr.intent_p2,'single'); 243 | fwrite(fid,hdr.intent_p3,'single'); 244 | fwrite(fid,hdr.intent_code,'int16'); 245 | fwrite(fid,hdr.datatype,'int16'); 246 | fwrite(fid,hdr.bitpix,'int16'); 247 | fwrite(fid,hdr.slice_start,'int16'); 248 | fwrite(fid,hdr.pixdim,'single'); 249 | fwrite(fid,hdr.vox_offset,'single'); 250 | fwrite(fid,hdr.scl_slope,'single'); 251 | fwrite(fid,hdr.scl_inter,'single'); 252 | fwrite(fid,hdr.slice_end,'int16'); 253 | fwrite(fid,hdr.slice_code,'char'); 254 | fwrite(fid,hdr.xyzt_units,'char'); 255 | fwrite(fid,hdr.cal_max,'single'); 256 | fwrite(fid,hdr.cal_min,'single'); 257 | fwrite(fid,hdr.slice_duration,'single'); 258 | fwrite(fid,hdr.toffset,'single'); 259 | fwrite(fid,hdr.glmax,'int32'); 260 | fwrite(fid,hdr.glmin,'int32'); 261 | fwrite(fid,hdr.descrip,'char'); 262 | fwrite(fid,hdr.aux_file,'char'); 263 | fwrite(fid,hdr.qform_code,'int16'); 264 | fwrite(fid,hdr.sform_code,'int16'); 265 | fwrite(fid,hdr.quatern_b,'single'); 266 | fwrite(fid,hdr.quatern_c,'single'); 267 | fwrite(fid,hdr.quatern_d,'single'); 268 | fwrite(fid,hdr.qoffset_x,'single'); 269 | fwrite(fid,hdr.qoffset_y,'single'); 270 | fwrite(fid,hdr.qoffset_z,'single'); 271 | fwrite(fid,hdr.srow_x,'single'); 272 | fwrite(fid,hdr.srow_y,'single'); 273 | fwrite(fid,hdr.srow_z,'single'); 274 | fwrite(fid,hdr.intent_name,'char'); 275 | fwrite(fid,hdr.magic,'char'); 276 | 277 | if ftell(fid) ~= 348 278 | error('something is wrong') 279 | end 280 | 281 | % write the header exentsion 282 | 283 | % get size of the header extension 284 | js = jsondynprops(obj); 285 | extsz = length(js)+8; 286 | extsz = ceil(extsz/16)*16; 287 | js(extsz-8) = ' '; 288 | 289 | if extsz > 0 290 | fwrite(fid,char([1 0 0 0]),'char'); 291 | 292 | fwrite(fid,int32(extsz),'int32'); 293 | fwrite(fid,int32(obj.parscode),'int32'); 294 | fwrite(fid,js,'char'); 295 | else 296 | fwrite(fid,char([0 0 0 0]),'char'); 297 | end 298 | 299 | 300 | if ftell(fid) ~= hdr.vox_offset 301 | error('something is wrong v2') 302 | end 303 | 304 | % now, write the data 305 | if isreal(obj.img) 306 | fwrite(fid,obj.img,class(obj.img)); 307 | else % data is complex 308 | r = real(obj.img); 309 | i = imag(obj.img); 310 | r = cat(8,r,i); 311 | r = permute(r,[8 1:7]); 312 | fwrite(fid,r,class(r)); 313 | end 314 | 315 | % close the file 316 | fclose(fid); 317 | 318 | obj.dirty = false; 319 | 320 | end 321 | 322 | function hdr = readheader(obj) 323 | % open the file 324 | fid = fopen(obj.filepath,'r','ieee-le'); 325 | 326 | if fid<0 327 | error('could not open %s', obj.filepath); 328 | end 329 | 330 | hdr = struct(); 331 | 332 | % write the header 333 | hdr.sizeof_hdr = fread(fid,1,'*int32'); 334 | hdr.data_type = fread(fid,10,'*char')'; 335 | hdr.db_name = fread(fid,18,'*char')'; 336 | hdr.extents = fread(fid,1,'*int32'); 337 | hdr.session_error = fread(fid,1,'*int16'); 338 | hdr.regular = fread(fid,1,'*char')'; 339 | hdr.dim_info = fread(fid,1,'*char')'; 340 | hdr.dim = fread(fid,8,'*int16'); 341 | hdr.intent_p1 = fread(fid,1,'*single'); 342 | hdr.intent_p2 = fread(fid,1,'*single'); 343 | hdr.intent_p3 = fread(fid,1,'*single'); 344 | hdr.intent_code = fread(fid,1,'*int16'); 345 | hdr.datatype = fread(fid,1,'*int16'); 346 | hdr.bitpix = fread(fid,1,'*int16'); 347 | hdr.slice_start = fread(fid,1,'*int16'); 348 | hdr.pixdim = fread(fid,8,'*single'); 349 | hdr.vox_offset = fread(fid,1,'*single'); 350 | hdr.scl_slope = fread(fid,1,'*single'); 351 | hdr.scl_inter = fread(fid,1,'*single'); 352 | hdr.slice_end = fread(fid,1,'*int16'); 353 | hdr.slice_code = fread(fid,1,'*char')'; 354 | hdr.xyzt_units = fread(fid,1,'*char')'; 355 | hdr.cal_max = fread(fid,1,'*single'); 356 | hdr.cal_min = fread(fid,1,'*single'); 357 | hdr.slice_duration = fread(fid,1,'*single'); 358 | hdr.toffset = fread(fid,1,'*single'); 359 | hdr.glmax = fread(fid,1,'*int32'); 360 | hdr.glmin = fread(fid,1,'*int32'); 361 | hdr.descrip = fread(fid,80,'*char')'; 362 | hdr.aux_file = fread(fid,24,'*char')'; 363 | hdr.qform_code = fread(fid,1,'*int16'); 364 | hdr.sform_code = fread(fid,1,'*int16'); 365 | hdr.quatern_b = fread(fid,1,'*single'); 366 | hdr.quatern_c = fread(fid,1,'*single'); 367 | hdr.quatern_d = fread(fid,1,'*single'); 368 | hdr.qoffset_x = fread(fid,1,'*single'); 369 | hdr.qoffset_y = fread(fid,1,'*single'); 370 | hdr.qoffset_z = fread(fid,1,'*single'); 371 | hdr.srow_x = fread(fid,4,'*single'); 372 | hdr.srow_y = fread(fid,4,'*single'); 373 | hdr.srow_z = fread(fid,4,'*single'); 374 | hdr.intent_name = fread(fid,16,'*char')'; 375 | hdr.magic = fread(fid,4,'*char')'; 376 | end 377 | 378 | function readpars(obj) 379 | 380 | hdr = obj.readheader(); 381 | 382 | if hdr.vox_offset == 352 383 | % there are no extensions 384 | return 385 | end 386 | 387 | fid = fopen(obj.filepath,'r','ieee-le'); 388 | 389 | if fid<0 390 | error(['file not found: ' obj.filepath]) 391 | end 392 | 393 | f1 = fread(fid,1,'int32'); 394 | if f1 ~= 348 395 | fclose(fid); 396 | fid = fopen(obj.filepath,'r','ieee-be'); 397 | f1 = fread(fid,1,'int32'); 398 | 399 | if f1 ~= 348 400 | error(['not a nifti file: ' obj.filepath]) 401 | end 402 | end 403 | 404 | fseek(fid,348,'bof'); 405 | ex_info = fread(fid,4)'; 406 | 407 | if ex_info(1) == 0 408 | % there are no extensions 409 | fclose(fid); 410 | return; 411 | end 412 | 413 | i = 1; 414 | while (ftell(fid) < hdr.vox_offset) 415 | esize = fread(fid,1,'int32'); 416 | ecode = fread(fid,1,'int32'); 417 | edata = char(fread(fid,esize-8)'); 418 | edata = strtrim(edata(edata~=0)); 419 | 420 | if ecode == obj.parscode 421 | istr = jsondecode(edata); 422 | f = fieldnames(istr); 423 | for n=1:length(f) 424 | obj.addprop(f{n}); 425 | obj.(f{n}) = istr.(f{n}); 426 | end 427 | end 428 | i = i+1; 429 | end 430 | fclose(fid); 431 | end 432 | 433 | function readimg(obj) 434 | 435 | hdr = obj.readheader(); 436 | 437 | % what kind of data is this? 438 | idx = cellfun(@(x) x==hdr.datatype,obj.typecode.values); 439 | key = obj.typecode.keys; 440 | str = key{idx}; 441 | cpx = strfind(str,'complex'); 442 | 443 | mult=1; 444 | if cpx 445 | str = str(1:end-8); 446 | mult=2; 447 | end 448 | 449 | shp = hdr.dim(2:hdr.dim(1)+1); 450 | 451 | fid = fopen(obj.filepath,'r','ieee-le'); 452 | if fid<0 453 | error(['file not found: ' obj.filepath]) 454 | end 455 | 456 | fseek(fid,hdr.vox_offset,'bof'); 457 | obj.img = fread(fid,prod(shp)*mult,['*' str]); 458 | fclose(fid); 459 | 460 | if cpx 461 | obj.img = reshape(obj.img,2,[]); 462 | obj.img = obj.img(1,:) + 1i*obj.img(2,:); 463 | end 464 | 465 | obj.img = reshape(obj.img,shp'); 466 | 467 | end 468 | end 469 | end 470 | -------------------------------------------------------------------------------- /+remmi/niftispace.m: -------------------------------------------------------------------------------- 1 | classdef niftispace < dynamicprops 2 | % niftispace reads/writes niftifiles in a directory. 3 | % 4 | % Example: 5 | % 6 | % ns = niftispace(); % use pwd as the current folder path 7 | % ns = niftispace('/path/to/folder/'); 8 | % 9 | % any .nii files with valid filenames in the folder will be accessible 10 | % via class property as a niftifile object 11 | % 12 | % To create a new niftifile 'arbitrary_name.nii': 13 | % ns.arbitrary_name = test; 14 | % 15 | % where test is any struct like object that contains: 16 | % test.img = numeric image data 17 | % any other data stored in test is converted to json in niftifile and 18 | % stored in an extension header 19 | % 20 | % by Kevin Harkins (kevin.harkins@vanderbilt.edu) 21 | 22 | properties 23 | folderpath % the name used to store the data 24 | end 25 | 26 | methods 27 | function obj = niftispace(fpath) 28 | % niftispace(fpath), constructor 29 | % fpath = an optional folder path 30 | 31 | if ~exist('fpath','var') 32 | fpath = pwd; 33 | end 34 | 35 | if ~exist(fpath,'dir') 36 | error('fpath must exist') 37 | end 38 | 39 | obj.folderpath = fpath; 40 | 41 | files = dir(fullfile(obj.folderpath,'*.nii')); 42 | 43 | for n=1:length(files) 44 | fname = fullfile(files(n).folder,files(n).name); 45 | [~,name,~] = fileparts(fname); 46 | try 47 | nfile = remmi.niftifile(fname); 48 | 49 | name = strrep(name,'.','_'); 50 | name = strrep(name,'(',''); 51 | name = strrep(name,')',''); 52 | 53 | obj.addprop(name); 54 | obj.(name) = nfile; 55 | catch 56 | warning('%s could not be loaded',fname); 57 | end 58 | 59 | end 60 | end 61 | 62 | function obj = subsasgn(obj,a,val) 63 | 64 | if isprop(obj,a(1).subs) 65 | % this item already exists 66 | 67 | if numel(a) == 1 68 | if isa(val,'niftifile') || isstruct(val) 69 | % assign niftifile object directly 70 | obj.(a(1).subs) = val; 71 | else 72 | error('value cannot be assigned'); 73 | end 74 | else % numel(a) > 1 75 | if strcmp(a(2).type,'.') 76 | % carry on 77 | obj = builtin('subsasgn',obj,a,val); 78 | else 79 | error('niftispace properites cannot be arrayed'); 80 | end 81 | end 82 | else 83 | % we need to create this property. 84 | if numel(a) > 1 85 | error('assigned structure is not the correct format'); 86 | end 87 | 88 | % val should be a struct like object, containing the field 89 | % img 90 | try 91 | val.img; 92 | catch 93 | error('img property required'); 94 | end 95 | 96 | if ~isvarname(a(1).subs) 97 | error('property should be a valid variable name'); 98 | end 99 | 100 | fname = fullfile(obj.folderpath,[a(1).subs '.nii']); 101 | nf = remmi.niftifile(fname,val); 102 | 103 | % create and assign the property 104 | obj.addprop(a(1).subs); 105 | obj.(a(1).subs) = nf; 106 | end 107 | end 108 | end 109 | end -------------------------------------------------------------------------------- /+remmi/recon.m: -------------------------------------------------------------------------------- 1 | function dset = recon(dset,options) 2 | % dset = remmi.recon(dset,options) loads raw data from recognized 3 | % pre-clinical MRI data formats, and returns reconstructed images and 4 | % parameters. 5 | % 6 | % optional inputs: 7 | % dset.spath = string path to a study stored in a recognized vender format 8 | % dset.exps = list of experiments to reconstruct within spath 9 | % options.apodize_fn = function to apodize data 10 | % 11 | % output: 12 | % dset = a structure (or array of structures) containing: 13 | % dset.img = reconstructed images 14 | % dset.pars = list of experimental parameters used during image 15 | % acquisition 16 | % 17 | % Kevin Harkins & Mark Does, Vanderbilt University 18 | % for the REMMI Toolbox 19 | 20 | if nargin<1 21 | dset = struct(); 22 | end 23 | 24 | persistent rpath 25 | 26 | if ~isfield(dset,'spath') || isempty(dset.spath) 27 | disp('Select the study directory'); 28 | dset.spath = uigetdir(rpath,'Select study directory'); 29 | if dset.spath == 0 30 | error('No study path given'); 31 | end 32 | rpath = dset.spath; 33 | disp(['The study path is: ' dset.spath]); 34 | end 35 | 36 | study = remmi.vendors.autoVendor(dset.spath); 37 | 38 | if ~isfield(dset,'exps') || isempty(dset.exps) 39 | disp('Select the experiment(s)'); 40 | exps = study.list(); 41 | sel = listdlg('ListString',exps.name); 42 | exps = exps.id(sel); 43 | if isempty(exps) 44 | error('No experiments specified in %s',spath); 45 | end 46 | else 47 | exps = dset.exps; 48 | end 49 | 50 | % set the default reconstruction options 51 | if nargin<2 52 | options = struct(); 53 | end 54 | options = remmi.recon.options(options); 55 | 56 | if ~iscell(exps) 57 | if ischar(exps) 58 | exps = {exps}; 59 | else 60 | exps = num2cell(exps); 61 | end 62 | end 63 | 64 | dset.exps = exps; 65 | 66 | % load all the datasets 67 | for n=1:length(exps) 68 | [dset.img{n},dset.labels{n},dset.pars{n}] = study.load(num2str(exps{n}),options); 69 | dset.imgsize{n} = size(dset.img{n}); 70 | end 71 | 72 | % check to see if we should combine these datasets 73 | combine_datasets = false; 74 | 75 | % Is there only one dataset? 76 | if length(exps)>1 77 | 78 | % Do the parameters have the same structure? 79 | sameStructure = true; 80 | try 81 | pp = [dset.pars{:}]; 82 | catch 83 | % the structures here are disimilar. These data sets should not be 84 | % combined. 85 | sameStructure = false; 86 | end 87 | 88 | if sameStructure 89 | % Does each dataset use the same sequence? 90 | seq = unique({pp.sequence}); 91 | 92 | if (numel(seq) == 1) 93 | 94 | % Does each dataset have the same label order? 95 | if isequal(dset.labels{:}) 96 | 97 | % Do all datasets have the same size? 98 | sz = cellfun(@(x) size(x),dset.img,'UniformOutput',false); 99 | if isequal(sz{:}) 100 | combine_datasets = true; 101 | end 102 | end 103 | end 104 | end 105 | else 106 | % There is only one experiment. Reduce the cell array. 107 | names = fieldnames(dset); 108 | for n=1:length(names) 109 | if iscell(dset.(names{n})) 110 | dset.(names{n}) = dset.(names{n}){1}; 111 | end 112 | end 113 | end 114 | 115 | if combine_datasets 116 | % Based upon similarity between the datasets, we have decided to 117 | % combine them into a single dataset 118 | 119 | % concatenate the image data 120 | catdim = length(sz{1})+1; 121 | img = cat(catdim,dset.img{:}); 122 | labels = [dset.labels{1} 'EXP']; 123 | 124 | % concatenate parameters 125 | pars = [dset.pars{:}]; 126 | names = fieldnames(pars(1)); 127 | for n=1:length(names) 128 | if length(pars(1).(names{n})) > 1 129 | sz = size(pars(1).(names{n})); 130 | par.(names{n}) = cat(length(sz)+1,{pars.(names{n})}); 131 | else 132 | % array concatenation 133 | try 134 | par.(names{n}) = [pars.(names{n})]; 135 | catch 136 | % this must be a structure, or something else that doesn't 137 | % match in size. concatenation it as a cell array 138 | par.(names{n}) = {pars.(names{n})}; 139 | end 140 | end 141 | 142 | if ~isstruct(par.(names{n})(1)) 143 | % if all of the parameter values are identical, replace them 144 | % with a single value. 145 | try 146 | un = unique(par.(names{n})); 147 | if length(un) == 1; 148 | par.(names{n}) = un; 149 | end 150 | catch 151 | % this must be a cell array of a matrix/vector. 152 | if isequal(par.(names{n}){:}) 153 | par.(names{n}) = par.(names{n}){1}; 154 | end 155 | end 156 | end 157 | end 158 | 159 | dset = struct(); 160 | dset.img = img; 161 | dset.imgsize = size(dset.img); 162 | dset.pars = par; 163 | dset.labels = labels; 164 | end -------------------------------------------------------------------------------- /+remmi/version.m: -------------------------------------------------------------------------------- 1 | function ver = version() 2 | % ver = remmi.version() 3 | % 4 | % returns a char array showing the current remmi-matlab version number. 5 | % 6 | % Kevin Harkins & Mark Does, Vanderbilt University 7 | % for the REMMI Toolbox 8 | 9 | ver = '0.1.6'; 10 | end -------------------------------------------------------------------------------- /+remmi/workspace.m: -------------------------------------------------------------------------------- 1 | classdef workspace < dynamicprops 2 | % remmi.workspace is a general container for any kind of data, especially 3 | % given or returned from a remmi process. This class can effectively be 4 | % used the same way as a matlab structure, except the data is 5 | % simultaneously stored in the filename given to the constructor. This 6 | % class should be more efficient than matfile, as the data is also 7 | % stored in memory. 8 | % 9 | % Example: 10 | % rws = remmi.workspace('test.mat'); 11 | % rws.noise = randn(100); 12 | % 13 | % A structure can also be passed to remmi.workspace to preload fields 14 | % into the workspace. Example: 15 | % info.spath = '/data/studyname/'; 16 | % info.exps = 10; 17 | % rws = remmi.workspace('test.mat',info); 18 | % 19 | % Kevin Harkins & Mark Does, Vanderbilt University 20 | % for the REMMI Toolbox 21 | 22 | properties 23 | filename % the name used to store the data 24 | end 25 | 26 | methods 27 | function obj = workspace(fname,initdata) 28 | % workspace(name), contructor 29 | % name = a required alphanumeric string, where the first 30 | % character must be a letter. 31 | 32 | % "name" must be a string with an alpha first character 33 | if exist(fname,'var') || isempty(fname) 34 | error('workspace() requires a filename'); 35 | elseif ischar(fname) 36 | obj.filename = fname; 37 | else 38 | error('"fname" must be a character string'); 39 | end 40 | 41 | [p,n,ext] = fileparts(obj.filename); 42 | if ~endsWith(ext,'.mat') 43 | % the extention needs to end with '.mat', otherwise weird 44 | % behaviour can occur 45 | ext = strcat(ext,'.mat'); 46 | end 47 | if isempty(p) 48 | obj.filename = fullfile(pwd,[n ext]); 49 | end 50 | 51 | % does this file exist? If so, pre-load it 52 | if exist(obj.filename,'file') 53 | % load the current properties 54 | data = load(obj.filename); 55 | fields = fieldnames(data); 56 | 57 | for n=1:length(fields) 58 | p = obj.addprop(fields{n}); 59 | obj.(fields{n}) = data.(fields{n}); 60 | p.SetMethod = remmi.workspace.createSetMethod(fields{n}); 61 | end 62 | end 63 | 64 | % was a structure given to initialize the workspace? 65 | if exist('initdata','var') 66 | fields = fieldnames(initdata); 67 | 68 | for n=1:length(fields) 69 | if isprop(obj,fields{n}) 70 | obj.(fields{n}) = initdata.(fields{n}); 71 | else 72 | obj.add(fields{n},initdata.(fields{n})); 73 | end 74 | end 75 | end 76 | end 77 | 78 | function add(obj,name,data) 79 | p = obj.addprop(name); 80 | p.SetMethod = remmi.workspace.createSetMethod(name); 81 | 82 | if exist('data','var') 83 | obj.(name) = data; 84 | end 85 | end 86 | 87 | function obj = subsasgn(obj,a,val) 88 | if isprop(obj,a(1).subs) 89 | obj = builtin('subsasgn',obj,a,val); 90 | else 91 | % we need to create this property. 92 | % first recreate val if a is further subreferenced 93 | if numel(a) > 1 94 | if ~strcmp(a(1).type,'.') 95 | error('the class remmi.workspace cannot be subreferenced') 96 | end 97 | 98 | if strcmp(a(2).type, '.') 99 | str = struct(); 100 | elseif strcmp(a(2).type,'()') 101 | str = []; 102 | elseif strcmp(a(2).type,'{}') 103 | str = {}; 104 | end 105 | val = subsasgn(str,a(2:end),val); 106 | end 107 | 108 | % create and assign the property 109 | obj.add(a(1).subs,val); 110 | end 111 | end 112 | 113 | function a = saveobj(obj) 114 | % be smart about saving a remmi.workspace to file... 115 | a.filename = obj.filename; 116 | end 117 | end 118 | 119 | methods (Static) 120 | function obj = loadobj(a) 121 | % be smart about loading a remmi.workspace from file... 122 | obj = remmi.workspace(a.filename); 123 | end 124 | end 125 | 126 | methods (Static, Access = private) 127 | function method = createSetMethod(name) 128 | method = @setmethod; 129 | 130 | function setmethod(obj, value) 131 | obj.(name) = value; 132 | tmp.(name) = value; 133 | 134 | if exist(obj.filename,'file') 135 | save(obj.filename,'-struct','tmp','-append') 136 | else 137 | ver = remmi.util.strsplit(version(),{'.' ' '}); 138 | ver = str2double([ver{1} '.' ver{2}]); 139 | if ver < 7.3 % earlier than R2006a 140 | save(obj.filename,'-struct','tmp') 141 | else % R2006a or later 142 | save(obj.filename,'-struct','tmp');%,'-v7.3') 143 | end 144 | end 145 | 146 | [~,id] = lastwarn(); 147 | lastwarn(''); % clear other warnings 148 | if strcmp(id,'MATLAB:save:sizeTooBigForMATFile') 149 | warning(['The property ''' name ''' was not saved to file: ' obj.filename]); 150 | [p,n,ext] = fileparts(obj.filename); 151 | fn = fullfile(p,[n '.' name ext]); 152 | save(fn,'-struct','tmp','-v7.3') 153 | [~,id] = lastwarn; 154 | if isempty(id) 155 | disp(['No data has been lost: ''' name ''' was instead saved to ' fn]) 156 | else 157 | warning(['Something is wrong: ''' name ''' could not be saved.']) 158 | end 159 | end 160 | 161 | end 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | */.DS_store -------------------------------------------------------------------------------- /README.html: -------------------------------------------------------------------------------- 1 |

About REMMI-matlab

2 |

REMMI-Matlab is a toolbox for processing MRI data from supported small animal imaging vendors (mainly, Bruker and Varian/Agilent). We aim for this toolbox to provide easy access to advanced MRI measures of magnetization transfer, multi-exponential T2 (including the myelin water fraction), diffusion, etc.

3 |

REMMI-Matlab is written by Kevin Harkins (kevin.harkins@vanderbilt.edu) and Mark Does (mark.does@vanderbilt.edu), Vanderbilt University. This work is supported by NIH Grant TK

4 |

REMMI-Matlab Examples

5 |

‘Easy’ examples for processing diffusion tensor (easy_dti), magnetization transfer (easy_mtir), and multi-exponential T2 (easy_t2), data are given in the base directory. An example for advanced processing of multi- exponential T2 data is also provided (full_t2).

6 |

An Introduction to Matlab Namespaces

7 |

In Matlab, directory names preceded with a + are used as namespaces. For example, if the REMMI-Matlab base directory is in the Matlab path, the REMMI workspace class in the ./+remmi directory can be accessed as:

8 |
remmi.workspace()
9 |

Also, the recon function in that folder can be accessed as:

10 |
remmi.recon()
11 |

Inductively, the function dti() in the ./+remmi/+dwi sub-directory can be accessed with:

12 |
remmi.dwi.dti()
13 |

Saving and Loading Data

14 |

The remmi.workspace class is intended to provide easy to manage, clean workspaces of saved data. To create a new instance of a REMMI workspace:

15 |
fname = 'mydatafile.mat';
16 |
ws = remmi.workspace(fname);
17 |

where mydatafile.mat is the name of the file where data will be stored. Presently, all data is stored in Matlab’s native data format, *.mat. If mydatafile.mat already exists in the current path, any contents of that file will be automatically loaded into workspace.

18 |

Any existing Matlab structure can be converted into a REMMI workspace. For example:

19 |
info = struct();
20 |
info.data = {'cell','array','to','be','saved','to','file'};
21 |
ws = remmi.workspace(fname, info);
22 |

In practice, ws can be used like any Matlab structure, and any data put into ws is automatically saved to file. For instance, to save a 100x100 matrix of noise:

23 |
ws.noise = randn(100);
24 |

Note: For all practical purposes remmi.workspace is independent from the rest of the REMMI toolbox. There is no requirement to use this class for storing/saving data. The rest of the toolbox can be used with or without remmi.workspace. Likewise, remmi.workspace can be used with or without the rest of the toolbox.

25 |

Warning: By default, Matlab .mat files are limited to 2 GB in size. TK

26 |

Image Reconstruction

27 |

remmi.recon() is an entry-level function to simplify loading and reconstruction of MRI image data from any supported vendor.

28 |

For example:

29 |
info.spath = '/path/to/study'; % path to the study folder
30 |
info.exps = 20; % Bruker experiment number contained in info.spath
31 |
ws.images = remmi.recon(info);
32 |

Given a valid study path and list of experiments, the remmi.recon() function returns a Matlab structure containing the fields:

33 |
    34 |
  • img contains the reconstructed complex images
  • 35 |
  • labels contains a cell array of dimension labels for img
  • 36 |
  • pars is a structure of general and vendor-specific parameters used in the imaging sequence
  • 37 |
38 |

In this example, the structure returned by remmi.recon() is saved in the REMMI workspace, ws, with the variable name images.

39 |

Helper functions

40 |
    41 |
  • remmi.recon.options loads default options and processes custom options for reconstruction
  • 42 |
  • remmi.recon.ft is a function for basic 2D or 3D reconstruction of Fourier-encoded MRI data.
  • 43 |
44 |

Core REMMI functions

45 |

Most ‘Core’ REMMI functions take as a first argument and return a Matlab structure that contains both:

46 |
    47 |
  1. the data to be processed
  2. 48 |
  3. meta-data detailing what the data encodes.
  4. 49 |
50 |

For the example in above section: if info.spath and info.exps point to diffusion-tensor encoded image data, these images could be processed with:

51 |
ws.dti = remmi.dwi.dti(ws.images);
52 |

As before, the Matlab structure returned by remmi.dwi.dti() is saved in the to file with the variable name dti.

53 |

By default, most REMMI functions assume that the data to be processed exists in the field img. In the example above, image data is contained in remmi.images.img. However, the default field name can be modified. Check the function-specific help for reference.

54 |

Diffusion MRI

55 |

remmi.dwi.* contains functions for processing diffusion-weighted (DWI) data. See function-specific help for more details.

56 |

Core functions

57 |
    58 |
  • remmi.dwi.dti is a function for processing of diffusion-tensor encoded data
  • 59 |
  • remmi.dwi.addbmatrix is a function to add a vendor-specific b-matrix to structures of diffusion-encoded data
  • 60 |
61 |

Helper functions

62 |
    63 |
  • remmi.dwi.dtilin performs (simple) linear DTI analysis on raw signals
  • 64 |
  • remmi.dwi.dtinonlin performs (simple) non-linear DTI analysis (warning: this is sllllooowwww)
  • 65 |
66 |

By default, DTI signals are processed by simple linear analysis (remmi.dwi.dtilin).

67 |

Inversion-recovery

68 |

remmi.ir.* contains functions for processing inversion recovery (IR) data. See function-specific help for more details.

69 |

Core functions

70 |
    71 |
  • remmi.ir.T1 is a function for quantitative T1 analysis of inversion recovery data
  • 72 |
  • remmi.ir.qmt is a function for quantitative magnetization transfer (qmt) analysis of selective inversion recovery data
  • 73 |
74 |

Helper functions

75 |
    76 |
  • remmi.ir.sir solves the Bloch equations for signals from 2- compartment selective inversion recovery experiments
  • 77 |
  • remmi.ir.ir contains signal equations for inversion-recovery experiments
  • 78 |
79 |

Multiple-spin echo

80 |

remmi.mse.* contains functions for processing multiple spin echo (MSE) data. See function-specific help for more details.

81 |

Core functions

82 |
    83 |
  • remmi.mse.analysis is a function for generalized processing of relaxometry data, based upon the MERA toolbox
  • 84 |
  • remmi.mse.mT2 is a function for processing of EPG-based multi- exponential T2 (mT2) analysis of MSE data.
  • 85 |
86 |

Helper functions

87 |
    88 |
  • remmi.mse.mT2options loads default options and processes custom options for remmi.mse.mT2
  • 89 |
90 |

Region of Interest

91 |

remmi.roi.* contains functions for only basic region of interest (ROI) analysis. See function-specific help for more details.

92 |

Core functions

93 |
    94 |
  • remmi.roi.draw is a function for drawing and processing ROIs
  • 95 |
  • remmi.roi.copy copies ROIs and analysis from one structure to another
  • 96 |
97 |

Other Utilities

98 |

remmi.util.* contains utility functions that are independent of any other namespace

99 |

Core functions

100 |
    101 |
  • remmi.util.slice is a function to slice through multi-dimensional data
  • 102 |
  • remmi.util.thresholdmask is a function to create a threshold mask from image data
  • 103 |
104 |

Helper functions

105 |
    106 |
  • remmi.util.apodize provides Tukey window anodization of a 1D, 2D or 3D array
  • 107 |
  • remmi.util.githash returns the hash number of current git commit for REMMI-Matlab. This will make it easier to reprocess of data from older versions of REMMI-Matlab
  • 108 |
  • remmi.util.snrCalc calculates the SNR of an image
  • 109 |
  • remmi.util.strsplit provides backwards compatibility for Matlab distributions that don’t include strsplit
  • 110 |
111 |

MRI Vendor Support

112 |

remmi.vendors.* contains classes and functions for vendor-specific loading of data and parameter files

113 |

Classes

114 |
    115 |
  • remmi.vendors.BrukerPV is a class for loading Bruker PV5 & PV6 data and parameter files
  • 116 |
  • remmi.vendors.Agilent is a class for loading Varian/Agilent data and parameter files
  • 117 |
118 |

Helper functions

119 |
    120 |
  • remmi.vendors.autoVendor detects if the file structure within a study path matches that of a supported vendor
  • 121 |
  • remmi.vendors.bmatBruker is a function to calculate a b-matrix from the remmiRARE sequence
  • 122 |
  • remmi.vendors.loadBruker is a function to load Bruker data
  • 123 |
  • remmi.vendors.loadAgilent is a function to load Agilent data
  • 124 |
  • remmi.vendors.parsBruker is a function to load Bruker parameter files
  • 125 |
  • remmi.vendors.parsAgilent is a function to load Agilent parameter files
  • 126 |
127 |

Known Bugs

128 |

Bug Reporting

129 |

Please report bugs through this git repository. Alternatively, you can email kevin.harkins@vanderbilt.edu

130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # About REMMI-matlab 3 | 4 | REMMI-Matlab is a toolbox for processing MRI data from supported small 5 | animal imaging vendors (mainly, Bruker and Varian/Agilent). We aim for this 6 | toolbox to provide easy access to advanced MRI measures of magnetization 7 | transfer, multi-exponential T2 (including the myelin water fraction), 8 | diffusion, etc. 9 | 10 | REMMI-Matlab is written by Kevin Harkins () 11 | and Mark Does (), Vanderbilt University. This 12 | work is supported by NIH Grant EB019980. 13 | 14 | 15 | # REMMI-Matlab Examples 16 | 17 | 'Easy' examples for processing diffusion tensor (`easy_dti`), magnetization 18 | transfer (`easy_mtir`), and multi-exponential T2 (`easy_t2`), data are 19 | given in the base directory. An example for advanced processing of multi- 20 | exponential T2 data is also provided (`full_t2`). 21 | 22 | 23 | # An Introduction to Matlab Namespaces 24 | 25 | In Matlab, directory names preceded with a `+` are used as _namespaces_. 26 | For example, if the REMMI-Matlab base directory is in the Matlab path, the 27 | REMMI `workspace` class in the `./+remmi` directory can be accessed as: 28 | 29 | ```Matlab 30 | remmi.workspace() 31 | ``` 32 | 33 | Also, the `recon` function in that folder can be accessed as: 34 | 35 | ```Matlab 36 | remmi.recon() 37 | ``` 38 | 39 | Inductively, the function `dti()` in the `./+remmi/+dwi` sub-directory can 40 | be accessed with: 41 | 42 | ```Matlab 43 | remmi.dwi.dti() 44 | ``` 45 | 46 | 47 | # Saving and Loading Data 48 | 49 | The `remmi.workspace` class is intended to provide easy to manage, clean 50 | workspaces of saved data. To create a new instance of a REMMI workspace: 51 | 52 | ```Matlab 53 | fname = 'mydatafile.mat'; 54 | ws = remmi.workspace(fname); 55 | ``` 56 | 57 | where `mydatafile.mat` is the name of the file where data will be 58 | stored. Presently, all data is stored in Matlab's native data format, 59 | `*.mat`. If `mydatafile.mat` already exists in the current path, any 60 | contents of that file will be automatically loaded into workspace. 61 | 62 | Any existing Matlab structure can be converted into a REMMI workspace. For 63 | example: 64 | 65 | ```Matlab 66 | info = struct(); 67 | info.data = {'cell','array','to','be','saved','to','file'}; 68 | ws = remmi.workspace(fname, info); 69 | ``` 70 | 71 | In practice, `ws` can be used like any Matlab structure, and any data put 72 | into `ws` is automatically saved to file. For instance, to save a 100x100 73 | matrix of noise: 74 | 75 | ```Matlab 76 | ws.noise = randn(100); 77 | ``` 78 | 79 | **Note**: For all practical purposes `remmi.workspace` is independent from 80 | the rest of the REMMI toolbox. There is no requirement to use this class 81 | for storing/saving data. The rest of the toolbox can be used with or 82 | without `remmi.workspace`. Likewise, `remmi.workspace` can be used with or 83 | without the rest of the toolbox. 84 | 85 | **Warning**: By default, Matlab `.mat` files are limited to 2 GB in size. 86 | **TK** 87 | 88 | # Image Reconstruction 89 | 90 | `remmi.recon()` is an entry-level function to simplify loading and 91 | reconstruction of MRI image data from any supported vendor. 92 | 93 | For example: 94 | 95 | ```Matlab 96 | info.spath = '/path/to/study'; % path to the study folder 97 | info.exps = 20; % Bruker experiment number contained in info.spath 98 | ws.images = remmi.recon(info); 99 | ``` 100 | 101 | Given a valid study path and list of experiments, the `remmi.recon()` 102 | function returns a Matlab structure containing the fields: 103 | 104 | * `img` contains the reconstructed complex images 105 | * `labels` contains a cell array of dimension labels for `img` 106 | * `pars` is a structure of general and vendor-specific parameters used in 107 | the imaging sequence 108 | 109 | In this example, the structure returned by `remmi.recon()` is saved in the 110 | REMMI workspace, `ws`, with the variable name `images`. 111 | 112 | Helper functions 113 | 114 | * `remmi.recon.options` loads default options and processes custom 115 | options for reconstruction 116 | * `remmi.recon.ft` is a function for basic 2D or 3D reconstruction of 117 | Fourier-encoded MRI data. 118 | 119 | 120 | # Core REMMI functions 121 | 122 | Most 'Core' REMMI functions take as a first argument and return a Matlab 123 | structure that contains both: 124 | 125 | 1. the data to be processed 126 | 2. meta-data detailing what the data encodes. 127 | 128 | For the example in above section: if `info.spath` and `info.exps` point to 129 | diffusion-tensor encoded image data, these images could be processed with: 130 | 131 | ```Matlab 132 | ws.dti = remmi.dwi.dti(ws.images); 133 | ``` 134 | 135 | As before, the Matlab structure returned by `remmi.dwi.dti()` is saved in 136 | the to file with the variable name `dti`. 137 | 138 | By default, most REMMI functions assume that the data to be processed 139 | exists in the field `img`. In the example above, image data is contained in 140 | `remmi.images.img`. However, the default field name can be modified. Check 141 | the function-specific help for reference. 142 | 143 | 144 | # Diffusion MRI 145 | 146 | `remmi.dwi.*` contains functions for processing diffusion-weighted (DWI) 147 | data. See function-specific help for more details. 148 | 149 | ## Core functions 150 | * `remmi.dwi.dti` is a function for processing of diffusion-tensor encoded 151 | data 152 | * `remmi.dwi.addbmatrix` is a function to add a vendor-specific b-matrix to 153 | structures of diffusion-encoded data 154 | 155 | ## Helper functions 156 | 157 | * `remmi.dwi.dtilin` performs (simple) linear DTI analysis on raw signals 158 | * `remmi.dwi.dtinonlin` performs (simple) non-linear DTI analysis (warning: 159 | this is sllllooowwww) 160 | 161 | By default, DTI signals are processed by simple linear analysis 162 | (`remmi.dwi.dtilin`). 163 | 164 | 165 | # Inversion-recovery 166 | 167 | `remmi.ir.*` contains functions for processing inversion recovery (IR) 168 | data. See function-specific help for more details. 169 | 170 | ## Core functions 171 | 172 | * `remmi.ir.T1` is a function for quantitative T1 analysis of inversion 173 | recovery data 174 | * `remmi.ir.qmt` is a function for quantitative magnetization transfer 175 | (qmt) analysis of selective inversion recovery data 176 | 177 | ## Helper functions 178 | 179 | * `remmi.ir.sir` solves the Bloch equations for signals from 2- 180 | compartment selective inversion recovery experiments 181 | * `remmi.ir.ir` contains signal equations for inversion-recovery 182 | experiments 183 | 184 | 185 | # Multiple-spin echo 186 | 187 | `remmi.mse.*` contains functions for processing multiple spin echo (MSE) 188 | data. See function-specific help for more details. 189 | 190 | ## Core functions 191 | 192 | * `remmi.mse.analysis` is a function for generalized processing of 193 | relaxometry data, based upon the MERA toolbox 194 | * `remmi.mse.mT2` is a function for processing of EPG-based multi- 195 | exponential T2 (mT2) analysis of MSE data. 196 | 197 | ## Helper functions 198 | 199 | * `remmi.mse.mT2options` loads default options and processes custom 200 | options for `remmi.mse.mT2` 201 | 202 | # Region of Interest 203 | 204 | `remmi.roi.*` contains functions for *only basic* region of interest (ROI) 205 | analysis. See function-specific help for 206 | more details. 207 | 208 | ## Core functions 209 | 210 | * `remmi.roi.draw` is a function for drawing and processing ROIs 211 | * `remmi.roi.copy` copies ROIs and analysis from one structure to 212 | another 213 | 214 | 215 | # Other Utilities 216 | 217 | `remmi.util.*` contains utility functions that are independent of any other 218 | namespace 219 | 220 | ## Core functions 221 | 222 | * `remmi.util.slice` is a function to slice through multi-dimensional data 223 | * `remmi.util.thresholdmask` is a function to create a threshold mask from 224 | image data 225 | 226 | ## Helper functions 227 | 228 | * `remmi.util.apodize` provides Tukey window anodization of a 1D, 2D or 3D 229 | array 230 | * `remmi.util.githash` returns the hash number of current git commit for 231 | REMMI-Matlab. This will make it easier to reprocess of data from older 232 | versions of REMMI-Matlab 233 | * `remmi.util.snrCalc` calculates the SNR of an image 234 | * `remmi.util.strsplit` provides backwards compatibility for Matlab 235 | distributions that don't include strsplit 236 | 237 | 238 | # MRI Vendor Support 239 | 240 | `remmi.vendors.*` contains classes and functions for vendor-specific 241 | loading of data and parameter files 242 | 243 | ## Classes 244 | 245 | * `remmi.vendors.BrukerPV` is a class for loading Bruker PV5 & PV6 data and 246 | parameter files 247 | * `remmi.vendors.Agilent` is a class for loading Varian/Agilent data and 248 | parameter files 249 | 250 | ## Helper functions 251 | 252 | * `remmi.vendors.autoVendor` detects if the file structure within a study 253 | path matches that of a supported vendor 254 | * `remmi.vendors.bmatBruker` is a function to calculate a b-matrix from the 255 | remmiRARE sequence 256 | * `remmi.vendors.loadBruker` is a function to load Bruker data 257 | * `remmi.vendors.loadAgilent` is a function to load Agilent data 258 | * `remmi.vendors.parsBruker` is a function to load Bruker parameter files 259 | * `remmi.vendors.parsAgilent` is a function to load Agilent parameter files 260 | 261 | 262 | # Known Bugs 263 | 264 | 265 | 266 | # Bug Reporting 267 | 268 | Please report bugs through this git repository. Alternatively, you can 269 | email 270 | 271 | -------------------------------------------------------------------------------- /WHATSNEW: -------------------------------------------------------------------------------- 1 | What's new in: 2 | 3 | REMMI-matlab version 0.1.6 4 | * renamed remmi.dataset() to remmi.workspace 5 | 6 | REMMI-matlab version 0.1.5 7 | * Better error handling when Bruker reference scans are not included 8 | * Very preliminary methods to read/proces Varian data 9 | * This WHATSNEW file 10 | * The README.md & README.html files 11 | 12 | REMMI-matlab version 0.1.4 13 | * Updated to MERA v2.04 14 | * Work around for errors/warnings in remmi.dataset 15 | * remmi.mse.analysis only takes the absolute value if the input data is 16 | complex. Real data is left alone. 17 | * Bug fix when reconstrucing only a 2D image 18 | * Added function to specify MWF bounds in easy_T2 19 | * More examples and improvements to function help 20 | * Fixed a bug in remmi.mse.analysis that would error when MERA returns 21 | vectors that vary in length 22 | * By default, images are reconstructed without apodization 23 | * Added Contents.m to +remmi 24 | * Other various bug fixes -------------------------------------------------------------------------------- /easy_dti.m: -------------------------------------------------------------------------------- 1 | clearvars 2 | 3 | % List the study path and experiments to process 4 | info.spath = './data/dti_mse_study'; 5 | info.exps = 35; 6 | 7 | % specify where the data will be stored 8 | ws = remmi.workspace('easy_dti.mat'); 9 | 10 | % perform DTI analysis 11 | ws.dti = remmi.dwi.dti(info); 12 | -------------------------------------------------------------------------------- /easy_mtir.m: -------------------------------------------------------------------------------- 1 | clearvars 2 | 3 | % List the study path and experiments to process 4 | info.spath = './data/mtstudy'; 5 | info.exps = 29; 6 | 7 | % specify where the data will be stored 8 | ws = remmi.workspace('easy_mtir.mat'); 9 | 10 | % perform mtir analysis 11 | ws.mtir = remmi.ir.qmt(info); 12 | -------------------------------------------------------------------------------- /easy_t2.m: -------------------------------------------------------------------------------- 1 | clearvars 2 | 3 | % List the study path and experiments to process 4 | info.spath = './data/dti_mse_study'; 5 | info.exps = 30; 6 | 7 | % specify where the data will be stored 8 | ws = remmi.workspace('easy_t2.mat'); 9 | 10 | % load default options 11 | options = remmi.mse.mT2options(); 12 | 13 | % set T2 bounds on the MWF 14 | lb = 0.005; ub = 0.20; 15 | options.MWF = @(out)remmi.mse.calcT2frac(out,lb,ub); 16 | 17 | % perform epg multiple-exponential T2 analysis 18 | ws.epg = remmi.mse.mT2(info,options); 19 | -------------------------------------------------------------------------------- /full_t2.m: -------------------------------------------------------------------------------- 1 | clearvars 2 | 3 | % List the study path and experiments to process 4 | info.spath = './data/dti_mse_study'; 5 | info.exps = 30; 6 | 7 | % specify where the data will be stored 8 | ws = remmi.workspace('full_t2.mat',info); 9 | 10 | % reconstruct the data 11 | ws.images = remmi.recon(info); 12 | 13 | % Set a mask based upon the first echo time image 14 | ws.images = remmi.util.thresholdmask(ws.images); 15 | 16 | % load the default metrics for mT2 analysis 17 | metrics = remmi.mse.mT2options(); 18 | 19 | % add a spectrum to the output 20 | metrics.S = @(out) out.S; 21 | 22 | % Process multi-TE data 23 | ws.epg = remmi.mse.mT2(ws.images,metrics); 24 | 25 | % draw a few ROIS 26 | opts.nROIs = 3; 27 | ws.ROIs = remmi.roi.draw(ws.images,opts); 28 | 29 | % Process ROI data 30 | ws.ROI_epg = remmi.mse.mT2(ws.ROIs,metrics); 31 | --------------------------------------------------------------------------------