Skip to content

Commit 4292e68

Browse files
author
Luke Edwards
committed
Merge branch 'master' into add_separate-MT-B1map
2 parents 7b78f95 + 18b3c01 commit 4292e68

30 files changed

+1860
-159
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ Most recent version numbers *should* follow the [Semantic Versioning](https://se
88
### Added
99
- option to choose different models and parameters for B1-correction of MTsat
1010
- set default WM percent value in `hmri_defaults`
11+
- spatial processing: add explicit mask creation and fix implicit mask (0 to NaN in float images)
12+
- update FIL seste seq parameters in get_metadata_val_classic
13+
- denoising module-first part: Java-Matlab interface for LCPCA denoising
1114
- option to use a separate B1 map for B1 correction of MTsat; useful if pTx used for excitation pulses and CP mode for the MT pulse
1215

1316
### Fixed
1417
- replace `datestr(now)` with `datetime('now')` in line with [MATLAB recommendation](https://mathworks.com/help/matlab/matlab_prog/replace-discouraged-instances-of-serial-date-numbers-and-date-strings.html)
1518
- fix crash if input images have different matrix sizes, and warn
1619
- modify the filenames as files are copied to RFsensCalc to prevent overwriting in further processing
20+
- batch interface now enforces the number of B1 input images correctly for B1 mapping methods which only need two images.
21+
- fix error if optimization toolbox not present during NLLS R2* calculation
22+
- make B1-map creation using 3DEPI SE/STE and AFI methods fall back to defaults without sidecar files, rather than crash
1723

1824
## [v0.6.1]
1925
### Fixed

config/hmri_denoising_defaults.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
function hmri_denoising_defaults
2+
3+
%init the global variable which carries the params
4+
global hmri_def;
5+
6+
%Enter denoising default values as: hmri_def.denoising.(denoising-protocol).(default-value)
7+
8+
%The default values for lcpca denoising protocol
9+
%all optional parameters turned off
10+
hmri_def.denoising.lcpca_denoise.mag_input = {}; %%required-null initialize here input with GUI
11+
hmri_def.denoising.lcpca_denoise.phase_input = {}; %%optional-null initialize here input with GUI
12+
hmri_def.denoising.lcpca_denoise.output_path=''; %%required-null initialize here input with GUI
13+
hmri_def.denoising.lcpca_denoise.min_dimension= 0; %%required-initialize here
14+
hmri_def.denoising.lcpca_denoise.max_dimension = -1; %%required-initialize here
15+
hmri_def.denoising.lcpca_denoise.unwrap = false; %%optional
16+
hmri_def.denoising.lcpca_denoise.rescale_phs = false; %%optional
17+
hmri_def.denoising.lcpca_denoise.process_2d=false; %%optional
18+
hmri_def.denoising.lcpca_denoise.use_rmt=false; %%optional
19+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
%init the global variable which carries the params
2+
global hmri_def;
3+
4+
%Enter denoising default values as: hmri_def.denoising.(denoising-protocol).(default-value)
5+
6+
%The default values for lcpca denoising protocol
7+
%all optional parameters turned off
8+
hmri_def.denoising.lcpca_denoise.mag_input = {}; %%required-null initialize here input with GUI
9+
hmri_def.denoising.lcpca_denoise.phase_input = {}; %%optional-null initialize here input with GUI
10+
hmri_def.denoising.lcpca_denoise.output_path=''; %%required-null initialize here input with GUI
11+
hmri_def.denoising.lcpca_denoise.min_dimension= 0; %%required-initialize here
12+
hmri_def.denoising.lcpca_denoise.max_dimension = -1; %%required-initialize here
13+
hmri_def.denoising.lcpca_denoise.unwrap = false; %%optional
14+
hmri_def.denoising.lcpca_denoise.rescale_phs = false; %%optional
15+
hmri_def.denoising.lcpca_denoise.process_2d=false; %%optional
16+
hmri_def.denoising.lcpca_denoise.use_rmt=false; %%optional

hmri_calc_R2s.m

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@
147147
beta(2:end,:)=exp(beta(2:end,:));
148148

149149
case {'nlls_ols','nlls_wls1','nlls_wls2','nlls_wls3'}
150+
%lsqcurvefit uses optimization toolbox
151+
%Check both for the toolbox and/or an active license for it
152+
%Error if one of them is missing
153+
versionStruct = ver;
154+
versionCell = {versionStruct.Name};
155+
ver_status = any(ismember(versionCell, 'Optimization Toolbox'));
156+
[license_status,~] = license('checkout', 'Optimization_toolbox');
157+
if ver_status==0 || license_status==0
158+
error('hmri:NoOptimToolbox', "The methods 'nlls_ols','nlls_wls1','nlls_wls2','nlls_wls3' require Optimization Toolbox: either this toolbox and/or its license is missing." + ...
159+
" Please use another method which does not need the Optimization Toolbox such as 'ols','wls1','wls2','wls3'. ")
160+
end
161+
150162
% Check for NLLS case, where specification of the log-linear
151163
% initialisation method is in the method string following a hyphen
152164
r=regexp(lower(method),'^nlls_(.*)$','tokens');
@@ -167,7 +179,7 @@
167179
end
168180

169181
expDecay=@(x,D) (D(:,2:end)*x(2:end)).*exp(x(1)*D(:,1));
170-
182+
171183
% Loop over voxels
172184
parfor n=1:size(y,2)
173185
beta(:,n)=lsqcurvefit(@(x,D)expDecay(x,D),beta0(:,n),D,y(:,n),[],[],opt);

hmri_check_opt.m

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
function [opts,ind_repl] = hmri_check_opt(opts_def,opts,loc_opt)
2+
3+
% FORMAT opts = crc_check_flag(opts_def,opts)
4+
%
5+
% Function to automatically check the content of a "opts" structure, using
6+
% a "default opts structure", adding the missing fields and putting in the
7+
% default value if none was provided.
8+
%
9+
% INPUT:
10+
% opts_def : default or reference structure
11+
% opts : input option structure that need to be filled for missing
12+
% fields with default values
13+
%
14+
% OUPUT:
15+
% opts : filled up option structure
16+
%_______________________________________________________________________
17+
% Copyright (C) 2019 Cyclotron Research Centre
18+
19+
% Written by C. Phillips.
20+
% Cyclotron Research Centre, University of Liege, Belgium
21+
22+
% Local option structure:
23+
% loc_opt
24+
% .verbose : print out information [true] or not [false, def.] on the
25+
% fixed/updated structure.
26+
27+
if nargin<3
28+
loc_opt.verbose = false;
29+
end
30+
31+
f_names = fieldnames(opts_def);
32+
% list fields in default structure
33+
34+
Nfields = length(f_names);
35+
ind_repl = zeros(1,Nfields);
36+
for ii=1:Nfields
37+
% Update the output if
38+
% - a field is missing
39+
% - the field is empty when it shouldn't
40+
if ~isfield(opts,f_names{ii}) || ...
41+
( isempty(opts.(f_names{ii})) && ~isempty(opts_def.(f_names{ii})) )
42+
opts.(f_names{ii}) = opts_def.(f_names{ii});
43+
ind_repl(ii) = 1;
44+
if loc_opt.verbose
45+
fprintf('\n\tAdding field ''%s'' to structure ''%s''.', ...
46+
f_names{ii},inputname(2));
47+
jump_line = true;
48+
end
49+
end
50+
end
51+
52+
if loc_opt.verbose && jump_line
53+
fprintf('\n');
54+
end
55+
56+
end
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
% Test file for hmri_proc_zero2nan.m
2+
% Run with
3+
% results = runtests('hmri_proc_zero2nan_test.m')
4+
5+
%% Main function to generate tests
6+
function tests = hmri_proc_zero2nan_test
7+
tests = functiontests(localfunctions);
8+
end
9+
10+
%% Test functions
11+
% Check all data format available in SPM:
12+
% uint8, int16, int32, float32, float64, int8, uint16, uint32
13+
14+
% uint8
15+
function testFunction_uint8(testCase)
16+
% Temporaty folder with the temporary data files
17+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
18+
19+
% Pick the 2 files, original & expected results
20+
fn_Orig = fullfile(pth_Dat,'Dat_uint8.nii');
21+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
22+
23+
% Apply zero-to-NaN conversion function
24+
hmri_proc_zero2nan(fn_Orig);
25+
26+
% Load image values
27+
actVal = spm_read_vols(spm_vol(fn_Orig));
28+
expVal = spm_read_vols(spm_vol(fn_ExpR));
29+
verifyEqual(testCase,actVal,expVal)
30+
end
31+
32+
% int16
33+
function testFunction_int16(testCase)
34+
% Temporaty folder with the temporary data files
35+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
36+
37+
% Pick the 2 files, original & expected results
38+
fn_Orig = fullfile(pth_Dat,'Dat_int16.nii');
39+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
40+
41+
% Apply zero-to-NaN conversion function
42+
hmri_proc_zero2nan(fn_Orig);
43+
44+
% Load image values
45+
actVal = spm_read_vols(spm_vol(fn_Orig));
46+
expVal = spm_read_vols(spm_vol(fn_ExpR));
47+
verifyEqual(testCase,actVal,expVal)
48+
end
49+
50+
% int32
51+
function testFunction_int32(testCase)
52+
% Temporaty folder with the temporary data files
53+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
54+
55+
% Pick the 2 files, original & expected results
56+
fn_Orig = fullfile(pth_Dat,'Dat_int32.nii');
57+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
58+
59+
% Apply zero-to-NaN conversion function
60+
hmri_proc_zero2nan(fn_Orig);
61+
62+
% Load image values
63+
actVal = spm_read_vols(spm_vol(fn_Orig));
64+
expVal = spm_read_vols(spm_vol(fn_ExpR));
65+
verifyEqual(testCase,actVal,expVal)
66+
end
67+
68+
% float32
69+
function testFunction_float32(testCase)
70+
% Temporaty folder with the temporary data files
71+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
72+
73+
% Pick the 2 files, original & expected results
74+
fn_Orig = fullfile(pth_Dat,'Dat_float32.nii');
75+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
76+
77+
% Apply zero-to-NaN conversion function
78+
hmri_proc_zero2nan(fn_Orig);
79+
80+
% Load image values
81+
actVal = spm_read_vols(spm_vol(fn_Orig));
82+
expVal = spm_read_vols(spm_vol(fn_ExpR));
83+
verifyEqual(testCase,actVal,expVal)
84+
end
85+
86+
% float64
87+
function testFunction_float64(testCase)
88+
% Temporaty folder with the temporary data files
89+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
90+
91+
% Pick the 2 files, original & expected results
92+
fn_Orig = fullfile(pth_Dat,'Dat_float64.nii');
93+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
94+
95+
% Apply zero-to-NaN conversion function
96+
hmri_proc_zero2nan(fn_Orig);
97+
98+
% Load image values
99+
actVal = spm_read_vols(spm_vol(fn_Orig));
100+
expVal = spm_read_vols(spm_vol(fn_ExpR));
101+
verifyEqual(testCase,actVal,expVal)
102+
end
103+
104+
% int8
105+
function testFunction_int8(testCase)
106+
% Temporaty folder with the temporary data files
107+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
108+
109+
% Pick the 2 files, original & expected results
110+
fn_Orig = fullfile(pth_Dat,'Dat_int8.nii');
111+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
112+
113+
% Apply zero-to-NaN conversion function
114+
hmri_proc_zero2nan(fn_Orig);
115+
116+
% Load image values
117+
actVal = spm_read_vols(spm_vol(fn_Orig));
118+
expVal = spm_read_vols(spm_vol(fn_ExpR));
119+
verifyEqual(testCase,actVal,expVal)
120+
end
121+
122+
% uint16
123+
function testFunction_uint16(testCase)
124+
% Temporaty folder with the temporary data files
125+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
126+
127+
% Pick the 2 files, original & expected results
128+
fn_Orig = fullfile(pth_Dat,'Dat_uint16.nii');
129+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
130+
131+
% Apply zero-to-NaN conversion function
132+
hmri_proc_zero2nan(fn_Orig);
133+
134+
% Load image values
135+
actVal = spm_read_vols(spm_vol(fn_Orig));
136+
expVal = spm_read_vols(spm_vol(fn_ExpR));
137+
verifyEqual(testCase,actVal,expVal)
138+
end
139+
140+
% uint32
141+
function testFunction_uint32(testCase)
142+
% Temporaty folder with the temporary data files
143+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
144+
145+
% Pick the 2 files, original & expected results
146+
fn_Orig = fullfile(pth_Dat,'Dat_uint32.nii');
147+
fn_ExpR = spm_file(fn_Orig,'suffix','_ExpRes');
148+
149+
% Apply zero-to-NaN conversion function
150+
hmri_proc_zero2nan(fn_Orig);
151+
152+
% Load image values
153+
actVal = spm_read_vols(spm_vol(fn_Orig));
154+
expVal = spm_read_vols(spm_vol(fn_ExpR));
155+
verifyEqual(testCase,actVal,expVal)
156+
end
157+
158+
159+
%% Those will be run once the test file is loaded/unloaded
160+
function setupOnce(testCase) %#ok<*INUSD> % do not change function name
161+
% Generate the random data files used for the tests
162+
% Put these files in Matlab's (predefined) temporary folder
163+
164+
% Get SPM's data types for images
165+
Dtypes = spm_type;
166+
167+
% Temporaty folder to save the temporary data files
168+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
169+
if ~exist(pth_Dat,'dir'), mkdir(pth_Dat), end % CP: Need this check ?
170+
171+
% Then create some 3D synthetic images, z-axis is of size 3 such that
172+
% - z=1 made of random non-zero values, positive & negative
173+
% - z=2 made of zeros
174+
% - z=3 made of NaNs
175+
Img_sz = [2 4 3]; % 3D image contains 3 slices of size 2x4
176+
Img_val = zeros(Img_sz);
177+
Img_val(:,:,1) = 10.^(randn(Img_sz(1:2))).*sign(randn(Img_sz(1:2)));
178+
Img_val(:,:,3) = NaN;
179+
% Expected results for integer/float images after applying the
180+
% zero-to-nan conversion:
181+
% - integer: NaN's were converted to zero's at image creation & zeros
182+
% remained untouched at conversion
183+
% - unsigned integer: like integer but negative values turned into zero at
184+
% image creation
185+
% - float: zero's were turned into NaN's by conversiotn
186+
% -> integer formatted data
187+
Img_val_expInteger = Img_val;
188+
Img_val_expInteger(:,:,3) = 0;
189+
% -> unsigned integer formatted data
190+
Img_val_expUInteger = Img_val_expInteger;
191+
Img_val_expUInteger(Img_val_expUInteger(:)<0) = 0;
192+
% -> float formatted data
193+
Img_val_expFloat = Img_val;
194+
Img_val_expFloat(:,:,2) = NaN;
195+
196+
% Save the same data in all formats with SPM functions:
197+
% use spm_vol, spm_create_vol, and spm_write_vol
198+
for ii=1:numel(Dtypes)
199+
% Create original data image
200+
fn_ii = fullfile(pth_Dat,sprintf('Dat_%s.nii',spm_type(Dtypes(ii))));
201+
V_ii = struct( ...
202+
'fname', fn_ii, ...
203+
'dim', Img_sz, ...
204+
'dt', [Dtypes(ii) 0], ...
205+
'mat', eye(4) , ...
206+
'descrip', sprintf( 'Test %s data',spm_type(Dtypes(ii)) ));
207+
V_ii = spm_create_vol(V_ii);
208+
spm_write_vol(V_ii,Img_val); %#ok<*NASGU,*AGROW>
209+
210+
% Create expected resulting data image
211+
fn_exp_ii = spm_file(fn_ii,'suffix','_ExpRes');
212+
V_exp_ii = struct( ...
213+
'fname', fn_exp_ii, ...
214+
'dim', Img_sz, ...
215+
'dt', [Dtypes(ii) 0], ...
216+
'mat', eye(4) , ...
217+
'descrip', sprintf( 'ExpRes %s data',spm_type(Dtypes(ii)) ));
218+
V_exp_ii = spm_create_vol(V_exp_ii);
219+
if spm_type(Dtypes(ii),'nanrep') % with NaN rep -> float
220+
spm_write_vol(V_exp_ii,Img_val_expFloat);
221+
else % No NaN rep -> signed/unsigned Integer
222+
if spm_type(Dtypes(ii),'minval')<0 % -> Integer
223+
spm_write_vol(V_exp_ii,Img_val_expInteger);
224+
else % unsigned Integer
225+
spm_write_vol(V_exp_ii,Img_val_expUInteger);
226+
end
227+
end
228+
end
229+
230+
end
231+
232+
function teardownOnce(testCase) % do not change function name
233+
% Delete the random data files used for the tests, as well as any other
234+
% temporary files
235+
236+
% Temporaty folder where the temporary data files are saved
237+
pth_Dat = fullfile(tempdir,'hMRI_zero2nan_test');
238+
if exist(pth_Dat,'dir')
239+
fn_2del = spm_select('FPlist',pth_Dat,'.*');
240+
for ii=1:size(fn_2del,1)
241+
delete(deblank(fn_2del(ii,:)));
242+
end
243+
rmdir(pth_Dat,'s');
244+
end
245+
246+
end
247+
248+
%% Those will be run at the beginning/end of each test function
249+
function setup(testCase) % do not change function name
250+
251+
end
252+
function teardown(testCase) % do not change function name
253+
254+
end

0 commit comments

Comments
 (0)