oceanproc 1.2.2__tar.gz → 1.3.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.
- {oceanproc-1.2.2 → oceanproc-1.3.1}/PKG-INFO +1 -1
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/bids_wrapper.py +40 -18
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/events_long.py +6 -1
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/group_series.py +8 -34
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/ocean_proc.py +54 -27
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/preprocessing_wrapper.py +51 -15
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/segmentation_wrapper.py +33 -8
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/utils.py +38 -7
- {oceanproc-1.2.2 → oceanproc-1.3.1}/pyproject.toml +1 -1
- {oceanproc-1.2.2 → oceanproc-1.3.1}/README.md +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/firstlevel/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/firstlevel/run_glm.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/firstlevel/tests/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/firstlevel/tests/test_run_glm.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/grouplevel/__init__.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/grouplevel/computations.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/grouplevel/design_mat_utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/grouplevel/dscalar_utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/oceanproc/grouplevel/utils.py +0 -0
- {oceanproc-1.2.2 → oceanproc-1.3.1}/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,10 +103,9 @@ 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.1
|
|
106
|
+
parser.add_argument("--version", action="version", version='%(prog)s v1.3.1')
|
|
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
|
-
parser.register("type", "Full-Path", lambda p: Path(p).resolve())
|
|
81
109
|
|
|
82
110
|
session_args.add_argument("--subject", "-su", required=True,
|
|
83
111
|
help="The identifier of the subject to preprocess")
|
|
@@ -138,7 +166,7 @@ def main():
|
|
|
138
166
|
config_args.add_argument("--dscans_path", type=ExistingDir,
|
|
139
167
|
help="""The path to the directory containing 'dscans' files for inserting dummy scans into bold runs. Must be named exactly as corresponding bold image except with 'dscans' as
|
|
140
168
|
the suffix and '.tsv' as the extension. Contents of the file must be a column, titled 'dummy_scan', with ones and zeros. Ones denote which frames should be dummy scans.""")
|
|
141
|
-
config_args.add_argument("--precomputed_derivatives", "-pd", type=
|
|
169
|
+
config_args.add_argument("--precomputed_derivatives", "-pd", type=ExistingDir, dest="derivatives", nargs="+",
|
|
142
170
|
help="A list of paths to any BIDS-style precomputed derivatives that should be used in preprocessing. (Ex. /path/to/bibsnet)")
|
|
143
171
|
config_args.add_argument("--work_dir", "-w", type=ExistingDir, required=True,
|
|
144
172
|
help="The path to the working directory used to store intermediate files")
|
|
@@ -155,19 +183,16 @@ def main():
|
|
|
155
183
|
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
184
|
config_args.add_argument("--bibsnet_work", "-bw", type=ExistingDir,
|
|
157
185
|
help="The path to the working directory used to store intermediate files for BIBSnet")
|
|
186
|
+
config_args.add_argument("--permissions_group", "-g", type=ValidGroup, default=default_user_group,
|
|
187
|
+
help=f"The user group that should own all of the created files. (Default for the current user is '{default_user_group}')")
|
|
188
|
+
config_args.add_argument("--file_permissions", "-fp", type=ValidPermissions, default=default_file_permissions,
|
|
189
|
+
help=f"The file permissions that all output files should have, represented in numeric format. (Default permissions is '{default_file_permissions}')")
|
|
158
190
|
args, unknown_args = parser.parse_known_args()
|
|
159
191
|
|
|
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
|
|
192
|
+
if args.infant:
|
|
193
|
+
flags.infant = True
|
|
194
|
+
|
|
195
|
+
defaults = infant_defaults if flags.infant else adult_defaults
|
|
171
196
|
for k,v in defaults.__dict__.items():
|
|
172
197
|
if k in args.__dict__ and args.__dict__[k] is None:
|
|
173
198
|
args.__dict__[k] = v
|
|
@@ -175,15 +200,12 @@ def main():
|
|
|
175
200
|
unknown_args = extract_options(unknown_args)
|
|
176
201
|
|
|
177
202
|
bibsnet_path = (args.derivs_path / "bibsnet").resolve()
|
|
178
|
-
if
|
|
203
|
+
if flags.infant and args.bibsnet_image_path:
|
|
179
204
|
if args.derivatives is None:
|
|
180
205
|
args.derivatives = [bibsnet_path]
|
|
181
206
|
elif bibsnet_path not in args.derivatives:
|
|
182
207
|
args.derivatives.append(bibsnet_path)
|
|
183
208
|
|
|
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
209
|
if args.longitudinal:
|
|
188
210
|
for lg_group in args.longitudinal:
|
|
189
211
|
if not lg_group[0].is_dir():
|
|
@@ -195,11 +217,10 @@ def main():
|
|
|
195
217
|
parser.error(f"usability file must be a .xml or .json file, and it must exist: {lg_group[1]}")
|
|
196
218
|
else:
|
|
197
219
|
parser.error(f"longitudinal argument cannot have more than 2 elements: {lg_group}")
|
|
220
|
+
flags.longitudinal = True
|
|
198
221
|
|
|
199
222
|
preproc_image = f"{defaults.image_name}:{args.image_version}"
|
|
200
223
|
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
224
|
flags.apptainer = True
|
|
204
225
|
preproc_image = args.preproc_image_path
|
|
205
226
|
|
|
@@ -212,6 +233,11 @@ def main():
|
|
|
212
233
|
if args.debug_mode:
|
|
213
234
|
flags.debug = True
|
|
214
235
|
logger.setLevel(logging.DEBUG)
|
|
236
|
+
|
|
237
|
+
flags.file_permissions = args.file_permissions
|
|
238
|
+
flags.permissions_group = args.permissions_group
|
|
239
|
+
flags.gid = grp.getgrnam(flags.permissions_group).gr_gid
|
|
240
|
+
flags.uid = os.getuid()
|
|
215
241
|
|
|
216
242
|
logger.info("Starting oceanproc...")
|
|
217
243
|
logger.info(f"Log will be stored at {log_path}")
|
|
@@ -222,14 +248,11 @@ def main():
|
|
|
222
248
|
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
249
|
logger.info(f"####### Exporting Configuration Arguments to: '{args.export_args}' #######")
|
|
224
250
|
export_args_to_file(args, config_args, args.export_args, extra_args=unknown_args)
|
|
251
|
+
update_permissions(permissions=flags.file_permissions, path=args.export_args, group=flags.permissions_group)
|
|
225
252
|
except Exception as e:
|
|
226
253
|
logger.exception(e)
|
|
227
254
|
exit_program_early(e)
|
|
228
255
|
|
|
229
|
-
if args.longitudinal:
|
|
230
|
-
flags.longitudinal = True
|
|
231
|
-
if args.infant:
|
|
232
|
-
flags.infant = True
|
|
233
256
|
|
|
234
257
|
# log the input arguments
|
|
235
258
|
for k,v in (dict(args._get_kwargs())).items():
|
|
@@ -275,6 +298,11 @@ def main():
|
|
|
275
298
|
with open(bids_path / ".bidsignore", "r") as f:
|
|
276
299
|
ignore_list.extend(f.readlines())
|
|
277
300
|
ignore_list = list(set(ignore_list))
|
|
301
|
+
other_sub_paths = [
|
|
302
|
+
p.name for p in bids_path.glob("sub-*")
|
|
303
|
+
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
|
|
304
|
+
]
|
|
305
|
+
ignore_list.extend(other_sub_paths)
|
|
278
306
|
bids_layout = BIDSLayout(args.bids_path, validate=False, indexer=BIDSLayoutIndexer(ignore=ignore_list, validate=False))
|
|
279
307
|
|
|
280
308
|
##### Remove the scans marked as 'unusable' #####
|
|
@@ -299,7 +327,6 @@ def main():
|
|
|
299
327
|
subject=args.subject,
|
|
300
328
|
session=args.session,
|
|
301
329
|
layout=bids_layout,
|
|
302
|
-
# xml_path=xml_data_path,
|
|
303
330
|
allow_uneven_fmap_groups=args.allow_uneven_fmap_groups,
|
|
304
331
|
extract_best_fmap=args.extract_best_fmap
|
|
305
332
|
)
|
|
@@ -359,7 +386,7 @@ def main():
|
|
|
359
386
|
)
|
|
360
387
|
|
|
361
388
|
##### Create long formatted event files #####
|
|
362
|
-
if not args.skip_event_files:
|
|
389
|
+
if not args.skip_event_files and not args.anat_only:
|
|
363
390
|
create_events_and_confounds(
|
|
364
391
|
bids_path=args.bids_path,
|
|
365
392
|
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,45 @@ 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
|
+
if out_path.exists():
|
|
381
|
+
# update file permissions for preprocessing outputs
|
|
382
|
+
update_permissions(
|
|
383
|
+
permissions=flags.file_permissions,
|
|
384
|
+
path=out_path,
|
|
385
|
+
recursive=True,
|
|
386
|
+
group=flags.permissions_group
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# check other top-level files
|
|
390
|
+
other_paths = [derivs_path/"dataset_description.json", derivs_path/"logs"]
|
|
391
|
+
for op in other_paths:
|
|
392
|
+
if op.exists() and (op.stat().st_uid == flags.uid):
|
|
393
|
+
update_permissions(
|
|
394
|
+
permissions=flags.file_permissions,
|
|
395
|
+
path=op,
|
|
396
|
+
recursive=True,
|
|
397
|
+
group=flags.permissions_group
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# update file permisions for working directory files
|
|
401
|
+
update_permissions(
|
|
402
|
+
permissions=flags.file_permissions,
|
|
403
|
+
path=work_path,
|
|
404
|
+
recursive=True,
|
|
405
|
+
group=flags.permissions_group,
|
|
406
|
+
quiet=True
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if not preproc_success:
|
|
410
|
+
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,30 @@ 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
|
+
if out_path.exists():
|
|
132
|
+
# update file permissions for segmentation outputs and working files
|
|
133
|
+
update_permissions(
|
|
134
|
+
permissions=flags.file_permissions,
|
|
135
|
+
path=out_path,
|
|
136
|
+
recursive=True,
|
|
137
|
+
group=flags.permissions_group)
|
|
138
|
+
|
|
139
|
+
# check other top-level files
|
|
140
|
+
other_paths = [derivs_path/"dataset_description.json"]
|
|
141
|
+
for op in other_paths:
|
|
142
|
+
if op.exists() and (op.stat().st_uid == flags.uid):
|
|
143
|
+
update_permissions(
|
|
144
|
+
permissions=flags.file_permissions,
|
|
145
|
+
path=op,
|
|
146
|
+
recursive=True,
|
|
147
|
+
group=flags.permissions_group
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if not segment_success:
|
|
151
|
+
exit_program_early(f"Program 'BIBSnet' has run into an error.")
|
|
127
152
|
|
|
128
153
|
|
|
@@ -239,20 +239,24 @@ 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, quiet=False):
|
|
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
|
+
ecode = 0
|
|
246
247
|
with Popen(split_cmd, stdout=PIPE, stderr=PIPE) as p:
|
|
247
248
|
while p.poll() == None:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
if log_output:
|
|
250
|
+
for line in p.stdout:
|
|
251
|
+
logger.info(line.decode("utf-8", "ignore"))
|
|
252
|
+
for line in p.stderr:
|
|
253
|
+
logger.info(line.decode("utf-8", "ignore"))
|
|
252
254
|
prepare_subprocess_logging(logger, stop=True)
|
|
255
|
+
ecode = p.poll()
|
|
253
256
|
p.kill()
|
|
254
|
-
if
|
|
255
|
-
raise RuntimeError(f"process -'{title}'- has ended with a non-zero exit code
|
|
257
|
+
if (ecode != 0) and (not quiet):
|
|
258
|
+
raise RuntimeError(f"process -'{title}'- has ended with a non-zero exit code <{ecode}>.")
|
|
259
|
+
return ecode
|
|
256
260
|
|
|
257
261
|
|
|
258
262
|
def log_linebreak():
|
|
@@ -389,3 +393,30 @@ def parcellate_dtseries(dtseries_path: Path,
|
|
|
389
393
|
exit_program_early(f"Program '{title}' has run into an error.")
|
|
390
394
|
|
|
391
395
|
return ptseries_path
|
|
396
|
+
|
|
397
|
+
def validate_permissions_string(permissions:str):
|
|
398
|
+
valid = True
|
|
399
|
+
if (len(permissions) != 3) or not permissions.isnumeric():
|
|
400
|
+
valid = False
|
|
401
|
+
if valid:
|
|
402
|
+
for character in permissions:
|
|
403
|
+
if int(character) > 7 or int(character) < 0:
|
|
404
|
+
valid = False
|
|
405
|
+
|
|
406
|
+
if not valid:
|
|
407
|
+
raise ValueError(f"Did not receive a valid permissions string: <{permissions}>")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def update_permissions(permissions:str, path:Path, recursive=False, group=None, quiet=False):
|
|
411
|
+
validate_permissions_string(permissions)
|
|
412
|
+
if not path.exists():
|
|
413
|
+
if quiet:
|
|
414
|
+
return
|
|
415
|
+
raise ValueError(f"Did not receive a valid path: <{path}>")
|
|
416
|
+
|
|
417
|
+
chmod_cmd = f"chmod {'-R' if recursive else ''} {permissions} {path.resolve()}"
|
|
418
|
+
run_subprocess(cmd=chmod_cmd, title="chmod", log_output=False, quiet=True)
|
|
419
|
+
|
|
420
|
+
if group is not None:
|
|
421
|
+
chgrp_cmd = f"chgrp {'-R' if recursive else ''} {group} {path.resolve()}"
|
|
422
|
+
run_subprocess(cmd=chgrp_cmd, title="chgrp", log_output=False, quiet=True)
|
|
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
|