Skip to content

Commit

Permalink
Major fix for spatial processing, part 2
Browse files Browse the repository at this point in the history
Mask creation, as needed for further SPM analysis
- adding mask creation function
- interfacing this function with the batch system
- updating pipelines to include mask creation last step
  • Loading branch information
ChristophePhillips authored and Beliy Nikita committed Oct 26, 2021
1 parent 034d931 commit 2c744fe
Show file tree
Hide file tree
Showing 7 changed files with 539 additions and 33 deletions.
56 changes: 56 additions & 0 deletions hmri_check_opt.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function [opts,ind_repl] = hmri_check_opt(opts_def,opts,loc_opt)

% FORMAT opts = crc_check_flag(opts_def,opts)
%
% Function to automatically check the content of a "opts" structure, using
% a "default opts structure", adding the missing fields and putting in the
% default value if none was provided.
%
% INPUT:
% opts_def : default or reference structure
% opts : input option structure that need to be filled for missing
% fields with default values
%
% OUPUT:
% opts : filled up option structure
%_______________________________________________________________________
% Copyright (C) 2019 Cyclotron Research Centre

% Written by C. Phillips.
% Cyclotron Research Centre, University of Liege, Belgium

% Local option structure:
% loc_opt
% .verbose : print out information [true] or not [false, def.] on the
% fixed/updated structure.

if nargin<3
loc_opt.verbose = false;
end

f_names = fieldnames(opts_def);
% list fields in default structure

Nfields = length(f_names);
ind_repl = zeros(1,Nfields);
for ii=1:Nfields
% Update the output if
% - a field is missing
% - the field is empty when it shouldn't
if ~isfield(opts,f_names{ii}) || ...
( isempty(opts.(f_names{ii})) && ~isempty(opts_def.(f_names{ii})) )
opts.(f_names{ii}) = opts_def.(f_names{ii});
ind_repl(ii) = 1;
if loc_opt.verbose
fprintf('\n\tAdding field ''%s'' to structure ''%s''.', ...
f_names{ii},inputname(2));
jump_line = true;
end
end
end

if loc_opt.verbose && jump_line
fprintf('\n');
end

end
153 changes: 153 additions & 0 deletions hmri_proc_crtMask.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
function [fn_maskTC,fn_meanTC] = hmri_proc_crtMask(fn_smwTC, opts)
% Generating tissue specific mask for the SPM analysis.
% The aim is to preselect, based on smoothed modulated posterior tissue
% probability maps, which voxels should be considered for further
% statistical analysis.
%
% FORMAT
% [fn_maskTC,fn_meanTC] = hmri_proc_crtMask(fn_smwTC, opts)
%
% INPUT
% - fn_smwTC : filenames (cell array, 1 cell per TC, containing a char or
% cell array) of the modulated warped tissue classes,
% ex. the mwc1/2*.nii filenames in the 1st and 2nd cell.
% - opts : option structure
% .minTCp : minimal posterior probability value to be considerd [.2, def]
% .noOvl : avoid overlap by assigning to tissue class with maximum
% posterior probability [1, def]
% .outPth : path to output folder, use that of the 1st image if it is
% left empty ['', def]
%
% OUTPUT
% - fn_maskTC : filenames (char array) of the tissue specific masks
% - fn_meanTC : filenames (char array) of the mean tissue class images
%
% The core idea comes from Callaghan et al.
% > smoothed Jacobian-modulated tissue probability maps in MNI space were
% > averaged across all subjects for each tissue class (GM, WM, and
% > cerebrospinal fluid). Masks were generated by assigning voxels to the
% > tissue class for which the probability was maximal. Voxels for which
% > neither the GM nor the WM probability exceeded 20% were excluded from
% > the analysis. This approach was used to ensure that each voxel was
% > analyzed in only one subspace and that non-brain tissue was excluded.
%
% The function is relatively flexible as
% - nTC differe tissue class images can be introduced. if only one is
% provided, then only the minimal probability thresholding can be
% applied.
% - this minimal probability threshold, by default 20%, can be changed;
% - the "no overlap by assigning the the class with maximum probability"
% can be disabled and then only the thresholding is applied.
% - the mean tissue class images could still come in handy for quality &
% sanity checks so they're not deleted and there filenames passed out.
%
% REFERENCE
% Callaghan et al., https://doi.org/10.1016/j.neurobiolaging.2014.02.008
%_______________________________________________________________________
% Copyright (C) 2019 Cyclotron Research Centre

