From 726b1b9d480f79481270a39d3a0be901c0d87fca Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Tue, 14 Aug 2018 15:15:14 -0700 Subject: [PATCH 01/31] inital pass at manifest + run script + dockerfile --- Dockerfile | 37 +++++++++ gear/manifest.json | 35 ++++++++ gear/run.py | 196 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 Dockerfile create mode 100644 gear/manifest.json create mode 100755 gear/run.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..cd73ac23 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Create Docker container that can run fLoc analysis. + +# Start with the Matlab r2017a runtime container +FROM flywheel/matlab-mcr:v92.1 + +MAINTAINER Michael Perry + +############################ +# Install dependencies +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --force-yes \ + xvfb \ + xfonts-100dpi \ + xfonts-75dpi \ + xfonts-cyrillic \ + zip \ + unzip \ + python \ + jq + +# Set the diplay env variable for xvfb +ENV DISPLAY :1.0 + +# ADD the Matlab Stand-Alone (MSA) into the container. +# COPY gear/bin/gear_floc /usr/local/bin/floc + +# 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 ${FLYWHEEL}/run +RUN chmod +x ${FLYWHEEL}/run +COPY gear/manifest.json ${FLYWHEEL}/manifest.json + +# Configure entrypoint +ENTRYPOINT ["/flywheel/v0/run"] diff --git a/gear/manifest.json b/gear/manifest.json new file mode 100644 index 00000000..5e0bcf66 --- /dev/null +++ b/gear/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "floc", + "label": "VPNL: fLoc - Face Localizer Analysis Pipeline", + "description": "Functional localizer experiment used to define category-selective cortical regions (published in Stigliani et al., 2015).", + "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", + "maintainer": "Michael Perry Date: Wed, 10 Oct 2018 14:23:54 -0700 Subject: [PATCH 02/31] Removed the stc option --- gear/manifest.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index 5e0bcf66..a01e48aa 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -25,11 +25,6 @@ "clip": { "description": "Number of TRs to clip from the beginning of each run (int)", "type": "integer" - }, - "stc": { - "description": "slice time correction flag (logical; default = 0, no STC)", - "default": 0, - "type": "integer" } } } From cb6a809091201e79ba9afdef83d108869d0b16fe Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 16:47:59 -0700 Subject: [PATCH 03/31] Improve inplane file logic. Save json to output directory. --- gear/run.py | 57 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/gear/run.py b/gear/run.py index 1e71651e..1e355057 100755 --- a/gear/run.py +++ b/gear/run.py @@ -41,7 +41,7 @@ def get_timestamp_delta(timestamp, reference, absolute_value=False): return delta.total_seconds() -def fLoc_data(config=None, data_dir='/flywheel/v0/output/'): +def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): # Get the container info from the config doc analysis_id = str(config['destination']['id']) @@ -63,7 +63,7 @@ def fLoc_data(config=None, data_dir='/flywheel/v0/output/'): session = fw.get_session(parent_session_id) session_acquisitions = fw.get_session_acquisitions(parent_session_id) - data_dir = os.path.join(data_dir, 'fLoc', session['label']) + data_dir = os.path.join(out_dir, 'fLoc', session['label']) print('Data dir set to: %s' % (data_dir)) if not os.path.isdir(data_dir): os.makedirs(data_dir) @@ -113,24 +113,33 @@ def fLoc_data(config=None, data_dir='/flywheel/v0/output/'): # FIND IN-PLANE SCAN - # TODO: Find the correct inplane when one or more exist. + inplanes = [] for a in session_acquisitions_full: for f in a['files']: - if f['classification'].has_key('Features') and 'In-Plane' in f['classification']['Features']: - nifti = [ x for x in a['files'] if x['type'] == 'nifti' ] - if nifti and len(nifti) == 1: - nifti = nifti[0] - nifti_rename = 'Inplane.nii.gz' - this_input = {'nifti': nifti['name'], - 'nifti_out_name': nifti_rename, - 'acquisition_id': a['_id'], - 'label': a['label'], - 'timestamp': a['timestamp'].strftime('%Y-%m-%dT%H:%M:%S')} - input_files.append(this_input) - print('Found associated inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) - fw.download_file_from_acquisition(a['_id'], - f['name'], - os.path.join(data_dir, nifti_rename)) + if f['classification'] and f['classification'].has_key('Features') and 'In-Plane' in f['classification']['Features']: + inplanes.append(a) + + if inplanes and len(inplanes) >1: + print('More than one Inplane acquisition was found!!!') + + # Find the correct inplane when one or more exist, making the assumption that + # the last inplane is the one we actually want to use. + inplanes_sorted = sorted(inplanes, key=lambda k: k['timestamp']) + inplane = inplanes_sorted[-1] + nifti = [ x for x in inplane['files'] if x['type'] == 'nifti' ] + if nifti and len(nifti) == 1: + nifti = nifti[0] + nifti_rename = 'Inplane.nii.gz' + this_input = {'nifti': nifti['name'], + 'nifti_out_name': nifti_rename, + 'acquisition_id': inplane['_id'], + 'label': inplane['label'], + 'timestamp': inplane['timestamp'].strftime('%Y-%m-%dT%H:%M:%S')} + input_files.append(this_input) + print('Found associated inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) + fw.download_file_from_acquisition(inplane['_id'], + nifti['name'], + os.path.join(data_dir, nifti_rename)) # TRACK INPUTS AND WRITE TO FILE inputs = { "session_id": parent_session_id, @@ -140,12 +149,12 @@ def fLoc_data(config=None, data_dir='/flywheel/v0/output/'): } # Write out the json file with input files - json_file = os.path.join(data_dir, 'input_data.json') + json_file = os.path.join(out_dir, 'input_data.json') with open(json_file, 'w') as jf: json.dump(inputs, jf) - return inputs + return inputs, data_dir ############################################################################### @@ -181,16 +190,20 @@ def fLoc_data(config=None, data_dir='/flywheel/v0/output/'): # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) - input_files = fLoc_data(config, args.output_dir) + input_files, data_dir = fLoc_data(config, args.output_dir) if not input_files: print('Errors finding input files...') os.sys.exit(1) # RUN MATLAB CODE + run_command = '%s %s %s %s' % (matlab_binary, matlab_library, data_dir, args.config_file) + os.system(run_command) + # COMPRESS OUTPUTS (preserve the config/json/log files at the top-level?) + # The results come in a deeply nested directory tree, thus we want to compress the outputs so that they are easy to work withself. - # COMPRESS OUTPUTS (preserve the json at the top-level) + # Preserve the directory tree in json format for easy navigation outside of the platform. - Package list for the zip file. # EXIT From 4e014207fa2293cd15daac9aba75cc19222b3f97 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 16:56:01 -0700 Subject: [PATCH 04/31] Add freesurfer to path prior to matlab exe launch. --- gear/run.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gear/run.py b/gear/run.py index 1e355057..55343463 100755 --- a/gear/run.py +++ b/gear/run.py @@ -197,7 +197,8 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): os.sys.exit(1) # RUN MATLAB CODE - run_command = '%s %s %s %s' % (matlab_binary, matlab_library, data_dir, args.config_file) + path_string = 'export PATH=$PATH:/opt/freesurfer/bin; ' + run_command = '%s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file) os.system(run_command) # COMPRESS OUTPUTS (preserve the config/json/log files at the top-level?) From 7354a64de24edea9ffe8b69b118bc5d9877a7859 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 17:03:27 -0700 Subject: [PATCH 05/31] Handle freesurfer license file --- gear/manifest.json | 4 ++++ gear/run.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index a01e48aa..70b8ab5d 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -19,6 +19,10 @@ "api_key": { "description": "User's api key", "base": "api_key" + }, + "freesurfer_license": { + "description": "FreeSurfer license file, provided during registration with FreeSurfer. This file will by copied to the $FSHOME directory and used during execution of the Gear.", + "base": "file" } }, "config": { diff --git a/gear/run.py b/gear/run.py index 55343463..ab30ba27 100755 --- a/gear/run.py +++ b/gear/run.py @@ -163,10 +163,12 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): if __name__ == '__main__': import urllib3 + import shutil + import argparse + urllib3.disable_warnings() os.environ['FLYWHEEL_SDK_SKIP_VERSION_CHECK'] = '1' - import argparse ap = argparse.ArgumentParser() ap.add_argument('--config_file', type=str, @@ -188,6 +190,10 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): print(' Creating SDK client...') fw = flywheel.Flywheel(config['inputs']['api_key']['key']) + # Copy FS license into place + license_file_path = config['inputs']['freesurfer_license']['location']['path'] + shutil.copyfile(license_file_path, '/opt/freesurfer/.license') + # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) input_files, data_dir = fLoc_data(config, args.output_dir) @@ -197,7 +203,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): os.sys.exit(1) # RUN MATLAB CODE - path_string = 'export PATH=$PATH:/opt/freesurfer/bin; ' + path_string = 'export FSHOME=/opt/freesurfer; export PATH=$PATH:/opt/freesurfer/bin; ' run_command = '%s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file) os.system(run_command) From 24b1d1a9c2247b37b7e4b0f7d394f625bbba1bad Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 17:03:45 -0700 Subject: [PATCH 06/31] Install freesurfer --- Dockerfile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Dockerfile b/Dockerfile index cd73ac23..41d14247 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,26 @@ RUN apt-get update && apt-get install -y --force-yes \ # Set the diplay env variable for xvfb ENV DISPLAY :1.0 +RUN apt-get update && apt-get -y install \ + bc \ + tar \ + zip \ + wget \ + gawk \ + tcsh \ + python \ + libgomp1 \ + python2.7 \ + perl-modules + +# 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 + +# The brainstem and hippocampal subfield modules in FreeSurfer 6.0 require the Matlab R2012 runtime +RUN apt-get install -y libxt-dev libxmu-dev +ENV FREESURFER_HOME /opt/freesurfer +RUN wget -N -qO- "http://surfer.nmr.mgh.harvard.edu/fswiki/MatlabRuntime?action=AttachFile&do=get&target=runtime2012bLinux.tar.gz" | tar -xz -C $FREESURFER_HOME && chown -R root:root /opt/freesurfer/MCRv80 + # ADD the Matlab Stand-Alone (MSA) into the container. # COPY gear/bin/gear_floc /usr/local/bin/floc From cef3807dbe0923cca1a6e5c375e79630c9fc1da6 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 17:04:58 -0700 Subject: [PATCH 07/31] Parse config file for needed params --- functions/fLocGearRun.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index 2b514097..6e679916 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -1,23 +1,33 @@ -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) +% 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) -% +% % 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 +%% Parse config file for params. + +% Read the json file +config = jsonread(config); + +% 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); clear global - end From 619e24a43560f0f9dd4139cb293dd8daf7adb546 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 10 Oct 2018 17:18:14 -0700 Subject: [PATCH 08/31] zip outputs --- functions/fLocGearRun.m | 6 +++++- gear/run.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index 6e679916..0a682ae8 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -1,4 +1,4 @@ -function err = fLocGearRun(session, config) +function err = fLocGearRun(session, config, out_dir) % Generate data structures of vistasoft parameters for preprocessing and % analyzing fLoc data with a GLM using a Flywheel gear. % @@ -28,6 +28,10 @@ %% Run fLoc Analysis err = fLocAnalysis(session, init_params, glm_params, clip, QA); +if err == 0 + zip(fullfile(out_dir, 'fLoc_output.zip'), session); + delete(session) + clear global end diff --git a/gear/run.py b/gear/run.py index ab30ba27..3862cb5a 100755 --- a/gear/run.py +++ b/gear/run.py @@ -204,7 +204,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): # RUN MATLAB CODE path_string = 'export FSHOME=/opt/freesurfer; export PATH=$PATH:/opt/freesurfer/bin; ' - run_command = '%s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file) + run_command = '%s %s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file, args.output_dir) os.system(run_command) # COMPRESS OUTPUTS (preserve the config/json/log files at the top-level?) From 44a161c40cd128d94e3bf39e1a020658c49a7cb0 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Tue, 16 Oct 2018 15:27:19 -0700 Subject: [PATCH 09/31] Modify inplane logic to find the most recent, with respect to the first bold scan, of multiple inplanes. --- gear/run.py | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/gear/run.py b/gear/run.py index 3862cb5a..835bde2f 100755 --- a/gear/run.py +++ b/gear/run.py @@ -2,6 +2,7 @@ import os import json +import time import shutil import flywheel import numpy as np @@ -21,7 +22,7 @@ def parse_config(config_json_file): with open(config_json_file, 'r') as jsonfile: config = json.load(jsonfile) - return config + return config['config'] def get_timestamp_delta(timestamp, reference, absolute_value=False): """ @@ -94,7 +95,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): 'nifti_out_name': nifti_rename, 'acquisition_id': a['_id'], 'label': a['label'], - 'timestamp': a['timestamp'].strftime('%Y-%m-%dT%H:%M:%S')} + 'timestamp': a['timestamp']} input_files.append(this_input) print(' Found associated nifti file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) fw.download_file_from_acquisition(a['_id'], @@ -119,13 +120,22 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): if f['classification'] and f['classification'].has_key('Features') and 'In-Plane' in f['classification']['Features']: inplanes.append(a) + print('%d Inplane acquisitions were found!' % (len(inplanes))) + if inplanes and len(inplanes) >1: - print('More than one Inplane acquisition was found!!!') + # Find the correct inplane when one or more exist, making the assumption that + # the last inplane is the one we actually want to use. + inplanes_sorted = sorted(inplanes, key=lambda k: k['timestamp']) + + # The inplane is the last inplane prior to the first BOLD scan. + # Get the earliest timestamp in the input+files array and compare that to the list of inplane files + first_timestamp = sorted(input_files, key=lambda k: k['timestamp']) + inplane = [ x for x in inplanes_sorted if x['timestamp'] < first_timestamp[0]['timestamp'] ][-1] + else: + inplane = inplanes[0] + - # Find the correct inplane when one or more exist, making the assumption that - # the last inplane is the one we actually want to use. - inplanes_sorted = sorted(inplanes, key=lambda k: k['timestamp']) - inplane = inplanes_sorted[-1] + # Download the Inplane file nifti = [ x for x in inplane['files'] if x['type'] == 'nifti' ] if nifti and len(nifti) == 1: nifti = nifti[0] @@ -134,18 +144,22 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): 'nifti_out_name': nifti_rename, 'acquisition_id': inplane['_id'], 'label': inplane['label'], - 'timestamp': inplane['timestamp'].strftime('%Y-%m-%dT%H:%M:%S')} + 'timestamp': inplane['timestamp']} input_files.append(this_input) - print('Found associated inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) + print('Using inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) fw.download_file_from_acquisition(inplane['_id'], nifti['name'], os.path.join(data_dir, nifti_rename)) # TRACK INPUTS AND WRITE TO FILE + def sanitize_ts(x): + x['timestamp'] = x['timestamp'].strftime('%Y-%m-%dT%H:%M:%S') + return x + sanitized_inputs = [ sanitize_ts(x) for x in input_files ] inputs = { "session_id": parent_session_id, - "session_label":session['label'], + "session_label": session['label'], "num_runs": str(num_runs), - "files": input_files + "files": sanitized_inputs } # Write out the json file with input files @@ -191,8 +205,9 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): fw = flywheel.Flywheel(config['inputs']['api_key']['key']) # Copy FS license into place - license_file_path = config['inputs']['freesurfer_license']['location']['path'] - shutil.copyfile(license_file_path, '/opt/freesurfer/.license') + # license_file_path = config['inputs']['freesurfer_license']['location']['path'] + # TODO: + # shutil.copyfile(license_file_path, '/opt/freesurfer/.license') # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) @@ -204,8 +219,11 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): # RUN MATLAB CODE path_string = 'export FSHOME=/opt/freesurfer; export PATH=$PATH:/opt/freesurfer/bin; ' + matlab_binary = '' + matlab_library = '' run_command = '%s %s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file, args.output_dir) - os.system(run_command) + #TODO + # os.system(run_command) # COMPRESS OUTPUTS (preserve the config/json/log files at the top-level?) # The results come in a deeply nested directory tree, thus we want to compress the outputs so that they are easy to work withself. From 5849e72698a1f1b9a23643e698c7ddfb19cd9964 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 5 Nov 2018 11:36:45 -0800 Subject: [PATCH 10/31] update sytem calls --- functions/fLocAnalysis.m | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/functions/fLocAnalysis.m b/functions/fLocAnalysis.m index c57d6909..45cdefea 100644 --- a/functions/fLocAnalysis.m +++ b/functions/fLocAnalysis.m @@ -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); From 54c13df58993f453869f582f66570670602bdfbd Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 5 Nov 2018 11:37:33 -0800 Subject: [PATCH 11/31] BF for parfile reading + remove dependence on jsonio + remove QA opt. --- functions/fLocAnalysisParams.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/fLocAnalysisParams.m b/functions/fLocAnalysisParams.m index 4c088dfc..e098dd4a 100644 --- a/functions/fLocAnalysisParams.m +++ b/functions/fLocAnalysisParams.m @@ -75,7 +75,7 @@ % get the durations of TR and events nii = niftiRead(niipaths{1}); TR = nii.pixdim(4); -pid = fopen(parfiles{1}); +pid = fopen(parpaths{1}); ln1 = fgetl(pid); ln1(ln1 == sprintf('\t')) = ''; ln2 = fgetl(pid); ln2(ln2 == sprintf('\t')) = ''; prts1 = deblank(strsplit(ln1, ' ')); prts2 = deblank(strsplit(ln2, ' ')); From 77bcacd2eb4a0f7c51f152dd47d829047d2c5f73 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 5 Nov 2018 11:43:58 -0800 Subject: [PATCH 12/31] Remove dependence on jsonio + remove QA opt. --- functions/fLocGearRun.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index 0a682ae8..ebfd0e17 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -1,4 +1,4 @@ -function err = fLocGearRun(session, config, out_dir) +function err = fLocGearRun(session, config_file, out_dir) % Generate data structures of vistasoft parameters for preprocessing and % analyzing fLoc data with a GLM using a Flywheel gear. % @@ -16,7 +16,7 @@ %% Parse config file for params. % Read the json file -config = jsonread(config); +config = jsondecode(fileread(config_file)); % Set the params clip = config.config.clip; @@ -26,7 +26,7 @@ [~, 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); if err == 0 zip(fullfile(out_dir, 'fLoc_output.zip'), session); From faaf2ba07ddd0508c7228f6bf5b86c21e913319c Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Wed, 7 Nov 2018 16:02:01 -0800 Subject: [PATCH 13/31] Start with matlab v93 mcr + include multi-stage build for fs + install flywheel sdk --- Dockerfile | 19 ++++++++++--------- functions/fLocGearRun.m | 25 +++++++++++++++++++++---- gear/manifest.json | 6 +++--- gear/run.py | 27 ++++++++++++--------------- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index 41d14247..e3429364 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Create Docker container that can run fLoc analysis. -# Start with the Matlab r2017a runtime container -FROM flywheel/matlab-mcr:v92.1 +# Start with the Matlab r2017b runtime container +FROM flywheel/matlab-mcr:v93 MAINTAINER Michael Perry @@ -34,22 +34,23 @@ RUN apt-get update && apt-get -y install \ perl-modules # 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 +COPY --from=scitran/freesurfer-recon-all:0.1.4 /opt/freesurfer /opt/freesurfer -# The brainstem and hippocampal subfield modules in FreeSurfer 6.0 require the Matlab R2012 runtime -RUN apt-get install -y libxt-dev libxmu-dev -ENV FREESURFER_HOME /opt/freesurfer -RUN wget -N -qO- "http://surfer.nmr.mgh.harvard.edu/fswiki/MatlabRuntime?action=AttachFile&do=get&target=runtime2012bLinux.tar.gz" | tar -xz -C $FREESURFER_HOME && chown -R root:root /opt/freesurfer/MCRv80 +RUN apt-get update && \ + apt-get install -y python-pip && \ + pip install flywheel-sdk # ADD the Matlab Stand-Alone (MSA) into the container. -# COPY gear/bin/gear_floc /usr/local/bin/floc +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 ${FLYWHEEL}/run +COPY gear/run.py ${FLYWHEEL}/run RUN chmod +x ${FLYWHEEL}/run COPY gear/manifest.json ${FLYWHEEL}/manifest.json diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index ebfd0e17..897db1d0 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -1,10 +1,12 @@ -function err = fLocGearRun(session, config_file, out_dir) +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) @@ -13,7 +15,13 @@ % % AS 8/2018 -%% Parse config file for params. +%% 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)); @@ -29,8 +37,17 @@ err = fLocAnalysis(session, init_params, glm_params, clip); if err == 0 - zip(fullfile(out_dir, 'fLoc_output.zip'), session); - delete(session) + [~, session_label] = fileparts(session); + jpgs = mrvFindFile('*.jpg', session); + for ii=1:numel(jpgs) + copyfile(jpgs{ii}, out_dir) + end + logs = mrvFindFile('*log*', session); + for ii=1:numel(logs) + copyfile(logs{ii}, out_dir) + end + zip(fullfile(out_dir, ['fLoc_output-', session_label, '.zip']), session); + rmdir(session, 's') clear global diff --git a/gear/manifest.json b/gear/manifest.json index 70b8ab5d..e241f64c 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -11,13 +11,13 @@ "flywheel": { "suite": "VPNL" }, - "version": "1.0.0_rc1", + "version": "0.1.0", "custom": { - "docker-image": "vpnl/floc:1.0.0_rc1" + "docker-image": "vpnl/floc:0.1.0" }, "inputs": { "api_key": { - "description": "User's api key", + "description": "Calling user's api key.", "base": "api_key" }, "freesurfer_license": { diff --git a/gear/run.py b/gear/run.py index 835bde2f..0e1e004f 100755 --- a/gear/run.py +++ b/gear/run.py @@ -5,7 +5,6 @@ import time import shutil import flywheel -import numpy as np from datetime import datetime from pprint import pprint as pp @@ -22,7 +21,7 @@ def parse_config(config_json_file): with open(config_json_file, 'r') as jsonfile: config = json.load(jsonfile) - return config['config'] + return config def get_timestamp_delta(timestamp, reference, absolute_value=False): """ @@ -179,6 +178,7 @@ def sanitize_ts(x): import urllib3 import shutil import argparse + import subprocess urllib3.disable_warnings() os.environ['FLYWHEEL_SDK_SKIP_VERSION_CHECK'] = '1' @@ -205,9 +205,8 @@ def sanitize_ts(x): fw = flywheel.Flywheel(config['inputs']['api_key']['key']) # Copy FS license into place - # license_file_path = config['inputs']['freesurfer_license']['location']['path'] - # TODO: - # shutil.copyfile(license_file_path, '/opt/freesurfer/.license') + license_file_path = config['inputs']['freesurfer_license']['location']['path'] + shutil.copyfile(license_file_path, '/opt/freesurfer/.license') # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) @@ -218,17 +217,15 @@ def sanitize_ts(x): os.sys.exit(1) # RUN MATLAB CODE - path_string = 'export FSHOME=/opt/freesurfer; export PATH=$PATH:/opt/freesurfer/bin; ' - matlab_binary = '' - matlab_library = '' - run_command = '%s %s %s %s %s %s' % (path_string, matlab_binary, matlab_library, data_dir, args.config_file, args.output_dir) - #TODO - # os.system(run_command) + fs_home_dir = '/opt/freesurfer' + matlab_binary = '/usr/local/bin/run_fLocGearRun.sh' + matlab_library = '/opt/mcr/v93' - # COMPRESS OUTPUTS (preserve the config/json/log files at the top-level?) - # The results come in a deeply nested directory tree, thus we want to compress the outputs so that they are easy to work withself. - - # Preserve the directory tree in json format for easy navigation outside of the platform. - Package list for the zip file. + run_command = [matlab_binary, matlab_library, data_dir, args.config_file, args.output_dir, fs_home_dir] + status = subprocess.check_call(run_command) + # Clean up fLoc dir + shutil.rmtree(os.path.join(out_dir, 'fLoc')) # EXIT + os.sys.exit(status) From 4313f086fdc3bc20c5ecba89e3e4251381d26ff3 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Thu, 8 Nov 2018 15:47:54 -0800 Subject: [PATCH 14/31] matlab script to build the fLoc binary --- gear/bin/fLocGearRun_Build.m | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 gear/bin/fLocGearRun_Build.m diff --git a/gear/bin/fLocGearRun_Build.m b/gear/bin/fLocGearRun_Build.m new file mode 100755 index 00000000..d6a51fd9 --- /dev/null +++ b/gear/bin/fLocGearRun_Build.m @@ -0,0 +1,34 @@ +% 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 (strfind(version, '9.3.0') ~= 1) || (strfind(computer, 'GLNXA64') ~= 1) + error('You must compile this function using R2017b (9.3.0.713579) 64-bit (glnxa64). You are using %s, %s', version, computer); +end + +% Download the source code +disp('Cloning source code...'); +system('git clone -b gear https://github.com/vpnl/fLoc'); +system('git clone https://github.com/vistalab/vistasoft'); + +% Set paths +disp('Adding paths to build scope...'); +restoredefaultpath; +addpath(genpath(fullfile(pwd, 'fLoc'))); +rmpath(genpath(fullfile(pwd, 'fLoc', '.git'))); +addpath(genpath(fullfile(pwd, 'vistasoft'))); +rmpath(genpath(fullfile(pwd, 'vistasoft', '.git'))); + +% Compile +disp('Running compile code...'); +mcc -m fLoc/functions/fLocGearRun.m + +% Clean up +disp('Cleaning up...') +rmdir(fullfile(pwd, 'fLoc'), 's') +rmdir(fullfile(pwd, 'vistasoft'), 's') + +disp('Done!'); +exit From 544214092409e0090761735205079e708b6264b1 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 09:51:12 -0800 Subject: [PATCH 15/31] improve output file generation and naming --- functions/fLocGearRun.m | 12 ++++++++---- gear/run.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index 897db1d0..c36a1c87 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -36,17 +36,21 @@ %% Run fLoc Analysis err = fLocAnalysis(session, init_params, glm_params, clip); +%% Move select files to output and zip outputs if err == 0 - [~, session_label] = fileparts(session); + [base_path, session_label] = fileparts(session); + [~, subject_code] = fileparts(base_path) jpgs = mrvFindFile('*.jpg', session); for ii=1:numel(jpgs) - copyfile(jpgs{ii}, out_dir) + [~, f, e] = fileparts(jpgs{ii}) + copyfile(jpgs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e])); end logs = mrvFindFile('*log*', session); for ii=1:numel(logs) - copyfile(logs{ii}, out_dir) + [~, f, e] = fileparts(logs{ii}) + copyfile(logs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e])) end - zip(fullfile(out_dir, ['fLoc_output-', session_label, '.zip']), session); + zip(fullfile(out_dir, [subject_code, '_', session_label, '-fLoc.zip']), session); rmdir(session, 's') clear global diff --git a/gear/run.py b/gear/run.py index 0e1e004f..cabb9244 100755 --- a/gear/run.py +++ b/gear/run.py @@ -61,9 +61,11 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): # Get the acquisitions for the session session = fw.get_session(parent_session_id) + subject_code = session.subject.code + session_label = session.label session_acquisitions = fw.get_session_acquisitions(parent_session_id) - data_dir = os.path.join(out_dir, 'fLoc', session['label']) + data_dir = os.path.join(out_dir, subject_code, session_label) print('Data dir set to: %s' % (data_dir)) if not os.path.isdir(data_dir): os.makedirs(data_dir) @@ -156,13 +158,14 @@ def sanitize_ts(x): return x sanitized_inputs = [ sanitize_ts(x) for x in input_files ] inputs = { "session_id": parent_session_id, - "session_label": session['label'], + "subject_code": subject_code, + "session_label": session_label, "num_runs": str(num_runs), "files": sanitized_inputs } # Write out the json file with input files - json_file = os.path.join(out_dir, 'input_data.json') + json_file = os.path.join(out_dir, subject_code + '_' + session_label + '-input_data.json') with open(json_file, 'w') as jf: json.dump(inputs, jf) @@ -210,9 +213,9 @@ def sanitize_ts(x): # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) - input_files, data_dir = fLoc_data(config, args.output_dir) + inputs, data_dir = fLoc_data(config, args.output_dir) - if not input_files: + if not inputs: print('Errors finding input files...') os.sys.exit(1) @@ -225,7 +228,7 @@ def sanitize_ts(x): status = subprocess.check_call(run_command) # Clean up fLoc dir - shutil.rmtree(os.path.join(out_dir, 'fLoc')) + shutil.rmtree(os.path.join(out_dir, inputs['subject_code'])) # EXIT os.sys.exit(status) From 205479f7c825c865aff28df14d5184615aeec569 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 09:59:32 -0800 Subject: [PATCH 16/31] make freesurfer license a configuration param. --- Dockerfile | 1 + gear/manifest.json | 8 ++++---- gear/run.py | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index e3429364..5c6216ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,7 @@ RUN apt-get update && apt-get -y install \ # Download Freesurfer v6.0.0 from MGH and untar to /opt COPY --from=scitran/freesurfer-recon-all:0.1.4 /opt/freesurfer /opt/freesurfer +RUN touch /opt/freesurfer/.license && chmod 777 /opt/freesurfer/.license RUN apt-get update && \ apt-get install -y python-pip && \ diff --git a/gear/manifest.json b/gear/manifest.json index e241f64c..5a24ea1c 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -19,16 +19,16 @@ "api_key": { "description": "Calling user's api key.", "base": "api_key" - }, - "freesurfer_license": { - "description": "FreeSurfer license file, provided during registration with FreeSurfer. This file will by copied to the $FSHOME directory and used during execution of the Gear.", - "base": "file" } }, "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" } } } diff --git a/gear/run.py b/gear/run.py index cabb9244..6b7b1a17 100755 --- a/gear/run.py +++ b/gear/run.py @@ -208,8 +208,9 @@ def sanitize_ts(x): fw = flywheel.Flywheel(config['inputs']['api_key']['key']) # Copy FS license into place - license_file_path = config['inputs']['freesurfer_license']['location']['path'] - shutil.copyfile(license_file_path, '/opt/freesurfer/.license') + license_file_path = '/opt/freesurfer/.license' + with open(license_file_path, 'w') as lf: + lf.write(' '.join(config['config']['freesurfer_license'].split()).replace(" ", "\\n")) # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) From 69fa8daa0a1f49e4dd506d5e56e3b45d205d956a Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 13:22:00 -0800 Subject: [PATCH 17/31] fix license file writing + supress matlab commands --- functions/fLocGearRun.m | 13 +++++++------ gear/run.py | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index c36a1c87..4a0c8653 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -39,19 +39,20 @@ %% Move select files to output and zip outputs if err == 0 [base_path, session_label] = fileparts(session); - [~, subject_code] = fileparts(base_path) + [~, subject_code] = fileparts(base_path); jpgs = mrvFindFile('*.jpg', session); for ii=1:numel(jpgs) - [~, f, e] = fileparts(jpgs{ii}) + [~, f, e] = fileparts(jpgs{ii}); copyfile(jpgs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e])); 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, e] = fileparts(logs{ii}); + copyfile(logs{ii}, fullfile(out_dir, [subject_code, '_', session_label, '-', f, e])); end - zip(fullfile(out_dir, [subject_code, '_', session_label, '-fLoc.zip']), session); - rmdir(session, 's') + fprintf('Zipping outputs [%s]...\n', mrvDirup(session)); + zip(fullfile(out_dir, [subject_code, '_', session_label, '-fLoc.zip']), mrvDirup(session)); + rmdir(session, 's'); clear global diff --git a/gear/run.py b/gear/run.py index 6b7b1a17..1b2f44e6 100755 --- a/gear/run.py +++ b/gear/run.py @@ -210,7 +210,7 @@ def sanitize_ts(x): # Copy FS license into place license_file_path = '/opt/freesurfer/.license' with open(license_file_path, 'w') as lf: - lf.write(' '.join(config['config']['freesurfer_license'].split()).replace(" ", "\\n")) + lf.write(' '.join(config['config']['freesurfer_license'].split()).replace(" ", "\n")) # Download fLOC data print('Gathering fLOC Data in %s...' % (args.output_dir)) @@ -229,7 +229,9 @@ def sanitize_ts(x): status = subprocess.check_call(run_command) # Clean up fLoc dir - shutil.rmtree(os.path.join(out_dir, inputs['subject_code'])) + shutil.rmtree(os.path.join(args.output_dir, inputs['subject_code'])) # EXIT + if status == 0: + print('Success!') os.sys.exit(status) From 6c148300c349a8587432c6b61660a001e866313f Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 15:16:55 -0800 Subject: [PATCH 18/31] Improve build script --- gear/bin/fLocGearRun_Build.m | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gear/bin/fLocGearRun_Build.m b/gear/bin/fLocGearRun_Build.m index d6a51fd9..a144bde5 100755 --- a/gear/bin/fLocGearRun_Build.m +++ b/gear/bin/fLocGearRun_Build.m @@ -8,27 +8,30 @@ 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 -b gear https://github.com/vpnl/fLoc'); system('git clone https://github.com/vistalab/vistasoft'); % Set paths disp('Adding paths to build scope...'); restoredefaultpath; -addpath(genpath(fullfile(pwd, 'fLoc'))); -rmpath(genpath(fullfile(pwd, 'fLoc', '.git'))); addpath(genpath(fullfile(pwd, 'vistasoft'))); -rmpath(genpath(fullfile(pwd, 'vistasoft', '.git'))); +addpath(genpath('../../fLoc')) % Compile disp('Running compile code...'); -mcc -m fLoc/functions/fLocGearRun.m +mcc -v -R -nodisplay -m ../../functions/fLocGearRun.m % Clean up disp('Cleaning up...') -rmdir(fullfile(pwd, 'fLoc'), 's') -rmdir(fullfile(pwd, 'vistasoft'), 's') +rmdir(fullfile(pwd, 'vistasoft'), 's'); disp('Done!'); exit From 4031457672565d9388ab68afb5ec9ef9b78da1f5 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 15:39:59 -0800 Subject: [PATCH 19/31] update custom key --- gear/manifest.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index 5a24ea1c..f265b789 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -8,12 +8,13 @@ "url": "https://github.com/VPNL/fLoc", "source": "https://github.com/VPNL/fLoc", "license": "Other", - "flywheel": { - "suite": "VPNL" - }, - "version": "0.1.0", + "flywheel": "0", + "version": "0.0.1", "custom": { - "docker-image": "vpnl/floc:0.1.0" + "docker-image": "vpnl/floc:0.0.1", + "flywheel": { + "suite": "VPNL" + } }, "inputs": { "api_key": { From e3ae629734a44f217303039d804538236d7d8c3d Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 9 Nov 2018 15:48:17 -0800 Subject: [PATCH 20/31] fix api_key base type --- gear/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gear/manifest.json b/gear/manifest.json index f265b789..ab06a603 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -19,7 +19,8 @@ "inputs": { "api_key": { "description": "Calling user's api key.", - "base": "api_key" + "base": "api-key", + "read-only": true } }, "config": { From 3b1598feb369cb3ed23473003528db4a46b0f735 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Sun, 11 Nov 2018 15:41:17 -0800 Subject: [PATCH 21/31] Update custom key and description. --- gear/manifest.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index ab06a603..10656b68 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -1,17 +1,20 @@ { "name": "floc", "label": "VPNL: fLoc - Face Localizer Analysis Pipeline", - "description": "Functional localizer experiment used to define category-selective cortical regions (published in Stigliani et al., 2015).", + "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", - "maintainer": "Michael Perry ", "url": "https://github.com/VPNL/fLoc", "source": "https://github.com/VPNL/fLoc", "license": "Other", "flywheel": "0", - "version": "0.0.1", + "version": "0.0.2", "custom": { - "docker-image": "vpnl/floc:0.0.1", + "docker-image": "vpnl/floc:0.0.2", + "gear-builder": { + "image": "vpnl/floc:0.0.2" + }, "flywheel": { "suite": "VPNL" } From 56a4973e2911221114669934752de8c2551df90b Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Sun, 11 Nov 2018 15:45:41 -0800 Subject: [PATCH 22/31] use 'f' option to override the read-only status of the destination folder --- functions/fLocGearRun.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/fLocGearRun.m b/functions/fLocGearRun.m index 4a0c8653..afd7a12c 100755 --- a/functions/fLocGearRun.m +++ b/functions/fLocGearRun.m @@ -43,12 +43,12 @@ 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])); + 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])); + 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)); From ce1db5ace9bb4c0296a75215b4ac90a74153271a Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 16 Nov 2018 16:20:32 -0800 Subject: [PATCH 23/31] Streamline Dockerfile to reduce image size + bump version --- Dockerfile | 49 ++++++++++++++++++++++++++-------------------- gear/manifest.json | 7 ++----- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c6216ef..a50a3039 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,10 @@ FROM flywheel/matlab-mcr:v93 MAINTAINER Michael Perry + ############################ # Install dependencies -ARG DEBIAN_FRONTEND=noninteractive + RUN apt-get update && apt-get install -y --force-yes \ xvfb \ xfonts-100dpi \ @@ -16,32 +17,38 @@ RUN apt-get update && apt-get install -y --force-yes \ zip \ unzip \ python \ - jq - -# Set the diplay env variable for xvfb -ENV DISPLAY :1.0 + jq \ + bc \ + tar \ + zip \ + wget \ + gawk \ + tcsh \ + python \ + libgomp1 \ + python2.7 \ + perl-modules \ + python-pip -RUN apt-get update && apt-get -y install \ - bc \ - tar \ - zip \ - wget \ - gawk \ - tcsh \ - python \ - libgomp1 \ - python2.7 \ - perl-modules +############################ # Download Freesurfer v6.0.0 from MGH and untar to /opt -COPY --from=scitran/freesurfer-recon-all:0.1.4 /opt/freesurfer /opt/freesurfer -RUN touch /opt/freesurfer/.license && chmod 777 /opt/freesurfer/.license -RUN apt-get update && \ - apt-get install -y python-pip && \ - pip install flywheel-sdk +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 # 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/ diff --git a/gear/manifest.json b/gear/manifest.json index 10656b68..b37ef598 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -9,12 +9,9 @@ "source": "https://github.com/VPNL/fLoc", "license": "Other", "flywheel": "0", - "version": "0.0.2", + "version": "0.1.0", "custom": { - "docker-image": "vpnl/floc:0.0.2", - "gear-builder": { - "image": "vpnl/floc:0.0.2" - }, + "docker-image": "vpnl/floc:0.1.0", "flywheel": { "suite": "VPNL" } From 6302ae0ec06c689b83b1df614c751619bbde65ed Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Fri, 16 Nov 2018 16:56:25 -0800 Subject: [PATCH 24/31] WIP --- gear/README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 gear/README.md diff --git a/gear/README.md b/gear/README.md new file mode 100644 index 00000000..26a56140 --- /dev/null +++ b/gear/README.md @@ -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: +``` +/-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 + +``` From d5f964a1a5aa39537d991d9a68d61922cb05a41f Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 7 Jan 2019 11:46:55 -0800 Subject: [PATCH 25/31] Fix broken paths when loading files. --- functions/fLocAnalysis.m | 5 +---- functions/fLocAnalysisParams.m | 11 ++--------- functions/fLocAnalysisWrapper.m | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/functions/fLocAnalysis.m b/functions/fLocAnalysis.m index 982bae0e..45cdefea 100644 --- a/functions/fLocAnalysis.m +++ b/functions/fLocAnalysis.m @@ -177,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; diff --git a/functions/fLocAnalysisParams.m b/functions/fLocAnalysisParams.m index 3761b037..aeece5b0 100644 --- a/functions/fLocAnalysisParams.m +++ b/functions/fLocAnalysisParams.m @@ -47,16 +47,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 @@ -74,16 +69,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(parpaths{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, ' ')); diff --git a/functions/fLocAnalysisWrapper.m b/functions/fLocAnalysisWrapper.m index 7fe52ba3..0db6bc72 100644 --- a/functions/fLocAnalysisWrapper.m +++ b/functions/fLocAnalysisWrapper.m @@ -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 From f8749fa09470cd762e28cc91a95a9f51de4379ff Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 7 Jan 2019 11:47:31 -0800 Subject: [PATCH 26/31] use fork for local path code --- gear/bin/fLocGearRun_Build.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gear/bin/fLocGearRun_Build.m b/gear/bin/fLocGearRun_Build.m index a144bde5..377f34f2 100755 --- a/gear/bin/fLocGearRun_Build.m +++ b/gear/bin/fLocGearRun_Build.m @@ -4,7 +4,7 @@ % /software/matlab/r2017b/bin/matlab -nodesktop -r fLocGearRun_Build % Check that we are running a compatible version -if (strfind(version, '9.3.0') ~= 1) || (strfind(computer, 'GLNXA64') ~= 1) +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 @@ -17,7 +17,8 @@ % Download the source code disp('Cloning source code...'); -system('git clone https://github.com/vistalab/vistasoft'); +%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...'); From 44d7e4a2d1d9a61ec2c68865f177a0bc99f89fae Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Mon, 7 Jan 2019 11:47:49 -0800 Subject: [PATCH 27/31] bump version --- gear/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index b37ef598..462220bf 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -9,9 +9,9 @@ "source": "https://github.com/VPNL/fLoc", "license": "Other", "flywheel": "0", - "version": "0.1.0", + "version": "0.2.0", "custom": { - "docker-image": "vpnl/floc:0.1.0", + "docker-image": "vpnl/floc:0.2.0", "flywheel": { "suite": "VPNL" } From 678bd1db5a5c1ecc7e78397376cc1b49f3fb994e Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Tue, 8 Jan 2019 14:13:42 -0800 Subject: [PATCH 28/31] parse subject code from session parent directory --- functions/fLocAnalysisParams.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/functions/fLocAnalysisParams.m b/functions/fLocAnalysisParams.m index aeece5b0..951dd742 100644 --- a/functions/fLocAnalysisParams.m +++ b/functions/fLocAnalysisParams.m @@ -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}; @@ -104,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 @@ -119,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 From 93044c12fb7f646c265bce15097aef59d8da2968 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Tue, 8 Jan 2019 14:23:42 -0800 Subject: [PATCH 29/31] add proper logging --- gear/run.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/gear/run.py b/gear/run.py index 1b2f44e6..3ba01779 100755 --- a/gear/run.py +++ b/gear/run.py @@ -4,10 +4,14 @@ import json import time import shutil +import logging import flywheel from datetime import datetime from pprint import pprint as pp +logging.basicConfig() +log = logging.getLogger('fLOC') + # Parse a config file def parse_config(config_json_file): """ @@ -66,7 +70,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): session_acquisitions = fw.get_session_acquisitions(parent_session_id) data_dir = os.path.join(out_dir, subject_code, session_label) - print('Data dir set to: %s' % (data_dir)) + log.info('Data dir set to: %s' % (data_dir)) if not os.path.isdir(data_dir): os.makedirs(data_dir) @@ -83,7 +87,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): for f in a['files']: if f['name'].endswith('.par'): - print('Found parfile = %s ' % f['name']) + log.info('Found parfile = %s ' % f['name']) found_par_files = True # Find the nifti file in this acquisition and download @@ -98,7 +102,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): 'label': a['label'], 'timestamp': a['timestamp']} input_files.append(this_input) - print(' Found associated nifti file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) + log.info(' Found associated nifti file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) fw.download_file_from_acquisition(a['_id'], f['name'], os.path.join(data_dir, f['name'])) @@ -106,7 +110,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): nifti['name'], os.path.join(data_dir, nifti_rename)) else: - print('No nifti file found to associate with the parfile!!!') + log.info('No nifti file found to associate with the parfile!!!') if found_par_files == False: return None @@ -121,7 +125,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): if f['classification'] and f['classification'].has_key('Features') and 'In-Plane' in f['classification']['Features']: inplanes.append(a) - print('%d Inplane acquisitions were found!' % (len(inplanes))) + log.info('%d Inplane acquisitions were found!' % (len(inplanes))) if inplanes and len(inplanes) >1: # Find the correct inplane when one or more exist, making the assumption that @@ -147,7 +151,7 @@ def fLoc_data(config=None, out_dir='/flywheel/v0/output/'): 'label': inplane['label'], 'timestamp': inplane['timestamp']} input_files.append(this_input) - print('Using inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) + log.info('Using inplane file = %s \n\tDownloading nifti as %s...' % (nifti['name'], nifti_rename)) fw.download_file_from_acquisition(inplane['_id'], nifti['name'], os.path.join(data_dir, nifti_rename)) @@ -182,6 +186,11 @@ def sanitize_ts(x): import shutil import argparse import subprocess + import datetime + + log.setLevel(getattr(logging, 'DEBUG')) + logging.getLogger('fLOC').setLevel(logging.INFO) + log.info(' start %s' % datetime.datetime.utcnow()) urllib3.disable_warnings() os.environ['FLYWHEEL_SDK_SKIP_VERSION_CHECK'] = '1' @@ -204,7 +213,7 @@ def sanitize_ts(x): config = parse_config(args.config_file) # Create SDK client - print(' Creating SDK client...') + log.info(' Creating SDK client...') fw = flywheel.Flywheel(config['inputs']['api_key']['key']) # Copy FS license into place @@ -213,11 +222,11 @@ def sanitize_ts(x): lf.write(' '.join(config['config']['freesurfer_license'].split()).replace(" ", "\n")) # Download fLOC data - print('Gathering fLOC Data in %s...' % (args.output_dir)) + log.info('Gathering fLOC Data in %s...' % (args.output_dir)) inputs, data_dir = fLoc_data(config, args.output_dir) if not inputs: - print('Errors finding input files...') + log.warning('Errors finding input files...') os.sys.exit(1) # RUN MATLAB CODE @@ -233,5 +242,5 @@ def sanitize_ts(x): # EXIT if status == 0: - print('Success!') + log.info('Success!') os.sys.exit(status) From dcf7057aca4e9d41afa0b1d29363d52333c9fef4 Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Tue, 8 Jan 2019 15:15:07 -0800 Subject: [PATCH 30/31] bump version --- gear/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gear/manifest.json b/gear/manifest.json index 462220bf..915b7f42 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -9,9 +9,9 @@ "source": "https://github.com/VPNL/fLoc", "license": "Other", "flywheel": "0", - "version": "0.2.0", + "version": "0.2.1", "custom": { - "docker-image": "vpnl/floc:0.2.0", + "docker-image": "vpnl/floc:0.2.1", "flywheel": { "suite": "VPNL" } From 1c1891ab773a1431d072e631964f708732952cac Mon Sep 17 00:00:00 2001 From: Michael Perry Date: Sun, 20 Sep 2020 15:31:39 -0700 Subject: [PATCH 31/31] ENH: move to python3 for fw v3, update client + bump version --- Dockerfile | 5 +---- gear/manifest.json | 4 ++-- gear/run.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a50a3039..24c71cbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ RUN apt-get update && apt-get install -y --force-yes \ xfonts-cyrillic \ zip \ unzip \ - python \ jq \ bc \ tar \ @@ -24,9 +23,7 @@ RUN apt-get update && apt-get install -y --force-yes \ wget \ gawk \ tcsh \ - python \ libgomp1 \ - python2.7 \ perl-modules \ python-pip @@ -45,7 +42,7 @@ RUN wget -N -qO- ftp://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.0/free ENV DISPLAY :1.0 # Install Flywheel-SDK -RUN pip 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 diff --git a/gear/manifest.json b/gear/manifest.json index 915b7f42..190ec6a0 100644 --- a/gear/manifest.json +++ b/gear/manifest.json @@ -9,9 +9,9 @@ "source": "https://github.com/VPNL/fLoc", "license": "Other", "flywheel": "0", - "version": "0.2.1", + "version": "0.3.0", "custom": { - "docker-image": "vpnl/floc:0.2.1", + "docker-image": "vpnl/floc:0.3.0", "flywheel": { "suite": "VPNL" } diff --git a/gear/run.py b/gear/run.py index 3ba01779..fe118b86 100755 --- a/gear/run.py +++ b/gear/run.py @@ -214,7 +214,7 @@ def sanitize_ts(x): # Create SDK client log.info(' Creating SDK client...') - fw = flywheel.Flywheel(config['inputs']['api_key']['key']) + fw = flywheel.Client(config['inputs']['api_key']['key']) # Copy FS license into place license_file_path = '/opt/freesurfer/.license'