Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
726b1b9
inital pass at manifest + run script + dockerfile
lmperry Aug 14, 2018
f860fd8
Merge branch 'master' into gear
lmperry Aug 15, 2018
0f3a8dd
Merge branch 'master' into gear
lmperry Sep 27, 2018
a92535b
Removed the stc option
lmperry Oct 10, 2018
cb6a809
Improve inplane file logic. Save json to output directory.
lmperry Oct 10, 2018
4e01420
Add freesurfer to path prior to matlab exe launch.
lmperry Oct 10, 2018
7354a64
Handle freesurfer license file
lmperry Oct 11, 2018
24b1d1a
Install freesurfer
lmperry Oct 11, 2018
cef3807
Parse config file for needed params
lmperry Oct 11, 2018
619e24a
zip outputs
lmperry Oct 11, 2018
44a161c
Modify inplane logic to find the most recent, with respect to the fir…
lmperry Oct 16, 2018
5849e72
update sytem calls
lmperry Nov 5, 2018
54c13df
BF for parfile reading + remove dependence on jsonio + remove QA opt.
lmperry Nov 5, 2018
77bcacd
Remove dependence on jsonio + remove QA opt.
lmperry Nov 5, 2018
faaf2ba
Start with matlab v93 mcr + include multi-stage build for fs + instal…
lmperry Nov 8, 2018
4313f08
matlab script to build the fLoc binary
lmperry Nov 8, 2018
5442140
improve output file generation and naming
lmperry Nov 9, 2018
205479f
make freesurfer license a configuration param.
lmperry Nov 9, 2018
69fa8da
fix license file writing + supress matlab commands
lmperry Nov 9, 2018
6c14830
Improve build script
lmperry Nov 9, 2018
4031457
update custom key
lmperry Nov 9, 2018
b7c544e
Merge branch 'gear' of https://github.com/VPNL/fLoc into gear
lmperry Nov 9, 2018
e3ae629
fix api_key base type
lmperry Nov 9, 2018
3b1598f
Update custom key and description.
lmperry Nov 11, 2018
56a4973
use 'f' option to override the read-only status of the destination fo…
lmperry Nov 11, 2018
ce1db5a
Streamline Dockerfile to reduce image size + bump version
lmperry Nov 17, 2018
6302ae0
WIP
lmperry Nov 17, 2018
12757ff
Merge branch 'master' into gear
lmperry Jan 7, 2019
d5f964a
Fix broken paths when loading files.
lmperry Jan 7, 2019
f8749fa
use fork for local path code
lmperry Jan 7, 2019
44d7e4a
bump version
lmperry Jan 7, 2019
678bd1d
parse subject code from session parent directory
lmperry Jan 8, 2019
93044c1
add proper logging
lmperry Jan 8, 2019
dcf7057
bump version
lmperry Jan 8, 2019
1c1891a
ENH: move to python3 for fw v3, update client + bump version
lmperry Sep 20, 2020
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
63 changes: 63 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Create Docker container that can run fLoc analysis.

# Start with the Matlab r2017b runtime container
FROM flywheel/matlab-mcr:v93

MAINTAINER Michael Perry <lmperry@stanford.edu>


############################
# Install dependencies

RUN apt-get update && apt-get install -y --force-yes \
xvfb \
xfonts-100dpi \
xfonts-75dpi \
xfonts-cyrillic \
zip \
unzip \
jq \
bc \
tar \
zip \
wget \
gawk \
tcsh \
libgomp1 \
perl-modules \
python-pip


############################
# Download Freesurfer v6.0.0 from MGH and untar to /opt

RUN wget -N -qO- ftp://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.0/freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0.tar.gz | tar -xz -C /opt && \
chown -R root:root /opt/freesurfer && \
rm -rf /opt/freesurfer/MCRv80 /opt/freesurfer/average /opt/freesurfer/subjects /opt/freesurfer/trctrain /opt/freesurfer/tktools && \
touch /opt/freesurfer/.license && \
chmod 777 /opt/freesurfer/.license


