xnatqa 0.0.20__py3-none-any.whl → 0.0.23__py3-none-any.whl

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/launch.py CHANGED
@@ -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'qsub -P cncxnat boldqc.qsub {MRsession} {b}')
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} {b}')
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'qsub -P cncxnat anatqc.qsub {MRsession} {a}')
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} {a}')
53
- a+=1
68
+ os.system(f'qsub -P cncxnat anatqc.qsub {MRsession} {run}')
xnatqa/tag/__init__.py CHANGED
@@ -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(t1 = [], t1_move = [], t2 = [], t2_move = [], bold = [])
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['t1'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w'})
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['t2'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w'})
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
- tagger_data['bold'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#BOLD'})
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['t1_move'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w_MOVE'})
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['t1'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T1w'})
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['t2_move'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w_move'})
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['t2'].append({'series_description': series_description, 'image_type': image_type, 'tag': '#T2w'})
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,15 +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
- # generate the xnattag config file
196
- generate_tagger_config(dicom_dir, this_session_working_dir)
197
-
198
- # update the xnat tags
199
- if not dryrun:
200
- update_xnat_tags(MRsession, this_session_working_dir)
201
-
@@ -1,6 +1,9 @@
1
1
  import os
2
2
  import argparse
3
- from xnatqa.tag import tag_scans
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
- tag_scans(dicom_dir, experiment, working_dir, dryrun)
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xnatqa
3
- Version: 0.0.20
3
+ Version: 0.0.23
4
4
  Summary: A workflow for automatically tagging MRI scans within XNAT
5
5
  Author-email: Kyle Kurkela <kkurkela@bu.edu>
6
6
  License-Expression: MIT
@@ -0,0 +1,10 @@
1
+ xnatqa/__init__.py,sha256=RIG_ihjfFD1upKlhlxI-9Fs4ISf0lIW4Zg7CaFirV4A,644
2
+ xnatqa/launch.py,sha256=k8e2_7XBIhJlJkTNQuTXVcPGnnkb_sbdp7jMIEjyTA8,2505
3
+ xnatqa/tagger.py,sha256=YmjtjZRtwOgXHVYrz1FcppgdjYoXl6u3Ln7v1WdKkOo,1540
4
+ xnatqa/tag/__init__.py,sha256=Sv1YUK0RdjkwZePlbZHpStM3nfFNpAczovwGXL9ZKkQ,9140
5
+ xnatqa-0.0.23.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ xnatqa-0.0.23.dist-info/METADATA,sha256=orWZW2DRO3PxWkiG1xXLH4IsY3w0t7DmHbsF7iiwx8E,1187
7
+ xnatqa-0.0.23.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ xnatqa-0.0.23.dist-info/entry_points.txt,sha256=ueAa8JobR7U4C8T4CszGxy2TQVp-2cK4OMMOBUbIYYw,74
9
+ xnatqa-0.0.23.dist-info/top_level.txt,sha256=FmRWgO6ww0FwyNs5ccyhP9FcAxI129qSJqLs2OKOxJw,7
10
+ xnatqa-0.0.23.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
2
  launch = xnatqa.launch:main
3
- xnatqa = xnatqa.xnatqa:main
3
+ tagger = xnatqa.tagger:main
@@ -1,10 +0,0 @@
1
- xnatqa/__init__.py,sha256=RIG_ihjfFD1upKlhlxI-9Fs4ISf0lIW4Zg7CaFirV4A,644
2
- xnatqa/launch.py,sha256=QWqLdwfbq28cmeJd4XQcV2wxs2ZGRX85uzFmxHydmfg,1963
3
- xnatqa/xnatqa.py,sha256=PEAe3LGFKZ2KSVO4Bd5vAGuMeaFkXCg5GVY1sCr8K-E,915
4
- xnatqa/tag/__init__.py,sha256=dmreB5C3_GqL6v3JIfUai59NeUtBQy6xSTb2UilcREM,9175
5
- xnatqa-0.0.20.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- xnatqa-0.0.20.dist-info/METADATA,sha256=TVvwxvUXdB2mQSGJlCGEctq_BtjA30mqomn1jn1Kwa4,1187
7
- xnatqa-0.0.20.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
8
- xnatqa-0.0.20.dist-info/entry_points.txt,sha256=g5CvKDfx6vIIp76wdB1aiWe1SkBORj2S8gFReBBxBws,74
9
- xnatqa-0.0.20.dist-info/top_level.txt,sha256=FmRWgO6ww0FwyNs5ccyhP9FcAxI129qSJqLs2OKOxJw,7
10
- xnatqa-0.0.20.dist-info/RECORD,,