% Written by C. Phillips.
% Cyclotron Research Centre, University of Liege, Belgium


%% Deal with input:
% 1/ in case nothing is passed
if nargin<1
help(mfilename)
return
end

% 2/ set up default options & update passed options
if nargin<2, opts = struct; end
opts_def = struct(...
'minTCp', .2, ... % default 20%
'noOvl', true, ...
'outPth', '');
opts = hmri_check_opt(opts_def,opts);

% 3/ get nr of tissue classes (nTC) & nr of images (nImg), then check
nTC = numel(fn_smwTC);
nImg = zeros(1,nTC);
for ii=1:nTC
tmp = fn_smwTC{ii};
if iscell(tmp)
fn_smwTC{ii} = char( tmp ); % turn into cell array
end
nImg(ii) = size(fn_smwTC{ii},1);
end
if length(unique(nImg))>1
error('hmri:crtMsk','Provide same number of images per tissue class');
end
if nTC==1,
% No no-overlap possible if only relying on a single TC!
opts.noOvl = false;
end

% 4/ sort out output path
if isempty(opts.outPth)
% set it to that of the 1st image...
opts.outPth = spm_file(fn_smwTC{ii}(1,:),'path');
end

% 5/ chek & extract TC threshold
thrTC = opts.minTCp;
if thrTC>=1
% assume that thresholded is expressed explicitly in %,
% e.g. 20 instead of .2
fprintf('\nWARNING: threshold value passed >1!\n');
fprintf('\tAssuming you used percent value, need to convert it.\n');
fprintf('\tE.g. 20%% -> .2\n' );
thrTC = thrTC/100;
end

%% Deal with mask creation

% 1/ create the mean across subject of each the tissue map
% NOTE:
% it would look better to generate de mean image names in a more generic
% way, say with the just the tissue class index but no reference to any
% specific subject's Id.
fn_meanTC = cell(nTC,1);
imcalc_flags = struct(...
'dmtx', 1, ... % load into matrix X -> mean(X)
'mask', 1, ... % mask out 'bad' voxels across subjects
'descrip', 'mean tissue class image');
for ii=1:nTC
fn_meanTC{ii} = fullfile(opts.outPth, ...
['mean_',spm_file(fn_smwTC{ii}(1,:),'basename'),'.nii']);
spm_imcalc(fn_smwTC{ii}, fn_meanTC{ii}, 'mean(X)' ,imcalc_flags);
end

% 2/ binarize using 1 or 2 criteria:
% - always, some thresholding
% - possibly, some max-ing
fn_maskTC = cell(nTC,1);
imcalc_flags = struct(...
'dmtx', 0, ... % load into matrix X -> mean(X)
'mask', -1, ... % mask out 'bad' voxels
'dtype', 2, ... % save in int8 format
'descrip', 'tissue specific mask');
for ii=1:nTC
fn_maskTC{ii} = fullfile(opts.outPth, ...
['mask_',spm_file(fn_smwTC{ii}(1,:),'basename'),'.nii']);
% 1st thresholding
fn_in = fn_meanTC{ii};
f_oper = 'i1>thrTC';
% then max-ing, if needed
if opts.noOvl
% add images to input + add conditions to operations
l_ind2add = 1:nTC; l_ind2add(ii) = [];
for jj = 1:nTC-1
fn_in = char(fn_in,fn_meanTC{l_ind2add(jj)});
f_oper = sprintf('%s & i1>i%d', f_oper, jj+1);
end
end
% do it
spm_imcalc(fn_in, fn_maskTC{ii}, f_oper, imcalc_flags, ...
struct('name','thrTC','value',thrTC) );
% pass the extra variable "threTC" via a structure as the function
% 'inputname' was not working correctly on my Matlab...
end


