boldqc-bu 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boldqc_bu-0.0.1/LICENSE +26 -0
- boldqc_bu-0.0.1/PKG-INFO +20 -0
- boldqc_bu-0.0.1/README.md +11 -0
- boldqc_bu-0.0.1/boldqc/__init__.py +35 -0
- boldqc_bu-0.0.1/boldqc/__version__.py +6 -0
- boldqc_bu-0.0.1/boldqc/bids/__init__.py +153 -0
- boldqc_bu-0.0.1/boldqc/cli/__init__.py +3 -0
- boldqc_bu-0.0.1/boldqc/cli/get.py +83 -0
- boldqc_bu-0.0.1/boldqc/cli/process.py +110 -0
- boldqc_bu-0.0.1/boldqc/cli/tandem.py +66 -0
- boldqc_bu-0.0.1/boldqc/libexec/helpers.sh +97 -0
- boldqc_bu-0.0.1/boldqc/libexec/niftiqa.py +2432 -0
- boldqc_bu-0.0.1/boldqc/libexec/niftiqa_wrapper.py +171 -0
- boldqc_bu-0.0.1/boldqc/libexec/stackcheck_ext.sh +596 -0
- boldqc_bu-0.0.1/boldqc/parsers/__init__.py +36 -0
- boldqc_bu-0.0.1/boldqc/state/__init__.py +6 -0
- boldqc_bu-0.0.1/boldqc/tasks/__init__.py +63 -0
- boldqc_bu-0.0.1/boldqc/tasks/niftiqa_wrapper.py +50 -0
- boldqc_bu-0.0.1/boldqc/tasks/stackcheck_ext.py +55 -0
- boldqc_bu-0.0.1/boldqc/xnat/__init__.py +279 -0
- boldqc_bu-0.0.1/boldqc_bu.egg-info/PKG-INFO +20 -0
- boldqc_bu-0.0.1/boldqc_bu.egg-info/SOURCES.txt +26 -0
- boldqc_bu-0.0.1/boldqc_bu.egg-info/dependency_links.txt +1 -0
- boldqc_bu-0.0.1/boldqc_bu.egg-info/requires.txt +6 -0
- boldqc_bu-0.0.1/boldqc_bu.egg-info/top_level.txt +1 -0
- boldqc_bu-0.0.1/scripts/boldQC.py +122 -0
- boldqc_bu-0.0.1/setup.cfg +4 -0
- boldqc_bu-0.0.1/setup.py +39 -0
boldqc_bu-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright 2021 The President and Fellows of Harvard College. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
4
|
+
are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
7
|
+
list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
14
|
+
may be used to endorse or promote products derived from this software without
|
|
15
|
+
specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
18
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
19
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
21
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
22
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
23
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
24
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
25
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
boldqc_bu-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boldqc-bu
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: BOLD QC
|
|
5
|
+
Home-page: https://github.com/kakurk/boldqc
|
|
6
|
+
Author: Kyle Kurkela
|
|
7
|
+
Author-email: kkurkela@bu.edu
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: selfie
|
|
10
|
+
Requires-Dist: executors
|
|
11
|
+
Requires-Dist: yaxil
|
|
12
|
+
Requires-Dist: matplotlib
|
|
13
|
+
Requires-Dist: nibabel
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
Dynamic: requires-dist
|
|
20
|
+
Dynamic: summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# BOLD Quality Control (beta)
|
|
2
|
+
BOLDQC is an automated quality control pipeline for functional MRI scans. BOLDQC
|
|
3
|
+
is built on top of the excellent
|
|
4
|
+
[`dcm2niix`](https://github.com/rordenlab/dcm2niix),
|
|
5
|
+
[`FreeSurfer`](https://surfer.nmr.mgh.harvard.edu/),
|
|
6
|
+
and
|
|
7
|
+
[`FSL`](https://fsl.fmrib.ox.ac.uk/fsl)
|
|
8
|
+
software packages.
|
|
9
|
+
|
|
10
|
+
For the latest documentation please head over to [boldqc.readthedocs.io](https://boldqc.readthedocs.io).
|
|
11
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import tarfile
|
|
4
|
+
import logging
|
|
5
|
+
from . import __version__
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger()
|
|
8
|
+
|
|
9
|
+
def version():
|
|
10
|
+
return __version__.__version__
|
|
11
|
+
|
|
12
|
+
def archive(indir, output):
|
|
13
|
+
with tarfile.open(output, 'w:gz') as tar:
|
|
14
|
+
tar.add(indir, os.path.basename(indir))
|
|
15
|
+
|
|
16
|
+
def get_mask_threshold(sidecar):
|
|
17
|
+
with open(sidecar, 'r') as fo:
|
|
18
|
+
js = json.load(fo)
|
|
19
|
+
bits_stored = js.get('BitsStored', None)
|
|
20
|
+
receive_coil = js.get('ReceiveCoilName', None)
|
|
21
|
+
if bits_stored == 12:
|
|
22
|
+
logger.info(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 150.0')
|
|
23
|
+
return 150.0
|
|
24
|
+
if bits_stored == 16:
|
|
25
|
+
if receive_coil in ['Head_32']:
|
|
26
|
+
logger.info(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 1500.0')
|
|
27
|
+
return 1500.0
|
|
28
|
+
if receive_coil in ['Head_64', 'HeadNeck_64']:
|
|
29
|
+
logger.info(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 3000.0')
|
|
30
|
+
return 3000.0
|
|
31
|
+
raise MaskThresholdError(f'unexpected bits stored "{bits_stored}" + receive coil "{receive_coil}"')
|
|
32
|
+
|
|
33
|
+
class MaskThresholdError(Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class BIDS(object):
|
|
10
|
+
def __init__(self, base, sub, ses=None):
|
|
11
|
+
self._base = base
|
|
12
|
+
self._sub = sub
|
|
13
|
+
self._ses = ses
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def basename(sub, mod, ses=None, acq=None, ce=None, rec=None, run=None, defacemask=False):
|
|
17
|
+
# add sub (required)
|
|
18
|
+
b = 'sub-' + sub.replace('sub-', '')
|
|
19
|
+
if ses:
|
|
20
|
+
b += '_ses-' + ses.replace('ses-', '')
|
|
21
|
+
if acq:
|
|
22
|
+
b += '_acq-' + acq.replace('acq-', '')
|
|
23
|
+
if ce:
|
|
24
|
+
b += '_ce-' + ce.replace('ce-', '')
|
|
25
|
+
if rec:
|
|
26
|
+
b += '_rec-' + rec.replace('rec-', '')
|
|
27
|
+
if run:
|
|
28
|
+
b += '_run-' + str(run).replace('run-', '')
|
|
29
|
+
if defacemask:
|
|
30
|
+
b += '_mod-' + mod.replace('mod-', '')
|
|
31
|
+
b += '_defacemask'
|
|
32
|
+
else:
|
|
33
|
+
b += '_' + mod.replace('mod-', '')
|
|
34
|
+
return b
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def sidecar_for_image(image):
|
|
38
|
+
logger.info(image)
|
|
39
|
+
base = re.sub('\.nii.*$', '', image)
|
|
40
|
+
logger.info(base)
|
|
41
|
+
return base + '.json'
|
|
42
|
+
|
|
43
|
+
def derivatives_dir(self, name):
|
|
44
|
+
parts = [
|
|
45
|
+
self._sub
|
|
46
|
+
]
|
|
47
|
+
if self._ses:
|
|
48
|
+
parts.append(self._ses)
|
|
49
|
+
return os.path.join(
|
|
50
|
+
self._base,
|
|
51
|
+
'derivatives',
|
|
52
|
+
name,
|
|
53
|
+
*parts
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def raw_anat(self, mod, sourcedata=False, **kwargs):
|
|
57
|
+
parts = [
|
|
58
|
+
self._sub
|
|
59
|
+
]
|
|
60
|
+
if self._ses:
|
|
61
|
+
parts.append(self._ses)
|
|
62
|
+
if sourcedata:
|
|
63
|
+
dirname = os.path.join(self._base, 'sourcedata', *parts, 'anat')
|
|
64
|
+
else:
|
|
65
|
+
dirname = os.path.join(self._base, *parts, 'anat')
|
|
66
|
+
acq = kwargs.get('acq', None)
|
|
67
|
+
ce = kwargs.get('ce', None)
|
|
68
|
+
rec = kwargs.get('rec', None)
|
|
69
|
+
run = kwargs.get('run', None)
|
|
70
|
+
brainmask = kwargs.get('brainmask', None)
|
|
71
|
+
if acq:
|
|
72
|
+
parts.append('acq-' + acq)
|
|
73
|
+
if ce:
|
|
74
|
+
parts.append('ce-' + ce)
|
|
75
|
+
if rec:
|
|
76
|
+
parts.append('rec-' + rec)
|
|
77
|
+
if run:
|
|
78
|
+
parts.append('run-' + str(run))
|
|
79
|
+
if brainmask:
|
|
80
|
+
parts.append('mod-' + mod + '_brainmask')
|
|
81
|
+
else:
|
|
82
|
+
parts.append(mod)
|
|
83
|
+
basename = '_'.join(parts)
|
|
84
|
+
return dirname, basename
|
|
85
|
+
|
|
86
|
+
def raw_bold(self, mod, sourcedata=False, **kwargs):
|
|
87
|
+
parts = [
|
|
88
|
+
self._sub
|
|
89
|
+
]
|
|
90
|
+
if self._ses:
|
|
91
|
+
parts.append(self._ses)
|
|
92
|
+
if sourcedata:
|
|
93
|
+
dirname = os.path.join(self._base, 'sourcedata', *parts, 'func')
|
|
94
|
+
else:
|
|
95
|
+
dirname = os.path.join(self._base, *parts, 'func')
|
|
96
|
+
acq = kwargs.get('acq', None)
|
|
97
|
+
ce = kwargs.get('ce', None)
|
|
98
|
+
rec = kwargs.get('rec', None)
|
|
99
|
+
run = kwargs.get('run', None)
|
|
100
|
+
brainmask = kwargs.get('brainmask', None)
|
|
101
|
+
if acq:
|
|
102
|
+
parts.append('acq-' + acq)
|
|
103
|
+
if ce:
|
|
104
|
+
parts.append('ce-' + ce)
|
|
105
|
+
if rec:
|
|
106
|
+
parts.append('rec-' + rec)
|
|
107
|
+
if run:
|
|
108
|
+
parts.append('run-' + str(run))
|
|
109
|
+
if brainmask:
|
|
110
|
+
parts.append('mod-' + mod + '_brainmask')
|
|
111
|
+
else:
|
|
112
|
+
parts.append(mod)
|
|
113
|
+
basename = '_'.join(parts)
|
|
114
|
+
return dirname, basename
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def parse(filename):
|
|
118
|
+
result = {
|
|
119
|
+
'filename': filename
|
|
120
|
+
}
|
|
121
|
+
# get extension
|
|
122
|
+
match = re.match('^.*(.dicom|.json|.nii|.nii.gz)$', filename)
|
|
123
|
+
result['ext'] = match.group(1) if match else None
|
|
124
|
+
# get sub component
|
|
125
|
+
match = re.match('^sub-([^\W_]+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
126
|
+
result['sub'] = match.group(1) if match else None
|
|
127
|
+
# get ses component
|
|
128
|
+
match = re.match('.*_ses-([^\W_]+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
129
|
+
result['ses'] = match.group(1) if match else None
|
|
130
|
+
# get acq component
|
|
131
|
+
match = re.match('.*_acq-([^\W_]+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
132
|
+
result['acq'] = match.group(1) if match else None
|
|
133
|
+
# get run component
|
|
134
|
+
match = re.match('.*_run-(\d+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
135
|
+
result['run'] = match.group(1) if match else None
|
|
136
|
+
# get rec component
|
|
137
|
+
match = re.match('.*_rec-(\d+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
138
|
+
result['rec'] = match.group(1) if match else None
|
|
139
|
+
# get ce component
|
|
140
|
+
match = re.match('.*_ce-(\d+).*\.(dicom|json|nii|nii.gz)$', filename)
|
|
141
|
+
result['ce'] = match.group(1) if match else None
|
|
142
|
+
# get defacemask component
|
|
143
|
+
match = re.match('.*(_defacemask)\.(dicom|json|nii|nii.gz)$', filename)
|
|
144
|
+
result['defacemask'] = True if match else None
|
|
145
|
+
# get modality component
|
|
146
|
+
if result['defacemask']:
|
|
147
|
+
match = re.match('.*_mod-([^\W_]+)_defacemask\.(dicom|json|nii|nii.gz)$', filename)
|
|
148
|
+
result['mod'] = match.group(1) if match else None
|
|
149
|
+
else:
|
|
150
|
+
match = re.match('.*_([^\W_]+)\.(dicom|json|nii|nii.gz)$', filename)
|
|
151
|
+
result['mod'] = match.group(1) if match else None
|
|
152
|
+
return result
|
|
153
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import yaml
|
|
6
|
+
import yaxil
|
|
7
|
+
import logging
|
|
8
|
+
import argparse as ap
|
|
9
|
+
import subprocess as sp
|
|
10
|
+
import collections as col
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
def do(args):
|
|
15
|
+
if args.insecure:
|
|
16
|
+
logger.warning('disabling ssl certificate verification')
|
|
17
|
+
yaxil.CHECK_CERTIFICATE = False
|
|
18
|
+
|
|
19
|
+
# load authentication data and set environment variables for ArcGet.py
|
|
20
|
+
auth = yaxil.auth2(
|
|
21
|
+
args.xnat_alias,
|
|
22
|
+
args.xnat_host,
|
|
23
|
+
args.xnat_user,
|
|
24
|
+
args.xnat_pass
|
|
25
|
+
)
|
|
26
|
+
os.environ['XNAT_HOST'] = auth.url
|
|
27
|
+
os.environ['XNAT_USER'] = auth.username
|
|
28
|
+
os.environ['XNAT_PASS'] = auth.password
|
|
29
|
+
|
|
30
|
+
# query BOLD scans
|
|
31
|
+
with yaxil.session(auth) as ses:
|
|
32
|
+
scans = col.defaultdict(dict)
|
|
33
|
+
for scan in ses.scans(label=args.label, project=args.project):
|
|
34
|
+
note = scan['note']
|
|
35
|
+
bold_match = re.match('.*(^|\s)#BOLD(?P<run>_\d+)?(\s|$).*', note, flags=re.IGNORECASE)
|
|
36
|
+
if bold_match:
|
|
37
|
+
run = bold_match.group('run')
|
|
38
|
+
run = re.sub('[^0-9]', '', run or '1')
|
|
39
|
+
run = int(run)
|
|
40
|
+
scans[run]['bold'] = scan['id']
|
|
41
|
+
logger.info(json.dumps(scans, indent=2))
|
|
42
|
+
for run,scansr in scans.items():
|
|
43
|
+
logger.info('getting bold run=%s, scan=%s', run, scansr['bold'])
|
|
44
|
+
get_bold(args, auth, run, scansr['bold'], verbose=args.verbose)
|
|
45
|
+
|
|
46
|
+
def get_bold(args, auth, run, scan, verbose=False):
|
|
47
|
+
config = {
|
|
48
|
+
'func': {
|
|
49
|
+
'bold': [
|
|
50
|
+
{
|
|
51
|
+
'run': int(run),
|
|
52
|
+
'scan': scan
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
config = yaml.safe_dump(config)
|
|
58
|
+
cmd = [
|
|
59
|
+
'ArcGet.py',
|
|
60
|
+
'--label', args.label,
|
|
61
|
+
'--output-dir', args.bids_dir,
|
|
62
|
+
'--output-format', 'bids',
|
|
63
|
+
]
|
|
64
|
+
if args.project:
|
|
65
|
+
cmd.extend([
|
|
66
|
+
'--project', args.project
|
|
67
|
+
])
|
|
68
|
+
if args.insecure:
|
|
69
|
+
cmd.extend([
|
|
70
|
+
'--insecure'
|
|
71
|
+
])
|
|
72
|
+
if args.in_mem:
|
|
73
|
+
cmd.extend([
|
|
74
|
+
'--in-mem'
|
|
75
|
+
])
|
|
76
|
+
cmd.extend([
|
|
77
|
+
'--config', '-'
|
|
78
|
+
])
|
|
79
|
+
if verbose:
|
|
80
|
+
cmd.append('--debug')
|
|
81
|
+
logger.info(sp.list2cmdline(cmd))
|
|
82
|
+
sp.check_output(cmd, input=config.encode('utf-8'))
|
|
83
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import yaml
|
|
6
|
+
import yaxil
|
|
7
|
+
import glob
|
|
8
|
+
import math
|
|
9
|
+
import boldqc
|
|
10
|
+
import logging
|
|
11
|
+
import tarfile
|
|
12
|
+
import executors
|
|
13
|
+
import tempfile as tf
|
|
14
|
+
import subprocess as sp
|
|
15
|
+
import boldqc.tasks.stackcheck_ext as stackcheck_ext
|
|
16
|
+
import boldqc.tasks.niftiqa_wrapper as niftiqa_wrapper
|
|
17
|
+
from executors.models import Job, JobArray
|
|
18
|
+
from boldqc.bids import BIDS
|
|
19
|
+
from boldqc.xnat import Report
|
|
20
|
+
from boldqc.state import State
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
LIBEXEC = os.path.realpath(os.path.join(os.path.dirname(boldqc.__file__), 'libexec'))
|
|
24
|
+
os.environ['PATH'] = LIBEXEC + ':' + os.environ['PATH']
|
|
25
|
+
|
|
26
|
+
def do(args):
|
|
27
|
+
if args.insecure:
|
|
28
|
+
logger.warning('disabling ssl certificate verification')
|
|
29
|
+
yaxil.CHECK_CERTIFICATE = False
|
|
30
|
+
|
|
31
|
+
# create job executor and job array
|
|
32
|
+
if args.scheduler:
|
|
33
|
+
E = executors.get(args.scheduler, partition=args.partition)
|
|
34
|
+
else:
|
|
35
|
+
E = executors.probe(args.partition)
|
|
36
|
+
jarray = JobArray(E)
|
|
37
|
+
|
|
38
|
+
# create BIDS
|
|
39
|
+
B = BIDS(args.bids_dir, args.sub, ses=args.ses)
|
|
40
|
+
raw = B.raw_bold('bold', run=args.run)
|
|
41
|
+
logger.debug('BOLD raw: %s', raw)
|
|
42
|
+
|
|
43
|
+
# get repetition time from T1w sidecar for vNav processing
|
|
44
|
+
sidecar = os.path.join(*raw) + '.json'
|
|
45
|
+
logger.debug('sidecar: %s', sidecar)
|
|
46
|
+
with open(sidecar) as fo:
|
|
47
|
+
js = json.load(fo)
|
|
48
|
+
tr = js['RepetitionTime']
|
|
49
|
+
logger.debug('TR: %s', tr)
|
|
50
|
+
|
|
51
|
+
boldqc_outdir = None
|
|
52
|
+
infile = os.path.join(*raw) + '.nii.gz'
|
|
53
|
+
boldqc_outdir = B.derivatives_dir('boldqc')
|
|
54
|
+
boldqc_outdir = os.path.join(boldqc_outdir, 'func', raw[1])
|
|
55
|
+
|
|
56
|
+
# niftiqa job
|
|
57
|
+
task = niftiqa_wrapper.Task(
|
|
58
|
+
infile,
|
|
59
|
+
boldqc_outdir
|
|
60
|
+
)
|
|
61
|
+
logger.info(json.dumps(task.command, indent=1))
|
|
62
|
+
jarray.add(task.job)
|
|
63
|
+
|
|
64
|
+
# stackcheck_ext job
|
|
65
|
+
task = stackcheck_ext.Task(
|
|
66
|
+
infile,
|
|
67
|
+
boldqc_outdir
|
|
68
|
+
)
|
|
69
|
+
logger.info(json.dumps(task.command, indent=1))
|
|
70
|
+
jarray.add(task.job)
|
|
71
|
+
|
|
72
|
+
# submit jobs and wait for them to finish
|
|
73
|
+
if not args.dry_run:
|
|
74
|
+
logger.info('submitting jobs')
|
|
75
|
+
jarray.submit(limit=1)
|
|
76
|
+
logger.info('waiting for all jobs to finish')
|
|
77
|
+
jarray.wait()
|
|
78
|
+
numjobs = len(jarray.array)
|
|
79
|
+
failed = len(jarray.failed)
|
|
80
|
+
complete = len(jarray.complete)
|
|
81
|
+
if failed:
|
|
82
|
+
logger.info('%s/%s jobs failed', failed, numjobs)
|
|
83
|
+
for pid,job in iter(jarray.failed.items()):
|
|
84
|
+
logger.error('%s exited with returncode %s', job.name, job.returncode)
|
|
85
|
+
with open(job.output, 'r') as fp:
|
|
86
|
+
logger.error('standard output\n%s', fp.read())
|
|
87
|
+
with open(job.error, 'r') as fp:
|
|
88
|
+
logger.error('standard error\n%s', fp.read())
|
|
89
|
+
logger.info('%s/%s jobs completed', complete, numjobs)
|
|
90
|
+
if failed > 0:
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
# artifacts directory
|
|
94
|
+
if not args.artifacts_dir:
|
|
95
|
+
args.artifacts_dir = os.path.join(
|
|
96
|
+
boldqc_outdir,
|
|
97
|
+
'xnat-artifacts'
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# build data to upload to XNAT
|
|
101
|
+
R = Report(args.bids_dir, args.sub, args.ses, args.run)
|
|
102
|
+
logger.info('building xnat artifacts to %s', args.artifacts_dir)
|
|
103
|
+
R.build_assessment(args.artifacts_dir)
|
|
104
|
+
|
|
105
|
+
# upload data to xnat over rest api
|
|
106
|
+
if args.xnat_upload:
|
|
107
|
+
logger.info('Uploading artifacts to XNAT')
|
|
108
|
+
auth = yaxil.auth2(args.xnat_alias)
|
|
109
|
+
yaxil.storerest(auth, args.artifacts_dir, 'boldqc-resource')
|
|
110
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
import yaml
|
|
5
|
+
import yaxil
|
|
6
|
+
import logging
|
|
7
|
+
import yaxil.bids
|
|
8
|
+
import argparse as ap
|
|
9
|
+
import subprocess as sp
|
|
10
|
+
import boldqc.cli.get
|
|
11
|
+
import boldqc.cli.process
|
|
12
|
+
import collections as col
|
|
13
|
+
import yaxil.bids
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def do(args):
|
|
18
|
+
if args.insecure:
|
|
19
|
+
logger.warning('disabling ssl certificate verification')
|
|
20
|
+
yaxil.CHECK_CERTIFICATE = False
|
|
21
|
+
|
|
22
|
+
# load authentication data and set environment variables for ArcGet.py
|
|
23
|
+
auth = yaxil.auth2(
|
|
24
|
+
args.xnat_alias,
|
|
25
|
+
args.xnat_host,
|
|
26
|
+
args.xnat_user,
|
|
27
|
+
args.xnat_pass
|
|
28
|
+
)
|
|
29
|
+
os.environ['XNAT_HOST'] = auth.url
|
|
30
|
+
os.environ['XNAT_USER'] = auth.username
|
|
31
|
+
os.environ['XNAT_PASS'] = auth.password
|
|
32
|
+
|
|
33
|
+
# query BOLD scans
|
|
34
|
+
with yaxil.session(auth) as ses:
|
|
35
|
+
scans = col.defaultdict(dict)
|
|
36
|
+
for scan in ses.scans(label=args.label, project=args.project):
|
|
37
|
+
note = scan['note']
|
|
38
|
+
bold_match = re.match('.*(^|\s)#BOLD(?P<run>_\d+)?(\s|$).*', note, flags=re.IGNORECASE)
|
|
39
|
+
if bold_match:
|
|
40
|
+
run = bold_match.group('run')
|
|
41
|
+
run = re.sub('[^0-9]', '', run or '1')
|
|
42
|
+
run = int(run)
|
|
43
|
+
scans[run]['bold'] = scan['id']
|
|
44
|
+
|
|
45
|
+
subject_label = scan['subject_label']
|
|
46
|
+
logger.info(json.dumps(scans, indent=2))
|
|
47
|
+
|
|
48
|
+
for run,scansr in scans.items():
|
|
49
|
+
if run != args.run:
|
|
50
|
+
continue
|
|
51
|
+
logger.info('getting bold run=%s, scan=%s', run, scansr['bold'])
|
|
52
|
+
boldqc.cli.get.get_bold(
|
|
53
|
+
args,
|
|
54
|
+
auth,
|
|
55
|
+
run,
|
|
56
|
+
scansr['bold'],
|
|
57
|
+
verbose=args.verbose
|
|
58
|
+
)
|
|
59
|
+
args.run = int(run)
|
|
60
|
+
args.run = int(run)
|
|
61
|
+
bids_ses_label = yaxil.bids.legal.sub('', args.label)
|
|
62
|
+
bids_sub_label = yaxil.bids.legal.sub('', subject_label)
|
|
63
|
+
args.sub = 'sub-' + bids_sub_label
|
|
64
|
+
args.ses = 'ses-' + bids_ses_label
|
|
65
|
+
logger.debug('sub=%s, ses=%s', args.sub, args.ses)
|
|
66
|
+
boldqc.cli.process.do(args)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
function isInt {
|
|
4
|
+
num="$1"
|
|
5
|
+
if ! [[ "$num" =~ ^-?[0-9]+$ ]]; then
|
|
6
|
+
return 0
|
|
7
|
+
fi
|
|
8
|
+
return 1
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isFloat {
|
|
12
|
+
num="$1"
|
|
13
|
+
if ! [[ "$num" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then
|
|
14
|
+
return 0
|
|
15
|
+
fi
|
|
16
|
+
return 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function checkMode {
|
|
20
|
+
file="$1"
|
|
21
|
+
mode="$2"
|
|
22
|
+
if [ ! -e "$file" -a "$mode" != "c" ]; then
|
|
23
|
+
error "file does not exist '$file'"
|
|
24
|
+
fi
|
|
25
|
+
case $mode in
|
|
26
|
+
r)
|
|
27
|
+
if [ ! -r "$file" ]; then
|
|
28
|
+
error "file is unreadable '$file'"
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
w)
|
|
32
|
+
if [ ! -r "$file" ]; then
|
|
33
|
+
error "file is not writable '$file'"
|
|
34
|
+
fi
|
|
35
|
+
;;
|
|
36
|
+
x)
|
|
37
|
+
if [ ! -x "$file" ]; then
|
|
38
|
+
error "file is not executable '$file'"
|
|
39
|
+
fi
|
|
40
|
+
;;
|
|
41
|
+
c)
|
|
42
|
+
parent=`dirname "$file"`
|
|
43
|
+
if [ ! -w "$parent" ]; then
|
|
44
|
+
error "cannot create file in '$parent'"
|
|
45
|
+
fi
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function dirBackup {
|
|
52
|
+
dir="$1"
|
|
53
|
+
if [ -e "$dir" ]; then
|
|
54
|
+
newdir="$dir-`date +'%Y-%M-%d_%H.%M.%S'`"
|
|
55
|
+
echo -n "Moving directory $dir to $newdir... "
|
|
56
|
+
if [ ! -w "$dir" ]; then
|
|
57
|
+
error "permission denied."
|
|
58
|
+
else
|
|
59
|
+
mv "$dir" "$newdir" || error "failed."
|
|
60
|
+
fi
|
|
61
|
+
else
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
echo "Done." && return 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function error {
|
|
68
|
+
echo "[ERROR]: $1"
|
|
69
|
+
exit 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function warn {
|
|
73
|
+
caller=`caller 1`
|
|
74
|
+
if [ $caller ]; then
|
|
75
|
+
echo -n "$caller ";
|
|
76
|
+
fi
|
|
77
|
+
echo "[WARN]: $1"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function check {
|
|
81
|
+
if [ -z `which "$1"` ]; then
|
|
82
|
+
[ -n "$2" ] && error "Could not find command $1 (hint: $2)"
|
|
83
|
+
error "Could not find command $1"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function execute {
|
|
88
|
+
cmd="$1"
|
|
89
|
+
verbose="$2"
|
|
90
|
+
[ -n "$cmd" ] || error "No command supplied to execute function."
|
|
91
|
+
if [ "$verbose" = "0" ]; then
|
|
92
|
+
cmd="$cmd &> /dev/null"
|
|
93
|
+
else
|
|
94
|
+
echo "$cmd"
|
|
95
|
+
fi
|
|
96
|
+
eval "$cmd" && return $?
|
|
97
|
+
}
|