oceanproc 1.2.2__tar.gz → 1.3.0__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.
- {oceanproc-1.2.2 → oceanproc-1.3.0}/PKG-INFO +1 -1
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/bids_wrapper.py +40 -18
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/events_long.py +6 -1
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/group_series.py +8 -34
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/ocean_proc.py +53 -25
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/preprocessing_wrapper.py +50 -15
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/segmentation_wrapper.py +32 -8
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/utils.py +35 -7
- {oceanproc-1.2.2 → oceanproc-1.3.0}/pyproject.toml +1 -1
- {oceanproc-1.2.2 → oceanproc-1.3.0}/README.md +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/firstlevel/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/firstlevel/run_glm.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/firstlevel/tests/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/firstlevel/tests/test_run_glm.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/grouplevel/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/grouplevel/computations.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/grouplevel/design_mat_utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/grouplevel/dscalar_utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/grouplevel/utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.0}/oceanproc/oceanparse.py +0 -0
|
@@ -4,14 +4,12 @@ from argparse import ArgumentParser
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
-
import re
|
|
8
7
|
import shutil
|
|
9
|
-
import subprocess
|
|
10
8
|
from textwrap import dedent
|
|
11
9
|
import xml.etree.ElementTree as et
|
|
12
|
-
from .utils import exit_program_early, prompt_user_continue, prepare_subprocess_logging, debug_logging, log_linebreak, flags, run_subprocess
|
|
10
|
+
from .utils import exit_program_early, prompt_user_continue, prepare_subprocess_logging, debug_logging, log_linebreak, flags, run_subprocess, update_permissions
|
|
13
11
|
import logging
|
|
14
|
-
from bids import BIDSLayout
|
|
12
|
+
from bids import BIDSLayout
|
|
15
13
|
from bisect import bisect
|
|
16
14
|
module_logger = logging.getLogger("fsspec")
|
|
17
15
|
module_logger.setLevel(logging.CRITICAL)
|
|
@@ -136,16 +134,16 @@ def run_dcm2bids(subject:str,
|
|
|
136
134
|
logger.debug(f"deleting file: {f}")
|
|
137
135
|
os.remove(f)
|
|
138
136
|
|
|
139
|
-
except
|
|
137
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
140
138
|
prepare_subprocess_logging(logger, stop=True)
|
|
141
139
|
logger.exception(e, stack_info=True)
|
|
142
|
-
|
|
140
|
+
return False
|
|
143
141
|
|
|
142
|
+
return True
|
|
144
143
|
|
|
145
144
|
@debug_logging
|
|
146
145
|
def run_dcm2niix(source_dir:Path,
|
|
147
|
-
tmp_nifti_dir:Path
|
|
148
|
-
clean_up_func=None):
|
|
146
|
+
tmp_nifti_dir:Path):
|
|
149
147
|
"""
|
|
150
148
|
Run dcm2niix with the given input and output directories.
|
|
151
149
|
|
|
@@ -176,11 +174,12 @@ def run_dcm2niix(source_dir:Path,
|
|
|
176
174
|
log_linebreak()
|
|
177
175
|
logger.info("####### Converting DICOM files into NIFTI #######\n")
|
|
178
176
|
run_subprocess(helper_command, title=title)
|
|
179
|
-
except
|
|
177
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
180
178
|
prepare_subprocess_logging(logger, stop=True)
|
|
181
179
|
logger.exception(e, stack_info=True)
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
return True
|
|
184
183
|
|
|
185
184
|
|
|
186
185
|
def get_usability_from_file(usability_file:Path):
|
|
@@ -322,7 +321,8 @@ def dicom_to_bids(subject:str,
|
|
|
322
321
|
if not p.exists():
|
|
323
322
|
exit_program_early(f"Path {str(p)} does not exist.")
|
|
324
323
|
|
|
325
|
-
|
|
324
|
+
sub_ses_bids_dir = bids_dir / f"sub-{subject}/ses-{session}"
|
|
325
|
+
if sub_ses_bids_dir.exists() and (not skip_prompt):
|
|
326
326
|
ans = prompt_user_continue(dedent("""
|
|
327
327
|
A raw data bids path for this subject and session already exists.
|
|
328
328
|
Would you like to delete its contents and rerun dcm2bids? If not,
|
|
@@ -330,7 +330,7 @@ def dicom_to_bids(subject:str,
|
|
|
330
330
|
"""))
|
|
331
331
|
if ans:
|
|
332
332
|
logger.debug("removing the old BIDS raw data directory and its contents")
|
|
333
|
-
shutil.rmtree(
|
|
333
|
+
shutil.rmtree(sub_ses_bids_dir)
|
|
334
334
|
else:
|
|
335
335
|
return
|
|
336
336
|
|
|
@@ -345,16 +345,27 @@ def dicom_to_bids(subject:str,
|
|
|
345
345
|
logger.warning(f"There was a problem deleting the temporary directory at {tmp_nifti_path}")
|
|
346
346
|
|
|
347
347
|
clean_up(quiet=True)
|
|
348
|
+
nii_success = True
|
|
348
349
|
if not nifti:
|
|
349
|
-
run_dcm2niix(source_dir=source_dir,
|
|
350
|
-
|
|
350
|
+
nii_success = run_dcm2niix(source_dir=source_dir,
|
|
351
|
+
tmp_nifti_dir=tmp_nifti_path)
|
|
351
352
|
else:
|
|
352
353
|
# copy current niftis to tmp_nifti_path
|
|
353
354
|
logger.info(f"copying the source NIFTI files to tmp directory, {tmp_nifti_path}")
|
|
354
355
|
shutil.copytree(source_dir.resolve(), tmp_nifti_path.resolve())
|
|
355
356
|
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
# update the permissions on the nifti files
|
|
358
|
+
update_permissions(
|
|
359
|
+
permissions=flags.file_permissions,
|
|
360
|
+
path=tmp_nifti_path,
|
|
361
|
+
recursive=True,
|
|
362
|
+
group=flags.permissions_group
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
if not nii_success:
|
|
366
|
+
exit_program_early(f"Problem running 'dcm2niix'")
|
|
367
|
+
|
|
368
|
+
# filter out known 'weird' files that can be created during conversion
|
|
358
369
|
if not keep_all_niftis:
|
|
359
370
|
files_to_remove = []
|
|
360
371
|
short_runs = list(tmp_nifti_path.glob("[!.]*[a-z].nii*")) + list(tmp_nifti_path.glob("[!.]*[a-z].json"))
|
|
@@ -396,13 +407,24 @@ def dicom_to_bids(subject:str,
|
|
|
396
407
|
usability_file=usability_file,
|
|
397
408
|
session_number=session_index)
|
|
398
409
|
|
|
399
|
-
run_dcm2bids(subject=subject,
|
|
410
|
+
dcm2bids_success = run_dcm2bids(subject=subject,
|
|
400
411
|
session=session,
|
|
401
412
|
nifti_dir=tmp_nifti_path,
|
|
402
413
|
bids_output_dir=bids_dir,
|
|
403
414
|
config_file=bids_config,
|
|
404
415
|
nordic_config=nordic_config,
|
|
405
416
|
skip_validate=skip_validate)
|
|
417
|
+
|
|
418
|
+
update_permissions(
|
|
419
|
+
permissions=flags.file_permissions,
|
|
420
|
+
path=sub_ses_bids_dir,
|
|
421
|
+
recursive=True,
|
|
422
|
+
group=flags.permissions_group,
|
|
423
|
+
quiet=True
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if not dcm2bids_success:
|
|
427
|
+
exit_program_early(f"Problem running 'dcm2bids'")
|
|
406
428
|
|
|
407
429
|
if not flags.debug:
|
|
408
430
|
clean_up()
|
|
@@ -2,7 +2,7 @@ import pandas as pd
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import nibabel as nib
|
|
4
4
|
import json
|
|
5
|
-
from .utils import exit_program_early, debug_logging, log_linebreak
|
|
5
|
+
from .utils import exit_program_early, debug_logging, log_linebreak, update_permissions, flags
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import logging
|
|
8
8
|
|
|
@@ -162,6 +162,11 @@ def create_events_and_confounds(bids_path:Path, derivs_path:Path, sub:str, ses:s
|
|
|
162
162
|
output_file=event_file_out)
|
|
163
163
|
append_to_confounds(confounds_file=confounds_file,
|
|
164
164
|
fd_thresh=fd_thresh)
|
|
165
|
+
update_permissions(
|
|
166
|
+
permissions=flags.file_permissions,
|
|
167
|
+
path=event_file_out,
|
|
168
|
+
group=flags.permissions_group
|
|
169
|
+
)
|
|
165
170
|
|
|
166
171
|
|
|
167
172
|
|
|
@@ -3,53 +3,20 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
5
|
import argparse
|
|
6
|
-
import re
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
import xml.etree.ElementTree as et
|
|
9
7
|
from bids import BIDSLayout
|
|
10
8
|
import logging
|
|
11
9
|
|
|
12
10
|
from bids.layout import BIDSFile
|
|
13
|
-
from .utils import exit_program_early, debug_logging, log_linebreak, flags
|
|
11
|
+
from .utils import exit_program_early, debug_logging, log_linebreak, flags, update_permissions
|
|
14
12
|
import nibabel as nib
|
|
15
13
|
import numpy as np
|
|
16
|
-
import shutil
|
|
17
14
|
|
|
18
15
|
module_logger = logging.getLogger("fsspec")
|
|
19
16
|
module_logger.setLevel(logging.CRITICAL)
|
|
20
17
|
|
|
21
18
|
logger = logging.getLogger(__name__)
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
# @debug_logging
|
|
25
|
-
# def get_locals_from_xml(xml_path: Path) -> tuple[set, str]:
|
|
26
|
-
# """
|
|
27
|
-
# Read in the xml file to find the localizers.
|
|
28
|
-
|
|
29
|
-
# :param xml_path: Path to XML generated by XNAT
|
|
30
|
-
# :type xml_path: pathlib.Path
|
|
31
|
-
# :return: Set containing all localizer IDs
|
|
32
|
-
# :rtype: set
|
|
33
|
-
# """
|
|
34
|
-
# tree = et.parse(xml_path)
|
|
35
|
-
# prefix = "{" + str(tree.getroot()).split("{")[-1].split("}")[0] + "}"
|
|
36
|
-
# scan_element_list = list(tree.iter(f"{prefix}scans"))
|
|
37
|
-
# exp_element_list = list(tree.iter(f"{prefix}experiments"))
|
|
38
|
-
# study_id = exp_element_list[0][0].get("study_id")
|
|
39
|
-
|
|
40
|
-
# if len(scan_element_list) != 1:
|
|
41
|
-
# exit_program_early(f"Error parsing the xml file provided. Found none or more than one scan groups")
|
|
42
|
-
|
|
43
|
-
# scans = scan_element_list[0]
|
|
44
|
-
|
|
45
|
-
# localizers = set()
|
|
46
|
-
# for s in scans:
|
|
47
|
-
# if re.match(r"Localizer.*", s.get("type")) and s.find(f'{prefix}quality').text == "usable":
|
|
48
|
-
# acq_time = datetime.strptime(s.find(f"{prefix}startTime").text, "%H:%M:%S")
|
|
49
|
-
# series_id = s.get("ID")
|
|
50
|
-
# localizers.add((acq_time, series_id))
|
|
51
|
-
# return (sorted(localizers), study_id)
|
|
52
|
-
|
|
53
20
|
DEFAULT_LOCALIZER_LABEL = "00"
|
|
54
21
|
EXTRACTED_ACQ_VALUE = "bestVol"
|
|
55
22
|
|
|
@@ -331,6 +298,13 @@ def extract_best_acqusition(fmap_file: str|BIDSFile, bids_layout: BIDSLayout) ->
|
|
|
331
298
|
with open(best_acq_json, "w") as out_js:
|
|
332
299
|
json.dump(jd, out_js, indent=4)
|
|
333
300
|
|
|
301
|
+
for fmap_out_path in [best_acq_fmap, best_acq_json]:
|
|
302
|
+
update_permissions(
|
|
303
|
+
permissions=flags.file_permissions,
|
|
304
|
+
path=fmap_out_path,
|
|
305
|
+
group=flags.permissions_group,
|
|
306
|
+
)
|
|
307
|
+
|
|
334
308
|
return (BIDSFile(best_acq_fmap), BIDSFile(best_acq_json))
|
|
335
309
|
|
|
336
310
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
from ast import arg
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
import logging
|
|
@@ -10,10 +11,12 @@ from oceanproc.group_series import map_fmap_to_func, map_fmap_to_func_with_pairi
|
|
|
10
11
|
from oceanproc.preprocessing_wrapper import process_data, adult_defaults, infant_defaults, mount_opts, insert_dummy_frames
|
|
11
12
|
from oceanproc.segmentation_wrapper import segmentation_args, segment_anatomical
|
|
12
13
|
from oceanproc.events_long import create_events_and_confounds
|
|
13
|
-
from oceanproc.utils import
|
|
14
|
+
from oceanproc.utils import *
|
|
14
15
|
from oceanproc.oceanparse import OceanParser
|
|
15
16
|
from bids import BIDSLayout, BIDSLayoutIndexer
|
|
16
17
|
import shutil
|
|
18
|
+
import grp
|
|
19
|
+
import os
|
|
17
20
|
from textwrap import dedent
|
|
18
21
|
|
|
19
22
|
logging.basicConfig(level=logging.INFO,
|
|
@@ -65,6 +68,32 @@ def main():
|
|
|
65
68
|
f"path string <{path}> does not represent an existing file"
|
|
66
69
|
)
|
|
67
70
|
return p
|
|
71
|
+
|
|
72
|
+
def ValidPermissions(permissions):
|
|
73
|
+
try:
|
|
74
|
+
validate_permissions_string(permissions)
|
|
75
|
+
except ValueError:
|
|
76
|
+
raise argparse.ArgumentTypeError(
|
|
77
|
+
f"permissions string <{permissions}> does not represent a valid permissions string"
|
|
78
|
+
)
|
|
79
|
+
return permissions
|
|
80
|
+
|
|
81
|
+
def ValidGroup(group):
|
|
82
|
+
try:
|
|
83
|
+
group_id = grp.getgrnam(group).gr_gid
|
|
84
|
+
valid_groups = os.getgroups()
|
|
85
|
+
if group_id not in valid_groups:
|
|
86
|
+
raise argparse.ArgumentTypeError(
|
|
87
|
+
f"current user does not belong to group <{group}>"
|
|
88
|
+
)
|
|
89
|
+
except KeyError:
|
|
90
|
+
raise argparse.ArgumentTypeError(
|
|
91
|
+
f"group name <{group}> is not a valid system user group"
|
|
92
|
+
)
|
|
93
|
+
return group
|
|
94
|
+
|
|
95
|
+
default_user_group = grp.getgrgid(os.getgid()).gr_name
|
|
96
|
+
default_file_permissions = "771"
|
|
68
97
|
|
|
69
98
|
parser = OceanParser(
|
|
70
99
|
prog="oceanproc",
|
|
@@ -74,7 +103,7 @@ def main():
|
|
|
74
103
|
epilog="An arguments file can be accepted with @FILEPATH.\nAny additional arguments not listed in the help message will be passed to the preprocessing subprocess.",
|
|
75
104
|
allow_abbrev=False
|
|
76
105
|
)
|
|
77
|
-
parser.add_argument("--version", action="version", version='%(prog)s v1.
|
|
106
|
+
parser.add_argument("--version", action="version", version='%(prog)s v1.3.0')
|
|
78
107
|
session_args = parser.add_argument_group("Session Specific")
|
|
79
108
|
config_args = parser.add_argument_group("Configuration Arguments", "These arguments are saved to a file if the '--export_args' option is used")
|
|
80
109
|
parser.register("type", "Full-Path", lambda p: Path(p).resolve())
|
|
@@ -155,19 +184,16 @@ def main():
|
|
|
155
184
|
help="Path to the BIBSnet apptainer image to use for segmentation. If provided, BIBSnet segmentation will be run and the outputs will be used for preprocessing. (Must be used with the '--infant' flag)")
|
|
156
185
|
config_args.add_argument("--bibsnet_work", "-bw", type=ExistingDir,
|
|
157
186
|
help="The path to the working directory used to store intermediate files for BIBSnet")
|
|
187
|
+
config_args.add_argument("--permissions_group", "-g", type=ValidGroup, default=default_user_group,
|
|
188
|
+
help=f"The user group that should own all of the created files. (Default is for the current user is '{default_user_group}'")
|
|
189
|
+
config_args.add_argument("--file_permissions", "-fp", type=ValidPermissions, default=default_file_permissions,
|
|
190
|
+
help=f"The file permissions that all output files should have, represented in numeric format. (Default permissions is '{default_file_permissions}'")
|
|
158
191
|
args, unknown_args = parser.parse_known_args()
|
|
159
192
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# assert args.source_data.is_dir(), "Source data directory must exist but it cannot be found"
|
|
165
|
-
# assert args.work_dir.is_dir(), "Work directroy must exist but it cannot be found"
|
|
166
|
-
# except AssertionError as e:
|
|
167
|
-
# logger.exception(e)
|
|
168
|
-
# parser.error(e)
|
|
169
|
-
|
|
170
|
-
defaults = infant_defaults if args.infant else adult_defaults
|
|
193
|
+
if args.infant:
|
|
194
|
+
flags.infant = True
|
|
195
|
+
|
|
196
|
+
defaults = infant_defaults if flags.infant else adult_defaults
|
|
171
197
|
for k,v in defaults.__dict__.items():
|
|
172
198
|
if k in args.__dict__ and args.__dict__[k] is None:
|
|
173
199
|
args.__dict__[k] = v
|
|
@@ -175,15 +201,12 @@ def main():
|
|
|
175
201
|
unknown_args = extract_options(unknown_args)
|
|
176
202
|
|
|
177
203
|
bibsnet_path = (args.derivs_path / "bibsnet").resolve()
|
|
178
|
-
if
|
|
204
|
+
if flags.infant and args.bibsnet_image_path:
|
|
179
205
|
if args.derivatives is None:
|
|
180
206
|
args.derivatives = [bibsnet_path]
|
|
181
207
|
elif bibsnet_path not in args.derivatives:
|
|
182
208
|
args.derivatives.append(bibsnet_path)
|
|
183
209
|
|
|
184
|
-
# if args.bibsnet_work and (not args.bibsnet_work.exists()):
|
|
185
|
-
# parser.error("BIBSnet working directory must exist but it cannot be found")
|
|
186
|
-
|
|
187
210
|
if args.longitudinal:
|
|
188
211
|
for lg_group in args.longitudinal:
|
|
189
212
|
if not lg_group[0].is_dir():
|
|
@@ -195,11 +218,10 @@ def main():
|
|
|
195
218
|
parser.error(f"usability file must be a .xml or .json file, and it must exist: {lg_group[1]}")
|
|
196
219
|
else:
|
|
197
220
|
parser.error(f"longitudinal argument cannot have more than 2 elements: {lg_group}")
|
|
221
|
+
flags.longitudinal = True
|
|
198
222
|
|
|
199
223
|
preproc_image = f"{defaults.image_name}:{args.image_version}"
|
|
200
224
|
if args.preproc_image_path:
|
|
201
|
-
# if not args.preproc_image_path.exists():
|
|
202
|
-
# parser.error(f"Cannot find the preprocessing apptainer image at the path: {args.preproc_image_path}")
|
|
203
225
|
flags.apptainer = True
|
|
204
226
|
preproc_image = args.preproc_image_path
|
|
205
227
|
|
|
@@ -212,6 +234,11 @@ def main():
|
|
|
212
234
|
if args.debug_mode:
|
|
213
235
|
flags.debug = True
|
|
214
236
|
logger.setLevel(logging.DEBUG)
|
|
237
|
+
|
|
238
|
+
flags.file_permissions = args.file_permissions
|
|
239
|
+
flags.permissions_group = args.permissions_group
|
|
240
|
+
flags.gid = grp.getgrnam(flags.permissions_group).gr_gid
|
|
241
|
+
flags.uid = os.getuid()
|
|
215
242
|
|
|
216
243
|
logger.info("Starting oceanproc...")
|
|
217
244
|
logger.info(f"Log will be stored at {log_path}")
|
|
@@ -222,14 +249,11 @@ def main():
|
|
|
222
249
|
assert args.export_args.parent.exists() and args.export_args.suffix, "Argument export path must be a file path in a directory that exists"
|
|
223
250
|
logger.info(f"####### Exporting Configuration Arguments to: '{args.export_args}' #######")
|
|
224
251
|
export_args_to_file(args, config_args, args.export_args, extra_args=unknown_args)
|
|
252
|
+
update_permissions(permissions=flags.file_permissions, path=args.export_args, group=flags.permissions_group)
|
|
225
253
|
except Exception as e:
|
|
226
254
|
logger.exception(e)
|
|
227
255
|
exit_program_early(e)
|
|
228
256
|
|
|
229
|
-
if args.longitudinal:
|
|
230
|
-
flags.longitudinal = True
|
|
231
|
-
if args.infant:
|
|
232
|
-
flags.infant = True
|
|
233
257
|
|
|
234
258
|
# log the input arguments
|
|
235
259
|
for k,v in (dict(args._get_kwargs())).items():
|
|
@@ -275,6 +299,11 @@ def main():
|
|
|
275
299
|
with open(bids_path / ".bidsignore", "r") as f:
|
|
276
300
|
ignore_list.extend(f.readlines())
|
|
277
301
|
ignore_list = list(set(ignore_list))
|
|
302
|
+
other_sub_paths = [
|
|
303
|
+
p.name for p in bids_path.glob("sub-*")
|
|
304
|
+
if not f"sub-{args.subject}".startswith(p.name) and p.is_dir() # 'startswith' to account for pybids regexifying after the end of subject name
|
|
305
|
+
]
|
|
306
|
+
ignore_list.extend(other_sub_paths)
|
|
278
307
|
bids_layout = BIDSLayout(args.bids_path, validate=False, indexer=BIDSLayoutIndexer(ignore=ignore_list, validate=False))
|
|
279
308
|
|
|
280
309
|
##### Remove the scans marked as 'unusable' #####
|
|
@@ -299,7 +328,6 @@ def main():
|
|
|
299
328
|
subject=args.subject,
|
|
300
329
|
session=args.session,
|
|
301
330
|
layout=bids_layout,
|
|
302
|
-
# xml_path=xml_data_path,
|
|
303
331
|
allow_uneven_fmap_groups=args.allow_uneven_fmap_groups,
|
|
304
332
|
extract_best_fmap=args.extract_best_fmap
|
|
305
333
|
)
|
|
@@ -359,7 +387,7 @@ def main():
|
|
|
359
387
|
)
|
|
360
388
|
|
|
361
389
|
##### Create long formatted event files #####
|
|
362
|
-
if not args.skip_event_files:
|
|
390
|
+
if not args.skip_event_files and not args.anat_only:
|
|
363
391
|
create_events_and_confounds(
|
|
364
392
|
bids_path=args.bids_path,
|
|
365
393
|
derivs_path=preproc_derivs_path,
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import logging
|
|
5
5
|
from bids import BIDSLayout
|
|
6
|
-
from .utils import exit_program_early, make_option, prepare_subprocess_logging, flags, debug_logging, log_linebreak, run_subprocess
|
|
6
|
+
from .utils import exit_program_early, make_option, prepare_subprocess_logging, flags, debug_logging, log_linebreak, run_subprocess, update_permissions
|
|
7
7
|
import shutil
|
|
8
|
-
from subprocess import Popen, PIPE
|
|
9
8
|
from matplotlib import pyplot as plt
|
|
10
9
|
from bs4 import BeautifulSoup as bsoup
|
|
11
10
|
import pandas as pd
|
|
@@ -85,10 +84,8 @@ def run_preprocessing(subject:str,
|
|
|
85
84
|
if flags.apptainer:
|
|
86
85
|
cmd_prelude = f"apptainer run --nv --cleanenv --no-mount cwd --pwd {work_mount}"
|
|
87
86
|
mount_flag = "-B"
|
|
88
|
-
else:
|
|
89
|
-
|
|
90
|
-
gid = Popen(["id", "-g"], stdout=PIPE).stdout.read().decode("utf-8").strip()
|
|
91
|
-
cmd_prelude = f"docker run --rm -i -u {uid}:{gid}"
|
|
87
|
+
else:
|
|
88
|
+
cmd_prelude = f"docker run --rm -i -u {flags.uid}:{flags.gid}"
|
|
92
89
|
mount_flag = "-v"
|
|
93
90
|
|
|
94
91
|
additional_mount_paths = []
|
|
@@ -122,16 +119,18 @@ def run_preprocessing(subject:str,
|
|
|
122
119
|
{additional_mount_args}
|
|
123
120
|
{option_chain}"""
|
|
124
121
|
|
|
122
|
+
success = True
|
|
125
123
|
try:
|
|
126
124
|
run_subprocess(preproc_command, title=title)
|
|
127
|
-
except
|
|
125
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
128
126
|
prepare_subprocess_logging(logger, stop=True)
|
|
129
127
|
logger.exception(e, stack_info=True)
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
success = False
|
|
129
|
+
|
|
132
130
|
if not flags.debug:
|
|
133
131
|
clean_up()
|
|
134
132
|
|
|
133
|
+
return success
|
|
135
134
|
|
|
136
135
|
@debug_logging
|
|
137
136
|
def add_fd_plot_to_report(subject:str,
|
|
@@ -356,7 +355,7 @@ def process_data(subject:str,
|
|
|
356
355
|
option_chain = " ".join([make_option(v, key=k, delimeter=" ", convert_underscore=True) for k,v in kwargs.items()])
|
|
357
356
|
preproc_title = "NiBabies" if flags.infant else "fMRIPrep"
|
|
358
357
|
|
|
359
|
-
run_preprocessing(subject=subject,
|
|
358
|
+
preproc_success = run_preprocessing(subject=subject,
|
|
360
359
|
session=session if flags.infant else None,
|
|
361
360
|
bids_path=bids_path,
|
|
362
361
|
derivs_path=derivs_path,
|
|
@@ -367,8 +366,44 @@ def process_data(subject:str,
|
|
|
367
366
|
option_chain=option_chain,
|
|
368
367
|
additional_mounts=additional_mounts,
|
|
369
368
|
remove_work_folder=remove_work_folder)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
|
|
370
|
+
if preproc_success:
|
|
371
|
+
add_fd_plot_to_report(subject=subject,
|
|
372
|
+
session=session,
|
|
373
|
+
derivs_path=derivs_path,
|
|
374
|
+
title=preproc_title)
|
|
375
|
+
|
|
376
|
+
paths_to_update = []
|
|
377
|
+
paths_to_update.extend(sorted(derivs_path.glob(f"sub-{subject}*")))
|
|
378
|
+
paths_to_update.extend(sorted(derivs_path.glob(f"sourcedata/*/sub-{subject}*")))
|
|
379
|
+
for out_path in paths_to_update:
|
|
380
|
+
# update file permissions for preprocessing outputs
|
|
381
|
+
update_permissions(
|
|
382
|
+
permissions=flags.file_permissions,
|
|
383
|
+
path=out_path,
|
|
384
|
+
recursive=True,
|
|
385
|
+
group=flags.permissions_group
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# check other top-level files
|
|
389
|
+
other_paths = [derivs_path/"dataset_description.json", derivs_path/"logs"]
|
|
390
|
+
for op in other_paths:
|
|
391
|
+
if op.exists() and (op.stat().st_uid == flags.uid):
|
|
392
|
+
update_permissions(
|
|
393
|
+
permissions=flags.file_permissions,
|
|
394
|
+
path=op,
|
|
395
|
+
recursive=True,
|
|
396
|
+
group=flags.permissions_group
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# update file permisions for working directory files
|
|
400
|
+
update_permissions(
|
|
401
|
+
permissions=flags.file_permissions,
|
|
402
|
+
path=work_path,
|
|
403
|
+
recursive=True,
|
|
404
|
+
group=flags.permissions_group,
|
|
405
|
+
quiet=True
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if not preproc_success:
|
|
409
|
+
exit_program_early(f"Program '{preproc_title}' has run into an error.")
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import logging
|
|
5
|
-
from
|
|
5
|
+
from sre_constants import SUCCESS
|
|
6
|
+
from .utils import exit_program_early, make_option, prepare_subprocess_logging, flags, debug_logging, log_linebreak, run_subprocess, update_permissions
|
|
6
7
|
import shutil
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
@@ -59,9 +60,6 @@ def run_bibsnet(subject:str,
|
|
|
59
60
|
elif not bibsnet_image.exists():
|
|
60
61
|
exit_program_early(f"BIBSnet image path {derivs_path} does not exist.")
|
|
61
62
|
|
|
62
|
-
# uid = Popen(["id", "-u"], stdout=PIPE).stdout.read().decode("utf-8").strip()
|
|
63
|
-
# gid = Popen(["id", "-g"], stdout=PIPE).stdout.read().decode("utf-8").strip()
|
|
64
|
-
|
|
65
63
|
bids_mount = "/data"
|
|
66
64
|
derivs_mount = "/out"
|
|
67
65
|
work_mount = "/work"
|
|
@@ -75,16 +73,18 @@ def run_bibsnet(subject:str,
|
|
|
75
73
|
-w {work_mount} --verbose
|
|
76
74
|
{option_chain}"""
|
|
77
75
|
|
|
76
|
+
success = True
|
|
78
77
|
try:
|
|
79
78
|
run_subprocess(bibsnet_command, title="BIBSnet")
|
|
80
|
-
except
|
|
79
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
81
80
|
prepare_subprocess_logging(logger, stop=True)
|
|
82
81
|
logger.exception(e, stack_info=True)
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
success = False
|
|
83
|
+
|
|
85
84
|
if not flags.debug:
|
|
86
85
|
clean_up()
|
|
87
86
|
|
|
87
|
+
return success
|
|
88
88
|
|
|
89
89
|
@debug_logging
|
|
90
90
|
def segment_anatomical(subject:str,
|
|
@@ -116,7 +116,7 @@ def segment_anatomical(subject:str,
|
|
|
116
116
|
"""
|
|
117
117
|
option_chain = " ".join([make_option(v, key=k, delimeter=" ", convert_underscore=True) for k,v in kwargs.items()])
|
|
118
118
|
|
|
119
|
-
run_bibsnet(subject=subject,
|
|
119
|
+
segment_success = run_bibsnet(subject=subject,
|
|
120
120
|
session=session,
|
|
121
121
|
bids_path=bids_path,
|
|
122
122
|
derivs_path=derivs_path,
|
|
@@ -124,5 +124,29 @@ def segment_anatomical(subject:str,
|
|
|
124
124
|
bibsnet_image=bibsnet_image,
|
|
125
125
|
option_chain=option_chain,
|
|
126
126
|
remove_work_folder=remove_work_folder)
|
|
127
|
+
|
|
128
|
+
paths_to_update = sorted(derivs_path.glob(f"bibsnet/sub-{subject}*"))
|
|
129
|
+
paths_to_update.extend(sorted(work_path.glob(f"*/sub-{subject}*")))
|
|
130
|
+
for out_path in paths_to_update:
|
|
131
|
+
# update file permissions for segmentation outputs and working files
|
|
132
|
+
update_permissions(
|
|
133
|
+
permissions=flags.file_permissions,
|
|
134
|
+
path=out_path,
|
|
135
|
+
recursive=True,
|
|
136
|
+
group=flags.permissions_group)
|
|
137
|
+
|
|
138
|
+
# check other top-level files
|
|
139
|
+
other_paths = [derivs_path/"dataset_description.json"]
|
|
140
|
+
for op in other_paths:
|
|
141
|
+
if op.exists() and (op.stat().st_uid == flags.uid):
|
|
142
|
+
update_permissions(
|
|
143
|
+
permissions=flags.file_permissions,
|
|
144
|
+
path=op,
|
|
145
|
+
recursive=True,
|
|
146
|
+
group=flags.permissions_group
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if not segment_success:
|
|
150
|
+
exit_program_early(f"Program 'BIBSnet' has run into an error.")
|
|
127
151
|
|
|
128
152
|
|
|
@@ -239,20 +239,21 @@ def prepare_subprocess_logging(this_logger,
|
|
|
239
239
|
|
|
240
240
|
|
|
241
241
|
@debug_logging
|
|
242
|
-
def run_subprocess(cmd: str, title: str):
|
|
242
|
+
def run_subprocess(cmd: str, title: str, log_output=True):
|
|
243
243
|
split_cmd = shlex.split(cmd)
|
|
244
244
|
logger.info(f"running '{title}' with the following command: \n{' '.join(split_cmd)}\n")
|
|
245
245
|
prepare_subprocess_logging(logger)
|
|
246
246
|
with Popen(split_cmd, stdout=PIPE, stderr=PIPE) as p:
|
|
247
247
|
while p.poll() == None:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
if log_output:
|
|
249
|
+
for line in p.stdout:
|
|
250
|
+
logger.info(line.decode("utf-8", "ignore"))
|
|
251
|
+
for line in p.stderr:
|
|
252
|
+
logger.info(line.decode("utf-8", "ignore"))
|
|
252
253
|
prepare_subprocess_logging(logger, stop=True)
|
|
253
254
|
p.kill()
|
|
254
|
-
if p.poll() != 0:
|
|
255
|
-
raise RuntimeError(f"process -'{title}'- has ended with a non-zero exit code
|
|
255
|
+
if (ecode:=p.poll()) != 0:
|
|
256
|
+
raise RuntimeError(f"process -'{title}'- has ended with a non-zero exit code <{ecode}>.")
|
|
256
257
|
|
|
257
258
|
|
|
258
259
|
def log_linebreak():
|
|
@@ -389,3 +390,30 @@ def parcellate_dtseries(dtseries_path: Path,
|
|
|
389
390
|
exit_program_early(f"Program '{title}' has run into an error.")
|
|
390
391
|
|
|
391
392
|
return ptseries_path
|
|
393
|
+
|
|
394
|
+
def validate_permissions_string(permissions:str):
|
|
395
|
+
valid = True
|
|
396
|
+
if (len(permissions) != 3) or not permissions.isnumeric():
|
|
397
|
+
valid = False
|
|
398
|
+
if valid:
|
|
399
|
+
for character in permissions:
|
|
400
|
+
if int(character) > 7 or int(character) < 0:
|
|
401
|
+
valid = False
|
|
402
|
+
|
|
403
|
+
if not valid:
|
|
404
|
+
raise ValueError(f"Did not receive a valid permissions string: <{permissions}>")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def update_permissions(permissions:str, path:Path, recursive=False, group=None, quiet=False):
|
|
408
|
+
validate_permissions_string(permissions)
|
|
409
|
+
if not path.exists():
|
|
410
|
+
if quiet:
|
|
411
|
+
return
|
|
412
|
+
raise ValueError(f"Did not receive a valid path: <{path}>")
|
|
413
|
+
|
|
414
|
+
chmod_cmd = f"chmod {'-R' if recursive else ''} {permissions} {path.resolve()}"
|
|
415
|
+
run_subprocess(cmd=chmod_cmd, title="chmod", log_output=False)
|
|
416
|
+
|
|
417
|
+
if group is not None:
|
|
418
|
+
chgrp_cmd = f"chgrp {'-R' if recursive else ''} {group} {path.resolve()}"
|
|
419
|
+
run_subprocess(cmd=chgrp_cmd, title="chgrp", log_output=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|