Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions aa_engine/aas_firstlevel_model_prepare.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,26 +120,40 @@
%%%%%%%%%%%%%%%%%%%
clear SPM

%% retrieve TR from DICOM header
% TR is manually specified (not recommended as source of error)
%% retrieve TR


if isfield(aap.tasklist.currenttask.settings,'TR') && ...
~isempty(aap.tasklist.currenttask.settings.TR)
SPM.xY.RT =aap.tasklist.currenttask.settings.TR;

% TR is manually specified

SPM.xY.RT = aap.tasklist.currenttask.settings.TR;

else
% Get TR from DICOM header checking they're the same for all sessions

% Get TR from header

for sess=subjSessionI
try
DICOMHEADERS=load(aas_getfiles_bystream(aap,subj,sess,'epi_dicom_header'));
catch
DICOMHEADERS=load(aas_getfiles_bystream(aap,subj,sess,'epi_header')); % For backwards compatibility

if aas_isfile_bystream(aap,subj,sess,'epi_dicom_header') % epi
DICOMHEADERS = load(aas_getfiles_bystream(aap,subj,sess,'epi_dicom_header'));

elseif aas_isfile_bystream(aap,subj,sess,'dot_header') % dot
DICOMHEADERS = load(aas_getfiles_bystream(aap,subj,sess,'dot_header'));

elseif aas_isfile_bystream(aap,subj,sess,'epi_header') % backwards compatibility
DICOMHEADERS = load(aas_getfiles_bystream(aap,subj,sess,'epi_header'));

else
aas_log(aap,true,sprintf('No header found for this data. Exiting...'));

end

try
TR=DICOMHEADERS.DICOMHEADERS{1}.volumeTR;
TR = DICOMHEADERS.DICOMHEADERS{1}.volumeTR;
catch
end
% [AVG] This is for backwards compatibility!
if ~exist('TR','var') || isempty(TR)
TR=DICOMHEADERS.DICOMHEADERS{1}.volumeTR;
aas_log(aap,true,sprintf('TR not found in header. Exiting...'));
end

if (sess==subjSessionI(1))
Expand All @@ -149,7 +163,9 @@
aas_log(aap,true,sprintf('Session %d has different TR from earlier sessions, they can''t be in the same model.',sess));
end
end

end

end

%% Set up basis functions
Expand Down
6 changes: 5 additions & 1 deletion aa_engine/aas_renamestream.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
end

if strcmp(type,'output')
aas_log(aap,false,sprintf('WARNING: Renaming output %s of %s to %s.\n It might break dependency for the consecuting module(s).\n Make sure that you know what you are doing!',origstream,stage,newstream));
if (isempty(newstream))
aas_log(aap,false,sprintf('WARNING: Removing output "%s" of "%s"\n Downstream modules that take "%s" as an input will be passed an alternate source.',origstream,stage,origstream));
else
aas_log(aap,false,sprintf('WARNING: Renaming output "%s" of "%s" to "%s".\n Downstream modules that take "%s" as an input may also need to rename the stream.',origstream,stage,origstream,newstream));
end
end

%% Locate STAGE
Expand Down
257 changes: 257 additions & 0 deletions aa_modules/aamod_QA.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
function [aap,resp] = aamod_QA(aap, task)
%
% generate a QA summary of functional data
%
% Change History
%
% fall 2023 [MSJ] -- new
%

resp='';

switch task

case 'report'

case 'doit'


nvoxels = aap.tasklist.currenttask.settings.nvoxels;
Comment thread
tiborauer marked this conversation as resolved.
Resolved
carpetscale = aap.tasklist.currenttask.settings.carpetscale;

outlier_filter = aap.tasklist.currenttask.settings.outlier_filter;
outlier_threshold = aap.tasklist.currenttask.settings.outlier_threshold;

savedir = fullfile(aas_getstudypath(aap),'QA_images');
if ~exist(savedir,'dir');mkdir(savedir);end

[ ~,subindex_list ] = aas_getN_bydomain(aap,'subject');

summary_fname = fullfile(aas_getstudypath(aap),'data_summary.txt');

