Skip to content

Commit

Permalink
Large update of the quality control tools:
Browse files Browse the repository at this point in the history
Added new Version of image quality estimation cat_vol_qa that works now as hub
to call different versions of the QC cat_vol_qa#(x) added here, with # as the
year and month and an additional x that reference the updated versions corrected
for general problems observed in the new test framework. The test framework
includes the (i) standard BWP, (ii) the BWP with simulated preprocessing error,
(iii) IXI (aging), (iv) atlas (lesions), (iv) MR-ART (motion artifacts), and
(v) a private library of real datasets of public/private projects/studies.
Function was tested in cross-section, longitudinal, and batch.

1) Renamed:  Renamed cat_vol_qa to cat_vol_qa202205 (=deleted).
             Renamed cat_vol_qa2 to cat_vol_qa (=replaced).
             cat_vol_qa now only serves as hub to run specific qa versions
             cat_vol_qa# with # as data with year and month.
2) Renamed:  Renamed cat_tst_qa201602 (=deleted) to cat_vol_qa20602 (=new) to
             have all QC version together.
3) Changed:  Added setting to choose QC version in QC batch and extended the
             help in cat_conf_tools.
4) Changed:  Updated cat_stat_marks settings and added temporary SIQR variants.
5) New:      Added different processing version of cat_vol_qa#(x) available
             via batch, where the x marks updated/debugged versions.
6) New:      Added cat_vol_normer to normalize quality ratings within protocols
             for outlier detection.

Changed paths:
 M CHANGES.txt
 M cat_conf_tools.m
 M cat_run_job1639.m
 M cat_stat_marks.m
 D cat_tst_qa201602.m
M  cat_vol_mimcalc.m
 M cat_vol_qa.m
 D cat_vol_qa2.m
 M cat_vol_qa201901.m
  • Loading branch information
robdahn committed Nov 20, 2023
1 parent 4e4fc52 commit 5bc5cc3
Show file tree
Hide file tree
Showing 9 changed files with 682 additions and 2,737 deletions.
38 changes: 38 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
------------------------------------------------------------------------
r2480 | dahnke | 2023-11-20 16:52:28

Changed paths:
M CHANGES.txt
M cat_conf_tools.m
M cat_run_job1639.m
M cat_stat_marks.m
D cat_tst_qa201602.m
M cat_vol_mimcalc.m
M cat_vol_qa.m
D cat_vol_qa2.m
M cat_vol_qa201901.m

Large update of the quality control tools:
Added new Version of image quality estimation cat_vol_qa that works now as hub
to call different versions of the QC cat_vol_qa#(x) added here, with # as the
year and month and an additional x that reference the updated versions corrected
for general problems observed in the new test framework. The test framework
includes the (i) standard BWP, (ii) the BWP with simulated preprocessing error,
(iii) IXI (aging), (iv) atlas (lesions), (iv) MR-ART (motion artifacts), and
(v) a private library of real datasets of public/private projects/studies.
Function was tested in cross-section, longitudinal, and batch.

1) Renamed: Renamed cat_vol_qa to cat_vol_qa202205 (=deleted).
Renamed cat_vol_qa2 to cat_vol_qa (=replaced).
cat_vol_qa now only serves as hub to run specific qa versions
cat_vol_qa# with # as data with year and month.
2) Renamed: Renamed cat_tst_qa201602 (=deleted) to cat_vol_qa20602 (=new) to
have all QC version together.
3) Changed: Added setting to choose QC version in QC batch and extended the
help in cat_conf_tools.
4) Changed: Updated cat_stat_marks settings and added temporary SIQR variants.
5) New: Added different processing version of cat_vol_qa#(x) available
via batch, where the x marks updated/debugged versions.
6) New: Added cat_vol_normer to normalize quality ratings within protocols
for outlier detection.

------------------------------------------------------------------------
r2479 | gaser | 2023-11-14 19:40:19