# Set the diplay env variable for xvfb
ENV DISPLAY :1.0

# Install Flywheel-SDK
RUN pip install flywheel-sdk>=12.04

# ADD the Matlab Stand-Alone (MSA) into the container.
# Must be compiled prior to gear build - this will fail otherwise
COPY gear/bin/fLocGearRun \
gear/bin/run_fLocGearRun.sh \
/usr/local/bin/

# Make directory for flywheel spec (v0)
ENV FLYWHEEL /flywheel/v0
RUN mkdir -p ${FLYWHEEL}

# Copy and configure run script and metadata code
COPY gear/run.py ${FLYWHEEL}/run
RUN chmod +x ${FLYWHEEL}/run
COPY gear/manifest.json ${FLYWHEEL}/manifest.json

# Configure entrypoint
ENTRYPOINT ["/flywheel/v0/run"]
16 changes: 10 additions & 6 deletions functions/fLocAnalysis.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,17 @@
% this change has been made to account for the problem of mixed up axes in some of
% the sessions.
for rr = 1: length(init_params.functionals)
unix([ sprintf('mri_convert --force_ras_good %s %s ', init_params.functionals{rr}, init_params.functionals{rr}) ])
status = system(sprintf('mri_convert --force_ras_good %s %s ', init_params.functionals{rr}, init_params.functionals{rr}));
if status ~= 0
error('Could not run mri_convert on %s', init_params.functionals{rr})
end

end

unix([ sprintf('mri_convert --force_ras_good %s %s ', init_params.inplane, init_params.inplane) ])
status = system(sprintf('mri_convert --force_ras_good %s %s ', init_params.inplane, init_params.inplane));
if status ~= 0
error('Could not run mri_convert on %s', init_params.inplane)
end


nii = readFileNifti(init_params.functionals{1}); nslices = size(nii.data, 3);
Expand Down Expand Up @@ -170,10 +177,7 @@
hi = initHiddenInplane('MotionComp', 1);
baseScan = 1; targetScans = 1:length(init_params.functionals);
[hi, M] = betweenScanMotComp(hi, 'MotionComp_RefScan1', baseScan, targetScans);
%%%%%%%%%%%%%%% changed this to create local paths MN 12/2018 %%%%%%%%
% fname = fullfile(session, 'Inplane', 'MotionComp_RefScan1', 'ScanMotionCompParams');
fname = fullfile('Inplane', 'MotionComp_RefScan1', 'ScanMotionCompParams');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fname = fullfile(session, 'Inplane', 'MotionComp_RefScan1', 'ScanMotionCompParams');
save(fname, 'M', 'baseScan', 'targetScans');
hi = selectDataType(hi, 'MotionComp_RefScan1');
saveSession; close all;
Expand Down
16 changes: 5 additions & 11 deletions functions/fLocAnalysisParams.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

% searches for all parfiles and nifti's in session directory
[~, session_id] = fileparts(session);
[~, subject_id] = fileparts(mrvDirup(session));
niifiles = dir(fullfile(session, '*.nii.gz')); niifiles = {niifiles.name};
niifiles = niifiles(~contains(niifiles,'._'));
parfiles = dir(fullfile(session, '*.par')); parfiles = {parfiles.name};
Expand All @@ -47,16 +48,11 @@
while sum(contains(lower(niifiles), ['run' num2str(num_runs + 1) '.nii.gz'])) >= 1
num_runs = num_runs + 1;
nii_idx = find(contains(lower(niifiles), ['run' num2str(num_runs) '.nii.gz']), 1);
%%changed this and similar cases below to create local paths, MN 12/18 %%%%%%%
%niipaths{num_runs} = fullfile(session, niifiles{nii_idx});
niipaths{num_runs} = fullfile(niifiles{nii_idx});
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
par_idx = find(contains(lower(parfiles), ['run' num2str(num_runs) '.par']), 1);
if isempty(par_idx)
fprintf('Error: no .par file found for run %d. \n\n', num_runs); return;
else