if exist(summary_fname,'file');delete(summary_fname);end

fid = fopen(summary_fname,'w'); % plaintext stats summary....

if (fid < 0)
aas_log(aap, true, 'Cannot open summary file for writing. Halting...');
end

group_outliers = []; % for group outlier plot...

for subject_index = subindex_list

[ ~,all_sess ] = aas_getN_bydomain(aap, 'session', subject_index);
session_list = intersect(all_sess, aap.acq_details.selected_sessions);

for session_index = session_list

data_instream_struct = aap.tasklist.currenttask.inputstreams(1).stream{1};
fname = aas_getfiles_bystream(aap, subject_index, session_index, data_instream_struct.CONTENT);
Comment thread
tiborauer marked this conversation as resolved.
Resolved

header = spm_vol(fname);
data = spm_read_vols(header);

[ r,c,s,f ] = size(data);
data = reshape(data,r*c*s,f); % nvoxels x nframes

% stats

nancount = sum(isnan(data(:)));
zerocount = sum(data(:)==0);
voxsize = diag(header(1).mat);
voxsize = voxsize(1:3);

% swap nan for zeros -- so masking can strip nan

if (nancount > 0)
data(isnan(data)) = 0;
end

datamax = max(data(:));
datamin = min(data(:));

% extract voxel subset for processing / plotting

selector = floor(linspace(1,r*c*s,nvoxels));
data = data(selector,:);

% we don't have a brainmask, so instead do a
% quick and dirty implicit mask: we assume any
% identically zero rows are outside of brain
% this results in a reasonably good selection
% especially if nvoxels is large-ish

data = data(all(data~=0,2),:);

% style points - open window in center of display
% (this trick only works if not running cluster)

if (strcmp(aap.options.wheretoprocess, 'localsingle'))
h = figure('Position',[0 0 1500 1000], 'Color', [1 1 1],'NumberTitle','off', 'Visible', 'off', 'MenuBar', 'none');
movegui(h, 'center');
set(h, 'Visible', 'on');
else
h = figure('Position',[0 0 1500 1000],'Color', [1 1 1], 'NumberTitle','off','MenuBar','none');
end

subplot(3,3,[5 6 8 9]);

% joy plot

surf(data,log(abs(data)));
axis tight
xlabel('frame','FontSize',18);
ylabel('voxel','FontSize',18);
set(gca,'FontSize',18);
shading interp;
colormap gray;
cm = colormap;
colormap(cm.*cm.*cm); % suppresses midrange
view(-25,60);

% traditional carpet plot for comparison

subplot(3,3,[4 7]);
imagesc(data,[carpetscale(1)*max(data(:)) carpetscale(2)*max(data(:))]);
xlabel('frame','FontSize',14);
ylabel('voxel','FontSize',14);
title('grayplot','FontSize',14);

% sum over voxels plot and outliers

subplot(3,3,[1 2 3]);

sumovervoxels = sum(data,1,'omitnan');
plot(sumovervoxels,'k','LineWidth',2);
a = axis;
hold on;

switch outlier_filter

case 'anymedian'

outliers = isoutlier(data,'median',2, 'ThresholdFactor',outlier_threshold);
outliers = any(outliers,1);

case 'anymean'

outliers = isoutlier(data,'mean',2, 'ThresholdFactor',outlier_threshold);
outliers = any(outliers,1);

case 'mediansum'

outliers = isoutlier(sumovervoxels,'median', 'ThresholdFactor',outlier_threshold);

case 'meansum'

outliers = isoutlier(sumovervoxels,'mean', 'ThresholdFactor',outlier_threshold);

case 'none'

outliers = [];

end

outlier_index = find(outliers);
if ~isempty(outlier_index)
plot(outlier_index,0.5*(a(3)+a(4)),'r+','LineWidth',2,'MarkerSize',12);
end
axis tight;

% include stats in title