end
41 changes: 41 additions & 0 deletions hmri_run_proc_crtMask.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
function out = hmri_run_proc_crtMask(job)
% Function to run the mask creation routine
%
% The out structure has 2 fields:
% .fn_maskTC : filenames (char array) of the tissue specific masks
% .fn_meanTC : filenames (char array) of the mean tissue class images
%_______________________________________________________________________
% Copyright (C) 2019 Cyclotron Research Centre

% Written by Christophe Phillips

% Unpack job input & sort out output directory
% n_TCs = numel(job.vols_smwc); % #tissue classes
% for ii = 1:n_TCs
% fn_smwTC{ii,1} = job.vols_smwc{ii};
% end
fn_smwTC = job.vols_smwc;
% options.val = {threshTC noOverlap output};
opts = struct(...
'minTCp', job.options.threshTC, ...
'noOvl', job.options.noOverlap, ...
'outPth', '');
if isfield(job.options.output,'outdir') % use provided output dir
outPth = job.options.output.outdir{1};
if ~exist(outPth,'dir')
mkdir(outPth);
fprintf('\nWARNING:\n');
fprintf('\tCreating directory: %s\n\n',outPth)
end
opts.outPth = outPth;
end

% call function
[fn_maskTC,fn_meanTC] = hmri_proc_crtMask(fn_smwTC, opts);

% pack up output mask/mean image filenames
out.fn_maskTC = fn_maskTC;
out.fn_meanTC = fn_meanTC;

end

76 changes: 59 additions & 17 deletions hmri_run_proc_pipeline.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
function out = hmri_run_proc_pipeline(job)
% Deal with the preprocessing pipelines. There are 2 options
% 1/ US+Smooth
% 2/ US+Dartel+Smooth
% Deal with the preprocessing pipelines. There are 2 options:
% 1/ "US+Smooth+MaskCrt"
% 2/ "US+Dartel+Smooth+MaskCrt"
% The only difference is the inclusion of Dartel to bring all the data into
% a common space, before warping into MNI.
%
% So the operations go through 3 or 4 main steps:
% 1/ unified segementation, if no Dartel with warping to MNI
% 2/ if with Dartel, Dartel estimation and warping to MNI
% 3/ smoothing with tissue class specific smoothing
% 4/ creation of tissue specific mask
%
% Input include only some parametric maps, the structural maps (for
% segmentation), the required smoothing and which pipeline to use. All
Expand All @@ -11,12 +19,21 @@
%
% For more flexibility, individual modules can be combined. :-)
%
% The output structure 'out' provides:
% .tc : cell-array of size {n_TCs x n_pams}. Each element tc{ii,jj} is
% a cell array {n_subj x 1} with each subject's smoothed data for
% the ii^th TC and jj^th MPM
% .smwc : cell-array of size {n_TCs x1}. Each element smwc{ii} is a
% char array (n_subj x 1) with each subject's smooth modulated
% warped ii^th tissue class
% .maskTC : cell-array of size {n_TCs x1}. Each element smwc{ii} is the
% filename of the ii^th tissue specific masks
%_______________________________________________________________________
% Copyright (C) 2017 Cyclotron Research Centre

% Written by Christophe Phillips

% 1/ Setup the smoothing and execute hmri_run_proc_US
%% 1/ Setup the smoothing and execute hmri_run_proc_US
%----------------------------------------------------
% Get the proc_US job structure, with all the defaults
proc_us = tbx_scfg_hmri_proc_US;
Expand Down Expand Up @@ -50,7 +67,7 @@
% maps
% .def : cell-array with the deformations for each subject.

% 2/ Proceed with dartel (only if requested)
%% 2/ Proceed with dartel (only if requested)
%-------------------------------------------
% including template create and warping into MNI space