%parpaths{num_runs} = fullfile(session, parfiles{par_idx});
parpaths{num_runs} = fullfile(parfiles{par_idx});

end
Expand All @@ -74,16 +70,14 @@
fprintf('Warning: no inplane scan found for session %s. Generating pseudo inplane file. \n\n', session_id);
nii = niftiRead(niipaths{1}); nii.data = mean(nii.data, 4);
niftiWrite(nii, 'PseudoInplane.nii.gz');
% inplane = fullfile(session, 'PseudoInplane.nii.gz');
inplane = fullfile('PseudoInplane.nii.gz');
else
% inplane = fullfile(session, niifiles{inplane_idx});
inplane = fullfile(niifiles{inplane_idx});
end

% get the durations of TR and events
nii = niftiRead(niipaths{1}); TR = nii.pixdim(4);
pid = fopen(parfiles{1});
nii = niftiRead(fullfile(session,niipaths{1})); TR = nii.pixdim(4);
pid = fopen(fullfile(session, parpaths{1}));
ln1 = fgetl(pid); ln1(ln1 == sprintf('\t')) = '';
ln2 = fgetl(pid); ln2(ln2 == sprintf('\t')) = '';
prts1 = deblank(strsplit(ln1, ' ')); prts2 = deblank(strsplit(ln2, ' '));
Expand Down Expand Up @@ -111,7 +105,7 @@
init_params.motionCompRefScan = 1; % run number of reference scan for between-scans compensation

% necessary fields
init_params.sessionCode = session_id; % char array, local session data directory
init_params.sessionCode = session_id; % char array, local session data directory
init_params.doAnalParams = 1; % logical, set GLM analysis parameters during intialization
init_params.doSkipFrames = 1; % logical, clip countdown frames during initialization
init_params.doPreprocessing = 1; % logical, do some preprocessing during initialization
Expand All @@ -126,7 +120,7 @@
init_params.keepFrames = repmat([init_params.clip -1], num_runs, 1);

% descriptive fields for the mrVista session
init_params.subject = session_id(1:find(session_id == '_') - 1); % char array with participant ID
init_params.subject = subject_id;
init_params.description = 'localizer'; % char array describing session
init_params.comments = 'Analyzed using fLoc'; % char array of comments
init_params.annotations = annotations; % cell array of descriptions for each run
Expand Down
2 changes: 1 addition & 1 deletion functions/fLocAnalysisWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function fLocAnalysisWrapper(sessions, clip, stc, QA)
for ss = 1:length(sessions)
try
fprintf('\nStarting analysis of session %s. \n', sessions{ss});
[T, err] = evalc('fLocAnalysis(sessions{ss}, [], [], clip(ss), QA)');
[T, err] = evalc('fLocAnalysis(sessions{ss}, [], [], clip(ss), stc, QA)');
fid = fopen(fullfile(sessions{ss}, 'vistasoft_log.txt'), 'w+'); %Maybe change name
fprintf(fid, '%s', T); fclose(fid);
catch
Expand Down
50 changes: 43 additions & 7 deletions functions/fLocGearRun.m
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
function [session, init_params, glm_params] = fLocGearRun(session, config)
% Generate data structures of vistasoft parameters for preprocessing and
% analyzing fLoc data with a GLM using a Flywheel gear.
function err = fLocGearRun(session, config_file, out_dir, fshome)
% Generate data structures of vistasoft parameters for preprocessing and
% analyzing fLoc data with a GLM using a Flywheel gear.
%
% INPUTS
% 1) session -- path to data directory in Flywheel (char array)
% 2) config -- path to Flywheel config file (char array)
%
% 3) out_dir -- path to save outputs
% 4) fshome -- path to Freesurfer's directory
%
% OUPUTS
% 1) session -- path to data directory in Flywheel (char array)
% 2) init_params -- parameters for initialization/preprocessing (struct)
% 3) glm_params -- parameters for running GLM analysis (struct)
%
%
% AS 8/2018