[~,n,~]=fileparts(fname);
n = strrep(n,'_','-');
titlestring = [n ' dim: ' num2str([r c s f]) ' voxsize: ' num2str(voxsize') ' max: ' num2str(datamax,3) ' min: ' num2str(datamin,3) ' nan: ' num2str(nancount) ' zeros: ' num2str(zerocount)];
title(titlestring,'FontSize',18);

if ~isempty(outlier_index)
odescriptor = sprintf('outliers (%s > %g)', outlier_filter, outlier_threshold);
legend({'sum over voxels', odescriptor},'FontSize',16);
else
legend('sum over voxels','FontSize',16);
end
xlabel('frame','FontSize',18);
ylabel('intensity','FontSize',18);

fname_out = fullfile(savedir,sprintf('%s.jpg', n));

set(h,'Renderer','opengl');
set(findall(h,'Type','text'),'FontUnits','normalized');
h.InvertHardcopy = 'off'; % otherwise matlab changes our nice black bg to white
print(h, '-djpeg', '-r150', fname_out);

close(h);

% collect outliers for group outlier plot

if (isempty(group_outliers))
group_outliers(1,:) = outliers;
else
% must protect against unequal number-of-frames
[~,ncols] = size(group_outliers);
if (ncols < length(outliers))
% this will zero extend ALL rows of group_outliers
% to new width so we can add this data to bottom
group_outliers(1,length(outliers)) = 0;
end
if (ncols > length(outliers))
% this will extend current outliers to fit
% group_outliers (we assume frame(1) are aligned)
outliers(ncols) = 0;
end
group_outliers(end+1,:) = outliers;
end

% add stats to summary file

fprintf(fid,'%s outliers: %d\n', titlestring, sum(outliers));

end % session

end % subject

fclose(fid); % summary_fname

% group outlier plot
% this is useful for detecting correlated artifacts across subjects

% get a little better appearance if we size the window to fit data

[nrows,ncols] = size(group_outliers);

if (strcmp(aap.options.wheretoprocess, 'localsingle'))
h2 = figure('Position',[0 0 6*ncols 20*nrows], 'Color', [1 1 1],'NumberTitle','off', 'Visible', 'off', 'MenuBar', 'none');
movegui(h2, 'center');
set(h2, 'Visible', 'on');
else
h2 = figure('Position',[0 0 6*ncols 20*nrows],'Color', [1 1 1], 'NumberTitle','off','MenuBar','none');
end

group_outliers(end+1,end+1) = 0; % pcolor needs a dummy row/col
pcolor(group_outliers);
title('Outliers','FontSize',16);
xlabel('frame','FontSize',14);
ylabel('subject','FontSize',14);
colormap gray;

fname_out = fullfile(aas_getstudypath(aap),'group_outliers.jpg');
set(h2,'Renderer','opengl');
set(findall(h2,'Type','text'),'FontUnits','normalized');
h2.InvertHardcopy = 'off';
print(h2, '-djpeg', '-r150', fname_out);
close(h2);

aap = aas_desc_outputs(aap, 'data_summary', summary_fname);

% done!

case 'checkrequirements'

otherwise

aas_log(aap,true,sprintf('Unknown task %s',task));

end % switch
end % function
39 changes: 39 additions & 0 deletions aa_modules/aamod_QA.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<aap>
<tasklist>
<currenttask domain='study' desc='make QA summary of functional data' modality='MRI'>

<!-- generate a collection of epi QA maps -->

<qsub>
<timeBase>1</timeBase>
<memoryBase>1</memoryBase>
</qsub>

<!-- number of voxels to include in QA plots -->

<!-- actual number will probably be less, as any NaN or -->
<!-- zero timeseries are asssumed outside brain and skipped -->

<nvoxels>1000</nvoxels>

<!-- min/max color scaling for the carpet plot -->

<carpetscale>[-2 0.8]</carpetscale>

<!-- outlier flagging -->

<outlier_filter options='anymedian|anymean|mediansum|meansum|none'>meansum</outlier_filter>
<outlier_threshold>3</outlier_threshold>

<inputstreams>
<stream isrenameable='1'>epi</stream>
</inputstreams>

<outputstreams>
<stream>QA_summary</stream>
</outputstreams>

</currenttask>
</tasklist>
</aap>
Loading