Expand Down
96 changes: 63 additions & 33 deletions cat_conf_tools.m
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,7 @@
spmsegc.name = 'Default with SPM segment maps';
spmsegc.help = {'Select corresponing individual SPM GM tissue segments of the selected images above (c1*.nii). The WM and CSF maps were selected automatically. ' ''};

% segmentation in general
gm = data;
gm.tag = 'gm';
gm.name = 'GM segment';
Expand All @@ -1713,15 +1714,15 @@

% other posible cases
% - FSL segment maps
% - FS label map
% - FS label map, DL maps

model = cfg_choice;
model.tag = 'model';
model.name = 'Segmentation';
if expert > 1
model.name = 'Segmentation input';
if expert > 0
model.values = {catlab,catsegp,spmsegc,seg};
else
model.values = {catlab,catsegp,spmsegc};
model.values = {catlab,spmsegc};
end
model.val = {catlab};
model.help = {[ ...
Expand All @@ -1748,12 +1749,29 @@
version = cfg_menu;
version.tag = 'version';
version.name = 'Version';
version.help = {
['Select different versions of QC processing. ' ...
'The first version 201602 was quite stable with small bugfixes until the version 201901. ' ...
'Light corrections result then in version 202110 and later version 202205. ' ...
'Extensive tests result in a stronger modified version 202310 that was finally stable for ' ...
'(i) the brain web phantom, (ii) preprocessing error simulation, (iii) IXI (aging), ' ...
'(iv) ATLAS (lesions), (v) MR-ART (motion artificats), (vi) private motion data. ']
''
};
if expert > 1
version.labels = {'current','update2023','update2022','201901_202301','202110','201901','201602'};
version.values = {'cat_vol_qa','cat_vol_qa202301','cat_vol_qa202207b','cat_vol_qa201901_202301','cat_vol_qa202110','cat_vol_qa201901','cat_vol_qa201602'};
version.labels = {'202310 (current)', '202110x (reworked 202310)','201901x (reworked 202310)', ...
'202205', '202110', '201901','201602'};
version.values = {'cat_vol_qa202310', 'cat_vol_qa202110x', 'cat_vol_qa201901x', ...
'cat_vol_qa202205', 'cat_vol_qa202110', 'cat_vol_qa201901', 'cat_vol_qa201602'};
version.help = [ version.help , {
['The developer GUI further supports updated versions (cat_vol_qa#x) of 201901 and 202110 ' ...
'that included corrections for the differnet test cases and result finally in the cat_vol_qa202310. ' ...
'All functions uses the cat_vol_qa for basic loading of variables that may inlcude further differences to the fully orginal version. ']
''
}];
else
version.labels = {'current','202110','201901','201602'};
version.values = {'cat_vol_qa','cat_vol_qa202110','cat_vol_qa201901','cat_vol_qa201602'};
version.labels = {'202310 (current)', '202205', '202110', '201901', '201602'};
version.values = {'cat_vol_qa202310', 'cat_vol_qa202205', 'cat_vol_qa202110', 'cat_vol_qa201901', 'cat_vol_qa201602'};
end
% remove undefined cases
for vi = numel(version.values):-1:1
Expand All @@ -1762,11 +1780,8 @@
version.labels(vi) = [];
end
end
version.val = {'cat_vol_qa'};
version.val = {'cat_vol_qa202310'};
version.hidden = expert<1;
version.help = {
'Select different version of QC processing. '
};

rerun = cfg_menu;
rerun.tag = 'rerun';
Expand All @@ -1787,6 +1802,15 @@
verb.val = {2};
verb.help = {'Print progress and results. ';''};

writecsv = cfg_menu;
verb.tag = 'verb';
verb.name = 'Print results';
verb.labels = {'no' 'yes'};
verb.values = {0 2 };
verb.val = {2};
verb.help = {'Print progress and results. ';''};


outdir.val{1} = {'report'};
outdir.help = {'Create sub-directory within the main directory.'};
% could be confusing ... writes into the current report directory (defined by mri from the preprocessing)
Expand All @@ -1802,10 +1826,9 @@
qa.tag = 'iqe';
qa.name = 'Image quality estimation';
qa.val = {data, model, opts};
qa.prog = @cat_vol_qa2;
qa.prog = @cat_vol_qa;
qa.vfiles = @vout_qa; % XML files + values
%qa.hidden = expert<2;
qa.help = {'Image quality estimation based on a set of images and a given set of input segmentation defined by different models. '};
qa.help = {'Image quality estimation based on a set of images and their segmentation maps. '};
return

%_______________________________________________________________________
Expand Down Expand Up @@ -4561,24 +4584,31 @@
dep.tgt_spec = cfg_findspec({{'filter','image','strtype','e'}});
return;
%_______________________________________________________________________
function vf = vout_qa(job)
if isfield(job,'data')
s = cellstr(char(job.data)); vf = s;
elseif isfield(job,'images')
s = cellstr(char(job.images)); vf = s;
else
s = {};
vf = {};
end
for i=1:numel(s)
[pth,nam,ext,num] = spm_fileparts(s{i});
if isfield(job,'prefix') % old
vf{i} = fullfile(pth,[job.prefix,nam,ext,num]);
elseif isfield(job,'opts') && isfield(job.opts,'prefix')
vf{i} = fullfile(pth,[job.opts.prefix,nam,ext,num]);
else
vf{i} = fullfile(pth,[nam,ext,num]);
end
function dep = vout_qa(job)
if 0 % old
if isfield(job,'data')
s = cellstr(char(job.data)); dep = s;
elseif isfield(job,'images')
s = cellstr(char(job.images)); dep = s;
else
s = {};
dep = {};
end
for i=1:numel(s)
[pth,nam,ext,num] = spm_fileparts(s{i});
if isfield(job,'prefix') % old
dep{i} = fullfile(pth,[job.prefix,nam,ext,num]);
elseif isfield(job,'opts') && isfield(job.opts,'prefix')
dep{i} = fullfile(pth,[job.opts.prefix,nam,ext,num]);
else
dep{i} = fullfile(pth,[nam,ext,num]);
end
end
else
dep = cfg_dep;
dep.sname = 'CATQC';
dep.src_output = substruct('.','xmls','()',{':'});
dep.tgt_spec = cfg_findspec({{'filter','image','strtype','e'}});
end
return;

Expand Down
4 changes: 3 additions & 1 deletion cat_run_job1639.m
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,9 @@ function cat_run_job1639(job,tpm,subj)
VG1 = VG;
[Ym,Yt,Ybg,WMth] = APPmini(obj,VF);
else
stime = cat_io_cmd('Skip initial affine registration due to high-intensity background','','',1);
if ~( isfield(job,'useprior') && ~isempty(job.useprior) )
stime = cat_io_cmd('Skip initial affine registration due to high-intensity background','','',1);
end
VF = spm_vol(obj.image(1));
[Ym,Yt,Ybg,WMth] = APPmini(obj,VF); %#ok<ASGLU>
end
Expand Down
23 changes: 17 additions & 6 deletions cat_stat_marks.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@
% - resolution -
'qualitymeasures' 'res_vx_vol' 'linear' [ 0.50 3.00] 'voxel dimensions'
'qualitymeasures' 'res_RMS' 'linear' [ 0.50 3.00] 'RMS error of voxel size'
'qualitymeasures' 'res_ECR' 'normal' [ 0.125 1.00] 'normalized gradient slope of the white matter boundary' % [0.3222 0.5413]
'qualitymeasures' 'res_ECRmm' 'normal' [ 0.125 1.00] 'normalized gradient slope of the white matter boundary' % [0.3222 0.5413]
'qualitymeasures' 'res_ECR' 'linear' [ 0.125 1.00] 'normalized gradient slope of the white matter boundary' % [0.3222 0.5413]
%'qualitymeasures' 'res_MVR' 'linear' [ 0.50 3.00] 'mean voxel resolution'
%'qualitymeasures' 'res_vol' 'linear' [ 0.125 27] 'voxel volume'
%'qualitymeasures' 'res_isotropy' 'linear' [ 1.00 8.00] 'voxel isotropy'
Expand Down Expand Up @@ -167,7 +166,7 @@
evalnormalx = @(bst,wstd,bstm,wstm,bstl,wstl,x) setnan(isnan(x)+1) .* ...
(min(wstl,max(bstl,(1 - nv(x,bst,wstd)) .* abs(diff([bstm,wstm])) + bstm)));
evallinear = @(x,bst,wst) setnan(isnan(x)+1) .* ... max(0,
(min(def.wstl,max(def.bstl,(sign(wst-bst)*x - sign(wst-bst)*bst) ./ abs(diff([wst ,bst])) .* abs(diff([def.bstm,def.wstm])) + def.bstm)));
(min(def.wstl,max(-inf,(sign(wst-bst)*x - sign(wst-bst)*bst) ./ abs(diff([wst ,bst])) .* abs(diff([def.bstm,def.wstm])) + def.bstm)));
evalnormal = @(x,bst,wstd) setnan(isnan(x)+1) .* ...
(min(def.wstl,max(def.bstl,(1 - nv(x,bst,wstd)) .* abs(diff([def.bstm,def.wstmn])) + def.bstm)));

Expand Down Expand Up @@ -200,6 +199,9 @@
if nargin<1 || isempty(varargin{1})
error('MATLAB:cat_stat_marks:input','Need input structure with measurements!\n');
end
if numel(varargin{1}) > 1
def = varargin{2};
end
if ~isstruct(varargin{1})
error('MATLAB:cat_stat_marks:input','Second input has to be a structure!\n');
end
Expand Down Expand Up @@ -268,9 +270,18 @@

% SIQR is the successor of IQR and also uses the new edge-based resoltion rating
try
QAM.qualityratings.SIQR = rms([QAM.qualityratings.NCR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],8);
QAM.qualityratings.SIQR = rms([QAM.qualityratings.NCR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],8);
% further test cases
QAM.qualityratings.SIQR3rms2 = rms([QAM.qualityratings.NCR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],2);
QAM.qualityratings.SIQR3rms2 = rms([QAM.qualityratings.NCR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],8);
QAM.qualityratings.SIQR4rms2 = rms([QAM.qualityratings.NCR QAM.qualityratings.ICR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],2);
QAM.qualityratings.SIQR4rms8 = rms([QAM.qualityratings.NCR QAM.qualityratings.ICR QAM.qualityratings.res_RMS QAM.qualityratings.res_ECR],8);
catch
QAM.qualityratings.SIQR = nan;
QAM.qualityratings.SIQR = nan;
QAM.qualityratings.SIQR3rms2 = nan;
QAM.qualityratings.SIQR3rms8 = nan;
QAM.qualityratings.SIQR4rms2 = nan;
QAM.qualityratings.SIQR4rms8 = nan;
end
QAM.qualityratings.IQR = rms([QAM.qualityratings.NCR QAM.qualityratings.res_RMS ],8);
QAM.subjectratings.SQR = rms([QAM.subjectratings.vol_rel_CGW],8);
Expand All @@ -284,7 +295,7 @@
case {'cat_vol_qa202110','cat_vol_qa201901'} % older version
varargout{2} = {'NCR','ICR','res_RMS','contrastr'}; % ,'res_BB' is not working now
otherwise
varargout{2} = {'NCR','ICR','res_RMS','res_ECRmm','res_ECR','contrastr'}; % ,'res_BB' is not working now
varargout{2} = {'NCR','ICR','res_RMS','res_ECR','contrastr'}; % ,'res_BB' is not working now
end

case 'marks' % ausgabe einer leeren struktur
Expand Down
Loading

0 comments on commit 5bc5cc3

Please sign in to comment.