Expand Down Expand Up @@ -101,8 +118,8 @@
[~, job_Dnorm] = harvest(proc_Dnorm, proc_Dnorm, false, false);
% get last tempalte
job_Dnorm.template = out_Dwarp.template(end);
% get GM and WM tissue class
for ii=1:2
% get GM, WM and CSF tissue class
for ii=1:3
job_Dnorm.multsdata.vols_tc{ii} = ...
spm_file(out_US.tiss(ii).c,'number',1);
end
Expand All @@ -121,12 +138,12 @@
out_Dnorm = hmri_run_proc_dartel_norm(job_Dnorm);
end

% 3/ Deal with smoothing, with hmri_run_proc_smooth
%% 3/ Deal with smoothing, with hmri_run_proc_smooth
%--------------------------------------------------
proc_smooth = tbx_scfg_hmri_proc_smooth;
[~, job_smooth] = harvest(proc_smooth, proc_smooth, false, false);

% Get the image data, working *only* with mwc1 and mwc2 (GM and WM)
% Get the image data, working with mwc1, mwc2 and mwc3 (GM, WM and CSF)
switch job.pipe_c
case 1 % US+smooth
job_smooth = fill_fn_from_US(job_smooth,out_US);
Expand All @@ -143,14 +160,39 @@
% Run the *_proc_smooth
fprintf('\nhMRI-pipeline: running the weighted-average (smoothing) module.\n')
out_wa = hmri_run_proc_smooth(job_smooth);
% where the 'out_wa' is organized as a structure out_wa.tc where
% - tc is a cell-array of size {n_TCs x n_pams}
% - each element tc{ii,jj} is a cell array {n_subj x 1} with each subject's
% smoothed data for the ii^th TC and jj^th MPM
% The 'out_wa' structure is organized as a structure with 2 fileds
% .tc : cell-array of size {n_TCs x n_pams}. Each element tc{ii,jj} is a
% cell array {n_subj x 1} with each subject's smoothed data for
% the ii^th TC and jj^th MPM
% .smwc : cell-array of size {n_TCs x1}. Each element smwc{ii} is a char
% array (n_subj x 1) with each subject's smooth modulated warped
% ii^th tissue class

%% 4/ Create the tissue specific masks
proc_crtMask = tbx_scfg_hmri_proc_crtMask;
[~, job_crtMask] = harvest(proc_crtMask, proc_crtMask, false, false);
% Get the smwc images in & fix output directory
job_crtMask.vols_smwc = out_wa.smwc;
if isfield(job.output,'outdir_ps')
job_crtMask.options.output = struct('outdir',job.output.outdir_ps);
else
job_crtMask.options.output = job.output;
end
% Call to
out_cM = hmri_run_proc_crtMask(job_crtMask);
% The out_cM structure has 2 fields:
% .fn_maskTC : filenames (char array) of the tissue specific masks
% .fn_meanTC : filenames (char array) of the mean tissue class images


%% 5/ Collect output and as needed
out = out_wa;
% There are 2 fields with cell array in 'out_wa' :
% .tc : tissue specifically smoothed qMRIs
% .smwc : smooth modulated warped tissue class images

% 4/ Collect output and as needed
out = out_wa; % -> good enouh for the moment!
% Adding the the tissue mask images -> necessary for SPM analysis
out.maskTC = cellstr(out_cM.fn_maskTC);

end

Expand All @@ -168,8 +210,8 @@
for ii=1:N_pm
job_smooth.vols_pm{ii} = out_US.maps(ii).wvols_pm;
end
% Tissue classes -> use GM and WM, i.e. #1 and #2
for ii=1:2
% Tissue classes -> use GM, WM and CSF, i.e. #1, #2 and #3
for ii=1:3
job_smooth.vols_mwc{ii} = spm_file(out_US.tiss(ii).mwc,'number',1);
end

Expand Down
Loading

0 comments on commit 2c744fe

Please sign in to comment.