%% Set env and parse config file for params

% Set the env for fs bin
setenv('FREESURFER_HOME', fshome);
setenv('PATH', [getenv('PATH'), ':', fullfile(fshome, 'bin')]);
disp(getenv('FREESURFER_HOME'));
disp(getenv('PATH'));

% Read the json file
config = jsondecode(fileread(config_file));

% Set the params
clip = config.config.clip;

% Currently we're not setting those params in the config/manifest
% file, thus we set the defaults below.
[~, init_params, glm_params] = fLocAnalysisParams(session, clip);

%% Run fLoc Analysis
err = fLocAnalysis(session, init_params, glm_params, clip, QA);
err = fLocAnalysis(session, init_params, glm_params, clip);

clear global
%% Move select files to output and zip outputs
if err == 0
[base_path, session_label] = fileparts(session);
[~, subject_code] = fileparts(base_path);
jpgs = mrvFindFile('*.jpg', session);
for ii=1:numel(jpgs)
[~, f, e] = fileparts(jpgs{ii});
copyfile(jpgs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e]), 'f');
end
logs = mrvFindFile('*log*', session);
for ii=1:numel(logs)
[~, f, e] = fileparts(logs{ii});
copyfile(logs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e]), 'f');
end
fprintf('Zipping outputs [%s]...\n', mrvDirup(session));
zip(fullfile(out_dir, [subject_code, '_', session_label, '-fLoc.zip']), mrvDirup(session));
rmdir(session, 's');

clear global

end
65 changes: 65 additions & 0 deletions gear/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# fLoc Flywheel Gear (vpnl/floc)

