xnatqa 0.0.21__tar.gz → 0.0.23__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.
- {xnatqa-0.0.21/xnatqa.egg-info → xnatqa-0.0.23}/PKG-INFO +1 -1
- {xnatqa-0.0.21 → xnatqa-0.0.23}/pyproject.toml +6 -2
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa/launch.py +23 -8
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa/tag/__init__.py +37 -39
- xnatqa-0.0.21/xnatqa/xnatqa.py → xnatqa-0.0.23/xnatqa/tagger.py +19 -2
- {xnatqa-0.0.21 → xnatqa-0.0.23/xnatqa.egg-info}/PKG-INFO +1 -1
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa.egg-info/SOURCES.txt +1 -1
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa.egg-info/entry_points.txt +1 -1
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa.egg-info/top_level.txt +1 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/LICENSE +0 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/README.md +0 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/setup.cfg +0 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa/__init__.py +0 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa.egg-info/dependency_links.txt +0 -0
- {xnatqa-0.0.21 → xnatqa-0.0.23}/xnatqa.egg-info/requires.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xnatqa"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.23"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Kyle Kurkela", email="kkurkela@bu.edu" },
|
|
6
6
|
]
|
|
@@ -21,13 +21,17 @@ dependencies = [
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[project.scripts]
|
|
24
|
-
|
|
24
|
+
tagger = "xnatqa.tagger:main"
|
|
25
25
|
launch = "xnatqa.launch:main"
|
|
26
26
|
|
|
27
27
|
[project.urls]
|
|
28
28
|
Homepage = "https://github.com/kakurk/auto_labeler"
|
|
29
29
|
Issues = "https://github.com/kakurk/auto_labeler/issues"
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
exclude = ["tests*"]
|
|
34
|
+
|
|
31
35
|
[build-system]
|
|
32
36
|
requires = ["setuptools >= 77.0.3"]
|
|
33
37
|
build-backend = "setuptools.build_meta"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import yaxil
|
|
3
3
|
import argparse
|
|
4
|
+
import re
|
|
4
5
|
|
|
5
6
|
def main():
|
|
6
7
|
# So, at this point, everything has been labeled for this session.
|
|
@@ -27,27 +28,41 @@ def main():
|
|
|
27
28
|
# open and automatically close a connection to XNAT using the auth
|
|
28
29
|
with yaxil.session(auth) as sess:
|
|
29
30
|
# keep track of the number of BOLD (b) and ANAT (a) scans idenfified
|
|
30
|
-
b = 0
|
|
31
|
-
a = 0
|
|
32
31
|
|
|
33
32
|
# for each scan in this session...
|
|
34
33
|
for scan in sess.scans(label=MRsession):
|
|
35
34
|
|
|
36
35
|
# this scan's note
|
|
37
36
|
note = scan['note']
|
|
37
|
+
id = scan['ID']
|
|
38
|
+
type = scan['type']
|
|
39
|
+
SeriesDesc = scan['series_description']
|
|
40
|
+
quality = scan['quality']
|
|
38
41
|
|
|
39
42
|
# if that note has a "#BOLD" tag...
|
|
40
43
|
if '#BOLD' in note:
|
|
44
|
+
|
|
45
|
+
run = re.search('[0-9]{3}', note)
|
|
46
|
+
run = int(run.group())
|
|
47
|
+
|
|
48
|
+
print('')
|
|
41
49
|
print('Run BOLDQC:')
|
|
42
|
-
print(f'
|
|
50
|
+
print(f'{id}\t{type}\t{SeriesDesc}\t{quality}\t{note}')
|
|
51
|
+
print(f'qsub -P cncxnat boldqc.qsub {MRsession} {run}')
|
|
52
|
+
print('')
|
|
43
53
|
if not dryrun:
|
|
44
|
-
os.system(f'qsub -P cncxnat boldqc.qsub {MRsession} {
|
|
45
|
-
b+=1
|
|
54
|
+
os.system(f'qsub -P cncxnat boldqc.qsub {MRsession} {run}')
|
|
46
55
|
|
|
47
56
|
# if that note has a "#T1w" tag...
|
|
48
57
|
if '#T1w' in note:
|
|
58
|
+
|
|
59
|
+
run = re.search('[0-9]{3}', note)
|
|
60
|
+
run = int(run.group())
|
|
61
|
+
|
|
62
|
+
print('')
|
|
49
63
|
print('Run ANATQC:')
|
|
50
|
-
print(f'
|
|
64
|
+
print(f'{id}\t{type}\t{SeriesDesc}\t{quality}\t{note}')
|
|
65
|
+
print(f'qsub -P cncxnat anatqc.qsub {MRsession} {run}')
|
|
66
|
+
print('')
|
|
51
67
|
if not dryrun:
|
|
52
|
-
os.system(f'qsub -P cncxnat anatqc.qsub {MRsession} {
|
|
53
|
-
a+=1
|
|
68
|
+
os.system(f'qsub -P cncxnat anatqc.qsub {MRsession} {run}')
|
|
@@ -3,6 +3,7 @@ import yaml
|
|
|
3
3
|
from glob import glob
|
|
4
4
|
import re
|
|
5
5
|
import xnatqa
|
|
6
|
+
import shutil
|
|
6
7
|
|
|
7
8
|
def extract_bids_suffix(bids_string):
|
|
8
9
|
# Extract the BIDS suffix from a BIDS formatted string
|
|
@@ -18,9 +19,15 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
18
19
|
# dcm2niix will also include a field called "BidsGuess" containing an "educated guess" as to what the BIDS label of this scan should be.
|
|
19
20
|
# This seems to work well most of the time, with the odd hicups. I include manual code here to catch the "hicups".
|
|
20
21
|
|
|
22
|
+
# first, make sure the dcm2niix is on the searchpath. Thow an informative error if it isn't
|
|
23
|
+
if shutil.which('dcm2niix') is None:
|
|
24
|
+
raise FileNotFoundError(f'dcm2niix not found on PATH')
|
|
25
|
+
|
|
21
26
|
# call to dcm2niix. generates a bunch of *.json text files in the current working directory.
|
|
22
27
|
os.system(f"dcm2niix -s y -a y -b o -o {working_dir} -f 'output_%s_%d' -w 0 -m 1 -i y {dicom_dir} &>>{working_dir}/log.txt")
|
|
23
28
|
|
|
29
|
+
def generate_tagger_yaml(working_dir):
|
|
30
|
+
|
|
24
31
|
# idenfity all of these text files
|
|
25
32
|
jsonFiles = glob(f'{working_dir}/output*.json')
|
|
26
33
|
|
|
@@ -29,7 +36,7 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
29
36
|
jsonFiles.sort(key=lambda f: int(re.search('(?<=output_)\d+(?=_)', f).group()))
|
|
30
37
|
|
|
31
38
|
# initialize a dictionary to hold xnattager data
|
|
32
|
-
tagger_data = dict(
|
|
39
|
+
tagger_data = dict(t1w = [], t1w_move = [], t2w = [], t2w_move = [], bold = [])
|
|
33
40
|
|
|
34
41
|
# looping over the json sidecar files...
|
|
35
42
|
for f in jsonFiles:
|
|
@@ -41,13 +48,14 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
41
48
|
series_description = json_data['SeriesDescription']
|
|
42
49
|
series_number = json_data['SeriesNumber']
|
|
43
50
|
image_type = json_data['ImageType']
|
|
44
|
-
# remove the last element of the image_type list
|
|
51
|
+
# remove the last element of the image_type list. For whatever reason
|
|
45
52
|
del image_type[-1]
|
|
46
|
-
bids_guess = json_data['BidsGuess']
|
|
47
53
|
|
|
48
54
|
# if there is a BidsGuess field...
|
|
49
55
|
if 'BidsGuess' in json_data.keys():
|
|
50
56
|
|
|
57
|
+
bids_guess = json_data['BidsGuess']
|
|
58
|
+
|
|
51
59
|
# if that guess was "func"
|
|
52
60
|
if bids_guess[0] == 'func':
|
|
53
61
|
|
|
@@ -58,12 +66,12 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
58
66
|
if bids_suffix == 'bold':
|
|
59
67
|
|
|
60
68
|
# there are a couple of common problems with the BidsGuess feature. One is that it does NOT properly identify "SBREF" scans. Make sure this is not an "SBREF" scan. Here I search for the keyword "SBREF" to be somewhere within the study description name.
|
|
61
|
-
sbref_keywords = ['sbref']
|
|
69
|
+
sbref_keywords = ['sbref'] # 'localizer'
|
|
62
70
|
|
|
63
71
|
if any(kw in series_description.lower() for kw in sbref_keywords):
|
|
64
72
|
|
|
65
73
|
print()
|
|
66
|
-
print(f'Series Number: {series_number}')
|
|
74
|
+
print(f'Series Number: {series_number}')
|
|
67
75
|
print(f'Series Description: {series_description}')
|
|
68
76
|
print(f"Bids Guess: {json_data['BidsGuess']}")
|
|
69
77
|
print('Is an SBREF scan. Ignoring...')
|
|
@@ -76,36 +84,40 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
76
84
|
if any(kw in series_description.lower() for kw in exclude_keywords):
|
|
77
85
|
|
|
78
86
|
print()
|
|
79
|
-
print(f'Series Number: {series_number}')
|
|
87
|
+
print(f'Series Number: {series_number}')
|
|
80
88
|
print(f'Series Description: {series_description}')
|
|
81
89
|
print(f"Bids Guess: {bids_guess}")
|
|
82
90
|
print('Relableing to T1...')
|
|
83
91
|
print()
|
|
84
92
|
|
|
85
|
-
tagger_data['
|
|
93
|
+
tagger_data['t1w'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w'})
|
|
86
94
|
continue
|
|
87
95
|
|
|
88
96
|
exclude_keywords = ['t2']
|
|
89
97
|
if any(kw in series_description.lower() for kw in exclude_keywords):
|
|
90
98
|
|
|
91
99
|
print()
|
|
92
|
-
print(f'Series Number: {series_number}')
|
|
100
|
+
print(f'Series Number: {series_number}')
|
|
93
101
|
print(f'Series Description: {series_description}')
|
|
94
102
|
print(f'Bids Guess: {bids_guess}')
|
|
95
103
|
print('Relableing to T2...')
|
|
96
104
|
print()
|
|
97
105
|
|
|
98
|
-
tagger_data['
|
|
106
|
+
#tagger_data['t2w'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w'})
|
|
99
107
|
continue
|
|
100
108
|
|
|
101
109
|
print()
|
|
102
|
-
print(f'Series Number: {series_number}')
|
|
110
|
+
print(f'Series Number: {series_number}')
|
|
103
111
|
print(f'Series Description: {series_description}')
|
|
104
112
|
print(f'Bids Guess: {bids_guess}')
|
|
105
113
|
print('Labeling as BOLD...')
|
|
106
114
|
print()
|
|
107
115
|
|
|
108
|
-
|
|
116
|
+
entry = {'series_description': series_description, 'image_type': image_type, 'tag': '#BOLD'}
|
|
117
|
+
|
|
118
|
+
if entry not in tagger_data['bold']:
|
|
119
|
+
tagger_data['bold'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#BOLD'})
|
|
120
|
+
|
|
109
121
|
continue
|
|
110
122
|
|
|
111
123
|
# if the BidsGuess was "anat"...
|
|
@@ -125,56 +137,56 @@ def generate_tagger_config(dicom_dir, working_dir):
|
|
|
125
137
|
# this is a T1w VNAV setter scan.
|
|
126
138
|
|
|
127
139
|
print()
|
|
128
|
-
print(f'Series Number: {series_number}')
|
|
140
|
+
print(f'Series Number: {series_number}')
|
|
129
141
|
print(f'Series Description: {series_description}')
|
|
130
142
|
print(f"Bids Guess: {bids_guess}")
|
|
131
143
|
print('Labeling as T1w_move...')
|
|
132
144
|
print()
|
|
133
|
-
|
|
134
|
-
tagger_data['
|
|
145
|
+
|
|
146
|
+
#tagger_data['t1w_move'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w_MOVE'})
|
|
135
147
|
continue
|
|
136
148
|
|
|
137
149
|
elif not NonlinearGradientCorrection and 'NumberOfAverages' in json_data:
|
|
138
|
-
|
|
150
|
+
|
|
139
151
|
# this is a T1w scan
|
|
140
152
|
|
|
141
153
|
print()
|
|
142
|
-
print(f'Series Number: {series_number}')
|
|
154
|
+
print(f'Series Number: {series_number}')
|
|
143
155
|
print(f'Series Description: {series_description}')
|
|
144
156
|
print(f"Bids Guess: {bids_guess}")
|
|
145
157
|
print('Labeling as T1w...')
|
|
146
158
|
print()
|
|
147
|
-
|
|
148
|
-
tagger_data['
|
|
159
|
+
|
|
160
|
+
tagger_data['t1w'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w'})
|
|
149
161
|
continue
|
|
150
162
|
|
|
151
163
|
elif bids_suffix == 'T2w':
|
|
152
164
|
|
|
153
165
|
if json_data['SliceThickness'] == 8:
|
|
154
|
-
|
|
166
|
+
|
|
155
167
|
# this is a T2w VNAV setter scan
|
|
156
168
|
print()
|
|
157
|
-
print(f'Series Number: {series_number}')
|
|
169
|
+
print(f'Series Number: {series_number}')
|
|
158
170
|
print(f'Series Description: {series_description}')
|
|
159
171
|
print(f"Bids Guess: {bids_guess}")
|
|
160
172
|
print('Labeling as T2w_move...')
|
|
161
173
|
print()
|
|
162
174
|
|
|
163
|
-
tagger_data['
|
|
175
|
+
#tagger_data['t2w_move'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w_move'})
|
|
164
176
|
continue
|
|
165
177
|
|
|
166
178
|
else:
|
|
167
179
|
|
|
168
180
|
# this is a T2w scan
|
|
169
181
|
print()
|
|
170
|
-
print(f'Series Number: {series_number}')
|
|
182
|
+
print(f'Series Number: {series_number}')
|
|
171
183
|
print(f'Series Description: {series_description}')
|
|
172
184
|
print(f"Bids Guess: {bids_guess}")
|
|
173
|
-
print('Labeling as T2w...')
|
|
185
|
+
print('Labeling as T2w...')
|
|
174
186
|
print()
|
|
175
187
|
|
|
176
|
-
tagger_data['
|
|
177
|
-
continue
|
|
188
|
+
#tagger_data['t2w'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w'})
|
|
189
|
+
continue
|
|
178
190
|
|
|
179
191
|
# write tagger data to a yaml file. used by the xnattagger package for uploading tags to XNAT. See github.com/harvard-nrg/xnattager
|
|
180
192
|
with open(f'{working_dir}/tagger.yaml', 'a') as file:
|
|
@@ -187,17 +199,3 @@ def update_xnat_tags(MRsession, working_dir):
|
|
|
187
199
|
|
|
188
200
|
# run the command
|
|
189
201
|
os.system(f'xnat_tagger.py --label {MRsession} --target-modality all --xnat-alias xnat --config {working_dir}/tagger.yaml')
|
|
190
|
-
|
|
191
|
-
def tag_scans(dicom_dir, MRsession, working_dir, dryrun):
|
|
192
|
-
|
|
193
|
-
this_session_working_dir=os.path.join(working_dir, MRsession)
|
|
194
|
-
|
|
195
|
-
os.mkdir(this_session_working_dir)
|
|
196
|
-
|
|
197
|
-
# generate the xnattag config file
|
|
198
|
-
generate_tagger_config(dicom_dir, this_session_working_dir)
|
|
199
|
-
|
|
200
|
-
# update the xnat tags
|
|
201
|
-
if not dryrun:
|
|
202
|
-
update_xnat_tags(MRsession, this_session_working_dir)
|
|
203
|
-
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import argparse
|
|
3
|
-
from xnatqa.tag import
|
|
3
|
+
from xnatqa.tag import generate_tagger_config
|
|
4
|
+
from xnatqa.tag import generate_tagger_yaml
|
|
5
|
+
from xnatqa.tag import update_xnat_tags
|
|
6
|
+
import shutil
|
|
4
7
|
|
|
5
8
|
def main():
|
|
6
9
|
|
|
@@ -18,7 +21,21 @@ def main():
|
|
|
18
21
|
dryrun = args.dryrun
|
|
19
22
|
|
|
20
23
|
# tag all scans in this session
|
|
21
|
-
|
|
24
|
+
# create a working directory to write temporary files
|
|
25
|
+
this_session_working_dir = os.path.join(working_dir, 'xnattager', experiment)
|
|
26
|
+
if os.path.isdir(this_session_working_dir):
|
|
27
|
+
shutil.rmtree(this_session_working_dir)
|
|
28
|
+
os.makedirs(this_session_working_dir)
|
|
29
|
+
|
|
30
|
+
# generate the xnattag config file
|
|
31
|
+
generate_tagger_config(dicom_dir, this_session_working_dir)
|
|
32
|
+
|
|
33
|
+
# general the yaml
|
|
34
|
+
generate_tagger_yaml(this_session_working_dir)
|
|
35
|
+
|
|
36
|
+
# update the xnat tags
|
|
37
|
+
if not dryrun:
|
|
38
|
+
update_xnat_tags(experiment, this_session_working_dir)
|
|
22
39
|
|
|
23
40
|
if __name__ == "__main__":
|
|
24
41
|
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|