Avoid overwriting SPM.mat files from others and don't store additional contrasts in the original folder

Dear SPMers,

I got access to a dataset and I would like to compute additional T-contrasts. SPM by default overwrites the original SPM.mat file and also saves the computed contrast images in the original directory. I would like to avoid this behavior because I don’t want to mess around with the data from others and just add or manipulate data to/in their folders. Does anyone know if it’s possible to not overwrite the original SPM.mat files and to store the resulting images somewhere else and not in the original folder?

Example:

matlabbatch{1}.spm.stats.con.spmmat = {'/path/to/a/SPM/file/that/is/not/mine/SPM.mat'};
matlabbatch{1}.spm.stats.con.consess{1}.tcon.name = 'foobar';
matlabbatch{1}.spm.stats.con.consess{1}.tcon.weights = [1 0];
matlabbatch{1}.spm.stats.con.consess{1}.tcon.sessrep = 'none';
matlabbatch{1}.spm.stats.con.delete = 0;
spm_jobman('run',matlabbatch)

which outputs:

------------------------------------------------------------------------
30-Nov-2022 14:44:35 - Running job #1
------------------------------------------------------------------------
30-Nov-2022 14:44:35 - Running 'Contrast Manager'

SPM12: spm_contrasts.m                             14:44:35 - 30/11/2022
========================================================================
Contrasts folder                        :      ./ses-01/func/nback/first
	contrast image  5               :        ...written con_0005.nii
	spm{T} image  5                 :       ...written spmT_0005.nii
	Saving SPM.mat                  :               ...SPM.mat saved
Completed                               :          14:44:35 - 30/11/2022
30-Nov-2022 14:44:35 - Done    'Contrast Manager'

Not that I know of short of copying the whole folder containing your SPM.mat.

Other possibility: version control the data with datalad, so that you can keep everything in the same folder and still always go back to the pristine data you got from colleague.

I think I solved it: The solution is to create a copy of the SPM.mat file in the target directory and to change the file path fields SPM.VM.fname (mask image) SPM.VResMS.fname (residuals) and SPM.Vbeta.fname (beta-images) into absolute paths to the source directory. Only after doing that, SPM is able to find the respective files that it needs to compute contrasts (without the need to actually copy them over to the target directory). Here’s my code, maybe it’s helpful for others as well:

% Add additional statistical images by reading in SPM.mat files
% and running contrasts. Note: Not all subjects have completed all
% sessions and/or all tasks within the two sessions

%% Settings 
src_dir = 'path/to/src/dir';
tgt_dir = 'path/to/tgt/dir';

% create a new struct that holds the information on contrasts
tcon_struct.mid.neut = [0 1];
tcon_struct.mid.win = [1 0];
tcon_struct.faces.faces = [0 1];
tcon_struct.faces.shapes = [1 0];
tcon_struct.nback.twoback = [0 1];
tcon_struct.nback.zeroback = [1 0];

%% Preparation 

% find all SPM.mat files
cd(src_dir)
df = dir('**/SPM.mat');

% get found directories as cell array
folders = {df.folder};

% get full path to SPM.mat files
spm_filepaths_src = arrayfun(@(x) fullfile(x,'SPM.mat'), folders);
[df.spm_filepath_src] = spm_filepaths_src{:};

% create target directories for additional contrasts (which is 'subtracting' 
% the source directory from every folder and adding the target directory at the beginning
tgt_dirs = arrayfun(@(x) fullfile(tgt_dir,erase(x,src_dir)),folders);
[df.tgt_dir] = tgt_dirs{:};
for i=1:length(tgt_dirs)
    mkdir(tgt_dirs{i})
end

% create copies of every SPM.mat in the respective target directory
for i=1:length(tgt_dirs)
    copyfile(spm_filepaths_src{i},tgt_dirs{i})
end

% get full paths to target directories
spm_filepaths_tgt = arrayfun(@(x) fullfile(x, 'SPM.mat'), tgt_dirs);
[df.spm_filepath_tgt] = spm_filepaths_tgt{:};

% edit all copies
for i=1:length(spm_filepaths_tgt)
    edit_tgt_spm_mat(spm_filepaths_tgt{i},folders{i})
end

%% Run contrasts

% extract task using regular expression
tasks = arrayfun(@(x) regexp(x,'mid|faces|nback','match'),folders);
[df.task] = tasks{:};

% iterate over all rows and compute additional contrasts
for i=1:numel(df)

    spm_filepath = df(i).spm_filepath_tgt;
    task = df(i).task{:};
    task_struct = tcon_struct.(task);
    con_names = fieldnames(task_struct);
    
    % iterate over task-structure and compute all contrasts of interest
    for j=1:length(con_names)
        tcon_name = con_names{j};
        tcon_weights = task_struct.(tcon_name);
        t_contrast(spm_filepath,tcon_name,tcon_weights)
    end

end

%% FUNCTIONS 

% edit a copied SPM.mat file
function edit_tgt_spm_mat(spm_filepath_tgt,src_folder)
    
    % load the copied SPM.mat 
    load(spm_filepath_tgt);

    % change all necessary fields to absolute paths using source directory
    % that the copied SPM.mat file stems from
    SPM.VM.fname = fullfile(src_folder,SPM.VM.fname);
    SPM.VResMS.fname = fullfile(src_folder,SPM.VResMS.fname);
    
    % set fullpath for every beta-img
    % TODO: Can this also be done without a for loop
    beta_fnames = {SPM.Vbeta.fname};
    beta_fnames_full = cellfun(@(x) fullfile(src_folder,x),beta_fnames,'UniformOutput',false);
    for i=1:length(beta_fnames_full)
        SPM.Vbeta(i).fname = beta_fnames_full{i};
    end

    % overwrite the copied & edited SPM.mat
    save(string(spm_filepath_tgt),'SPM')

end

% create function that runs SPM to create contrast
function t_contrast(spm_filepath,tcon_name,tcon_weights)

matlabbatch{1}.spm.stats.con.spmmat = {spm_filepath};
matlabbatch{1}.spm.stats.con.consess{1}.tcon.name = tcon_name;
matlabbatch{1}.spm.stats.con.consess{1}.tcon.weights = tcon_weights;
matlabbatch{1}.spm.stats.con.consess{1}.tcon.sessrep = 'none';
matlabbatch{1}.spm.stats.con.delete = 0;
spm_jobman('run',matlabbatch)

end