This Docker context creates a Flywheel Gear that can analyze data generated with the Functional localizer experiment used to define category-selective cortical regions (published in [Stigliani et al., 2015](http://www.jneurosci.org/content/35/36/12412)).

By default the Gear generates the following voxel-wise parameters maps: Beta values, model residual error, proportion of variance explained, and GLM contrasts (t-values). All parameter maps are saved as .mat and nifti files in `session/Inplane/GLMs/` and can be viewed in Vistasoft. The Gear also writes a file named `fLocAnalysis_log.txt` that logs progress and saves input and glm parameters as `fLocAnalysisParams.mat`. If there are 10 conditions specified, 15 contrast maps will be generated. 10 maps will contrast each individual condition versus all others. The other 5 maps will contrast conditions 1 and 2 vs all others, 3 and 4 versus all others, and so on. If there are not 10 conditions specified in the parfiles, then the maps generated will contrast each individual condition versus all others.

## Instructions

Follow the instructions below to build the Gear.

### Building

1. Make sure you have Matlabr2017b and Docker installed on your LINUX machine
2. Clone fLoc repository on the computer you will use to build the image.
3. Run the build function in Matlab - (`fLoc/gear/bin/fLocGearRun_Build.m`)
```bash
cd fLoc/gear/bin/
/software/matlab/r2017b/bin/matlab -nodesktop -r fLocGearRun_Build
```
4. Build the image
```bash
docker build -t vpnl/floc fLoc/Dockerfile
```

### Execution

Execution of this Gear is limited to a Flywheel instance. Below are some important notes to get the gear to run correctly on your data.

1. This gear is means to run at the session level (one Subject at a time).
2. Ensure that you have uploaded the PAR files for each of the acquisitions you wish to be analyzed.
3. Run the analysis gear and set your CLIP and Freesurfer_License config parameters
4. The Gear requires no input files, as it relies on the Flywheel SDK to gather the required data.
5. Inplane logic: In the case that multiple inplane files exist, the Gear will use the last inplane prior to the first BOLD scan.

### Results

The main analysis results are zipped in an archive using the convention of:
```
<subject_code>/<session>-fLoc.zip
```

An example output looks like this (subject_code=s181, session=8111):
```
├── s181_8111-fLocAnalysis_log.txt
├── s181_8111-fLoc.zip
├── s181_8111-input_data.json
└── s181_8111-Within_Scan_Motion_Est.jpg
```

The output archive contains the following directory structure:
```
s181/
└── 8111
├── Images
└── Inplane
├── GLMs
│   ├── NiftiMaps
│   ├── RawMaps
│   └── Scan1
├── MotionComp
│   └── TSeries
└── MotionComp_RefScan1
└── TSeries

```
38 changes: 38 additions & 0 deletions gear/bin/fLocGearRun_Build.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
% This script should be run on Matlab 2017b, GLNXA64.
%
% EXAMPLE USAGE
% /software/matlab/r2017b/bin/matlab -nodesktop -r fLocGearRun_Build

% Check that we are running a compatible version
if (isempty(strfind(version, '9.3.0'))) || (isempty(strfind(computer, 'GLNXA64')))
error('You must compile this function using R2017b (9.3.0.713579) 64-bit (glnxa64). You are using %s, %s', version, computer);
end

disp(mfilename('fullpath'));
compileDir = fileparts(mfilename('fullpath'));
if ~strcmpi(pwd, compileDir)
disp('You must run this code from %s', compileDir);
end


% Download the source code
disp('Cloning source code...');
%system('git clone https://github.com/vistalab/vistasoft');
system('git clone https://github.com/MNordt/vistasoft --branch localPaths');

% Set paths
disp('Adding paths to build scope...');
restoredefaultpath;
addpath(genpath(fullfile(pwd, 'vistasoft')));
addpath(genpath('../../fLoc'))

% Compile
disp('Running compile code...');
mcc -v -R -nodisplay -m ../../functions/fLocGearRun.m

% Clean up
disp('Cleaning up...')
rmdir(fullfile(pwd, 'vistasoft'), 's');

disp('Done!');
exit
36 changes: 36 additions & 0 deletions gear/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "floc",
"label": "VPNL: fLoc - Face Localizer Analysis Pipeline",
"description": "Automated analysis of fMRI data from fLoc funcional localizer experiment used to define category-selective cortical regions. By default the Gear generates the following voxel-wise parameters maps: Beta values, model residual error, proportion of variance explained, and GLM contrasts (t-values). All parameter maps are saved as .mat and nifti files in session/Inplane/GLMs/ and can be viewed in Vistasoft. The Gear also writes a file named 'fLocAnalysis_log.txt' that logs progress and saves input and glm parameters as fLocAnalysisParams.mat. If there are 10 conditions specified, 15 contrast maps will be generated. 10 maps will contrast each individual condition versus all others. The other 5 maps will contrast conditions 1 and 2 vs all others, 3 and 4 versus all others, and so on. If there are not 10 conditions specified in the parfiles, then the maps generated will contrast each individual condition versus all others.",
"cite": "Temporal Processing Capacity in High-Level Visual Cortex Is Domain Specific. Anthony Stigliani, Kevin S. Weiner and Kalanit Grill-Spector. Journal of Neuroscience 9 September 2015, 35 (36) 12412-12424; DOI: https://doi.org/10.1523/JNEUROSCI.4822-14.2015",
"author": "Anthony Stigliani, VPNL, Stanford",
"maintainer": "Michael Perry <lmperry@stanford.edu>",
"url": "https://github.com/VPNL/fLoc",
"source": "https://github.com/VPNL/fLoc",
"license": "Other",
"flywheel": "0",
"version": "0.3.0",
"custom": {
"docker-image": "vpnl/floc:0.3.0",
"flywheel": {
"suite": "VPNL"
}
},
"inputs": {
"api_key": {
"description": "Calling user's api key.",
"base": "api-key",
"read-only": true
}
},
"config": {
"clip": {
"description": "Number of TRs to clip from the beginning of each run (int)",
"type": "integer"
},
"freesurfer_license": {
"description": "FreeSurfer license file text, provided during registration with FreeSurfer. This text will by written to the license file within the '$FSHOME' directory and used during execution of the Gear.",
"type": "string"
}
}
}
Loading