nifti2bids 0.0.9__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.
Potentially problematic release.
This version of nifti2bids might be problematic. Click here for more details.
- nifti2bids-0.0.9/LICENSE +21 -0
- nifti2bids-0.0.9/PKG-INFO +67 -0
- nifti2bids-0.0.9/README.md +29 -0
- nifti2bids-0.0.9/nifti2bids/__init__.py +1 -0
- nifti2bids-0.0.9/nifti2bids/_decorators.py +53 -0
- nifti2bids-0.0.9/nifti2bids/_exceptions.py +51 -0
- nifti2bids-0.0.9/nifti2bids/_helpers.py +6 -0
- nifti2bids-0.0.9/nifti2bids/io.py +287 -0
- nifti2bids-0.0.9/nifti2bids/logger.py +86 -0
- nifti2bids-0.0.9/nifti2bids/simulate.py +59 -0
- nifti2bids-0.0.9/nifti2bids/utils.py +547 -0
- nifti2bids-0.0.9/nifti2bids.egg-info/PKG-INFO +67 -0
- nifti2bids-0.0.9/nifti2bids.egg-info/SOURCES.txt +21 -0
- nifti2bids-0.0.9/nifti2bids.egg-info/dependency_links.txt +1 -0
- nifti2bids-0.0.9/nifti2bids.egg-info/requires.txt +12 -0
- nifti2bids-0.0.9/nifti2bids.egg-info/top_level.txt +1 -0
- nifti2bids-0.0.9/pyproject.toml +78 -0
- nifti2bids-0.0.9/setup.cfg +4 -0
- nifti2bids-0.0.9/tests/test_decorators.py +19 -0
- nifti2bids-0.0.9/tests/test_io.py +99 -0
- nifti2bids-0.0.9/tests/test_logger.py +30 -0
- nifti2bids-0.0.9/tests/test_simulate.py +27 -0
- nifti2bids-0.0.9/tests/test_utils.py +202 -0
nifti2bids-0.0.9/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Donisha Smith
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nifti2bids
|
|
3
|
+
Version: 0.0.9
|
|
4
|
+
Summary: Post-hoc BIDS conversion toolkit for NIfTI datasets without original DICOMs.
|
|
5
|
+
Author-email: Donisha Smith <dsmit420@jhu.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://nifti2bids.readthedocs.io
|
|
8
|
+
Project-URL: Github, https://github.com/donishadsmith/nifti2bids
|
|
9
|
+
Project-URL: Issues, https://github.com/donishadsmith/nifti2bids/issues
|
|
10
|
+
Project-URL: Changelog, https://nifti2bids.readthedocs.io/en/stable/changelog.html
|
|
11
|
+
Keywords: python,neuroimaging,fMRI,MRI,BIDS,NIfTI
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
23
|
+
Classifier: Development Status :: 3 - Alpha
|
|
24
|
+
Requires-Python: >=3.10.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy>=1.26.3
|
|
28
|
+
Requires-Dist: nibabel>=5.0.0
|
|
29
|
+
Requires-Dist: rich>=14.2.0
|
|
30
|
+
Requires-Dist: nilearn>=0.10.4
|
|
31
|
+
Requires-Dist: pandas>=2.1.0
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: nifti2bids[test]; extra == "all"
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: pytest; extra == "test"
|
|
36
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Nifti2Bids
|
|
40
|
+
|
|
41
|
+
[](https://pypi.python.org/pypi/nifti2bids/)
|
|
42
|
+
[](https://pypi.python.org/pypi/nifti2bids/)
|
|
43
|
+
[](https://github.com/donishadsmith/nifti2bids)
|
|
44
|
+
[](https://opensource.org/licenses/MIT)
|
|
45
|
+
[](https://github.com/donishadsmith/nifti2bids/actions/workflows/testing.yaml)
|
|
46
|
+
[](https://codecov.io/gh/donishadsmith/nifti2bids)
|
|
47
|
+
[](https://github.com/psf/black)
|
|
48
|
+
[](http://nifti2bids.readthedocs.io/en/latest/?badge=latest)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
A toolkit for post hoc BIDS-ification NIfTI datasets. Includes utilities for metadata extraction, file renaming, and JSON sidecar generation, designed primarily for datasets where the original DICOMs are unavailable.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
To install ``nifti2bids`` use one of the following methods:
|
|
55
|
+
|
|
56
|
+
### Standard Installation
|
|
57
|
+
```bash
|
|
58
|
+
pip install nifti2bids
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Development Version
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone --depth 1 https://github.com/donishadsmith/nifti2bids/
|
|
65
|
+
cd nifti2bids
|
|
66
|
+
pip install -e .
|
|
67
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Nifti2Bids
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/nifti2bids/)
|
|
4
|
+
[](https://pypi.python.org/pypi/nifti2bids/)
|
|
5
|
+
[](https://github.com/donishadsmith/nifti2bids)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/donishadsmith/nifti2bids/actions/workflows/testing.yaml)
|
|
8
|
+
[](https://codecov.io/gh/donishadsmith/nifti2bids)
|
|
9
|
+
[](https://github.com/psf/black)
|
|
10
|
+
[](http://nifti2bids.readthedocs.io/en/latest/?badge=latest)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
A toolkit for post hoc BIDS-ification NIfTI datasets. Includes utilities for metadata extraction, file renaming, and JSON sidecar generation, designed primarily for datasets where the original DICOMs are unavailable.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
To install ``nifti2bids`` use one of the following methods:
|
|
17
|
+
|
|
18
|
+
### Standard Installation
|
|
19
|
+
```bash
|
|
20
|
+
pip install nifti2bids
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Development Version
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone --depth 1 https://github.com/donishadsmith/nifti2bids/
|
|
27
|
+
cd nifti2bids
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.9"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Decorator functions."""
|
|
2
|
+
|
|
3
|
+
import functools, inspect
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from ._helpers import list_to_str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_all_none(parameter_names: list[str]) -> Callable:
|
|
11
|
+
"""
|
|
12
|
+
Checks if specific parameters are assigned ``None``.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
parameter_names: :obj:`list[str]`
|
|
17
|
+
List of parameter names to check.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
Callable
|
|
22
|
+
Decorator function wrapping target function.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def decorator(func: Callable) -> Callable:
|
|
26
|
+
signature = inspect.signature(func)
|
|
27
|
+
if invalid_params := [
|
|
28
|
+
param
|
|
29
|
+
for param in parameter_names
|
|
30
|
+
if param not in signature.parameters.keys()
|
|
31
|
+
]:
|
|
32
|
+
raise NameError(
|
|
33
|
+
"Error in ``parameter_names`` of decorator. The following "
|
|
34
|
+
f"parameters are not in the signature of '{func.__name__}': "
|
|
35
|
+
f"{list_to_str(invalid_params)}."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@functools.wraps(func)
|
|
39
|
+
def wrapper(*args: Any, **kwargs: Any) -> Callable:
|
|
40
|
+
bound_args = signature.bind(*args, **kwargs)
|
|
41
|
+
bound_args.apply_defaults()
|
|
42
|
+
all_param_values = [bound_args.arguments[name] for name in parameter_names]
|
|
43
|
+
if all(value is None for value in all_param_values):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"All of the following arguments cannot be None, "
|
|
46
|
+
f"one must be specified: {list_to_str(parameter_names)}."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return func(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
return decorator
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Custom exceptions."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SliceAxisError(Exception):
|
|
7
|
+
"""
|
|
8
|
+
Incorrect slice axis.
|
|
9
|
+
|
|
10
|
+
Raised when the number of slices does not match "slice_end" plus one.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
slice_axis: :obj:`Literal["x", "y", "z"]`
|
|
15
|
+
The specified slice dimension.
|
|
16
|
+
|
|
17
|
+
n_slices: :obj:`int`
|
|
18
|
+
The number of slices from the specified ``slice_axis``.
|
|
19
|
+
|
|
20
|
+
slice_end: :obj:`int`
|
|
21
|
+
The number of slices specified by "slice_end" in the NIfTI header.
|
|
22
|
+
|
|
23
|
+
message: :obj:`str` or :obj:`None`:
|
|
24
|
+
The error message. If None, a default error message is used.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
slice_axis: Literal["x", "y", "z"],
|
|
30
|
+
n_slices: int,
|
|
31
|
+
slice_end: int,
|
|
32
|
+
message: Optional[str] = None,
|
|
33
|
+
):
|
|
34
|
+
if not message:
|
|
35
|
+
self.message = (
|
|
36
|
+
"Incorrect slice axis. Number of slices for "
|
|
37
|
+
f"{slice_axis} dimension is {n_slices} but "
|
|
38
|
+
f"'slice_end' in NIfTI header is {slice_end}."
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
self.message = message
|
|
42
|
+
|
|
43
|
+
super().__init__(self.message)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DataDimensionError(Exception):
|
|
47
|
+
"""
|
|
48
|
+
Incorrect data dimensionality.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
pass
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Module for input/output operations."""
|
|
2
|
+
|
|
3
|
+
import glob, json, os, shutil
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import nibabel as nib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_nifti(
|
|
10
|
+
nifti_file_or_img: str | nib.nifti1.Nifti1Image,
|
|
11
|
+
) -> nib.nifti1.Nifti1Image:
|
|
12
|
+
"""
|
|
13
|
+
Loads a NIfTI image.
|
|
14
|
+
|
|
15
|
+
Loads NIfTI image when not a ``Nifti1Image`` object or
|
|
16
|
+
returns the image if already loaded in.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
21
|
+
Path to the NIfTI file or a NIfTI image.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
nib.nifti1.Nifti1Image
|
|
26
|
+
The loaded in NIfTI image.
|
|
27
|
+
"""
|
|
28
|
+
nifti_img = (
|
|
29
|
+
nifti_file_or_img
|
|
30
|
+
if isinstance(nifti_file_or_img, nib.nifti1.Nifti1Image)
|
|
31
|
+
else nib.load(nifti_file_or_img)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return nifti_img
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def compress_image(nifti_file: str, remove_src_file: bool = False) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Compresses a ".nii" image to a ".nii.gz" image.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
nifti_file: :obj:`str`
|
|
44
|
+
Path to the NIfTI image.
|
|
45
|
+
|
|
46
|
+
remove_src_file: :obj:`bool`
|
|
47
|
+
Deletes the original source image file.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
None
|
|
52
|
+
"""
|
|
53
|
+
img = nib.load(nifti_file)
|
|
54
|
+
nib.save(img, nifti_file.replace(".nii", ".nii.gz"))
|
|
55
|
+
|
|
56
|
+
if remove_src_file:
|
|
57
|
+
os.remove(nifti_file)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def glob_contents(src_dir: str, pattern: str) -> list[str]:
|
|
61
|
+
"""
|
|
62
|
+
Use glob to get contents with specific patterns.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
src_dir: :obj:`str`
|
|
67
|
+
The source directory.
|
|
68
|
+
|
|
69
|
+
ext: :obj:`str`
|
|
70
|
+
The extension.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
list[str]
|
|
75
|
+
List of contents with the pattern specified by ``pattern``.
|
|
76
|
+
"""
|
|
77
|
+
return glob.glob(os.path.join(src_dir, f"*{pattern}"))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_nifti_header(nifti_file_or_img):
|
|
81
|
+
"""
|
|
82
|
+
Get header from a NIfTI image.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
87
|
+
Path to the NIfTI file or a NIfTI image.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
nib.nifti1.Nifti1Image
|
|
92
|
+
The header from a NIfTI image.
|
|
93
|
+
"""
|
|
94
|
+
return load_nifti(nifti_file_or_img).header
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_nifti_affine(nifti_file_or_img):
|
|
98
|
+
"""
|
|
99
|
+
Get the affine matrix from a NIfTI image.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
104
|
+
Path to the NIfTI file or a NIfTI image.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
nib.nifti1.Nifti1Image
|
|
109
|
+
The header from a NIfTI image.
|
|
110
|
+
"""
|
|
111
|
+
return load_nifti(nifti_file_or_img).affine
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _copy_file(src_file: str, dst_file: str, remove_src_file: bool) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Copy a file and optionally remove the source file.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
src_file: :obj:`str`
|
|
121
|
+
The source file to be copied
|
|
122
|
+
|
|
123
|
+
dst_file: :obj:`str`
|
|
124
|
+
The new destination file.
|
|
125
|
+
|
|
126
|
+
remove_src_file: :obj:`bool`
|
|
127
|
+
Delete the source file if True.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
None
|
|
132
|
+
"""
|
|
133
|
+
shutil.copy(src_file, dst_file)
|
|
134
|
+
|
|
135
|
+
if remove_src_file:
|
|
136
|
+
os.remove(src_file)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def create_bids_file(
|
|
140
|
+
nifti_file: str,
|
|
141
|
+
subj_id: str | int,
|
|
142
|
+
desc: str,
|
|
143
|
+
ses_id: Optional[str | int] = None,
|
|
144
|
+
task_id: Optional[str] = None,
|
|
145
|
+
run_id: Optional[str | int] = None,
|
|
146
|
+
dst_dir: str = None,
|
|
147
|
+
remove_src_file: bool = False,
|
|
148
|
+
return_bids_filename: bool = False,
|
|
149
|
+
) -> str | None:
|
|
150
|
+
"""
|
|
151
|
+
Create a BIDS compliant filename with required and optional entities.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
nifti_file: :obj:`str`
|
|
156
|
+
Path to NIfTI image.
|
|
157
|
+
|
|
158
|
+
sub_id: :obj:`str` or :obj:`int`
|
|
159
|
+
Subject ID (i.e. 01, 101, etc).
|
|
160
|
+
|
|
161
|
+
desc: :obj:`str`
|
|
162
|
+
Description of the file (i.e., T1w, bold, etc).
|
|
163
|
+
|
|
164
|
+
ses_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
165
|
+
Session ID (i.e. 001, 1, etc). Optional entity.
|
|
166
|
+
|
|
167
|
+
ses_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
168
|
+
Session ID (i.e. 001, 1, etc). Optional entity.
|
|
169
|
+
|
|
170
|
+
task_id: :obj:`str` or :obj:`None`, default=None
|
|
171
|
+
Task ID (i.e. flanker, n_back, etc). Optional entity.
|
|
172
|
+
|
|
173
|
+
run_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
174
|
+
Run ID (i.e. 001, 1, etc). Optional entity.
|
|
175
|
+
|
|
176
|
+
dst_dir: :obj:`str`, default=None
|
|
177
|
+
Directory name to copy the BIDS file to. If None, then the
|
|
178
|
+
BIDS file is copied to the same directory as
|
|
179
|
+
|
|
180
|
+
remove_src_file: :obj:`str`, default=False
|
|
181
|
+
Delete the source file if True.
|
|
182
|
+
|
|
183
|
+
return_bids_filename: :obj:`str`, default=False
|
|
184
|
+
Returns the full BIDS filename if True.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
None or str
|
|
189
|
+
If ``return_bids_filename`` is True, then the BIDS filename is
|
|
190
|
+
returned.
|
|
191
|
+
|
|
192
|
+
Note
|
|
193
|
+
----
|
|
194
|
+
There are additional entities that can be used that are
|
|
195
|
+
not included in this function.
|
|
196
|
+
"""
|
|
197
|
+
bids_filename = f"sub-{subj_id}_ses-{ses_id}_task-{task_id}_" f"run-{run_id}_{desc}"
|
|
198
|
+
bids_filename = _strip_none_entities(bids_filename)
|
|
199
|
+
|
|
200
|
+
ext = f"{nifti_file.partition('.')[-1]}"
|
|
201
|
+
bids_filename += f"{ext}"
|
|
202
|
+
bids_filename = (
|
|
203
|
+
os.path.join(os.path.dirname(nifti_file), bids_filename)
|
|
204
|
+
if dst_dir is None
|
|
205
|
+
else os.path.join(dst_dir, bids_filename)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
_copy_file(nifti_file, bids_filename, remove_src_file)
|
|
209
|
+
|
|
210
|
+
return bids_filename if return_bids_filename else None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _strip_none_entities(bids_filename: str) -> str:
|
|
214
|
+
"""
|
|
215
|
+
Removes entities with None in a BIDS compliant filename.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
bids_filename: :obj:`str`
|
|
220
|
+
The BIDS filename.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
str
|
|
225
|
+
BIDS filename with entities ending in None removed.
|
|
226
|
+
|
|
227
|
+
Example
|
|
228
|
+
-------
|
|
229
|
+
>>> from bidsrep.io import _strip_none_entities
|
|
230
|
+
>>> bids_filename = "sub-101_ses-None_task-flanker_bold.nii.gz"
|
|
231
|
+
>>> _strip_none_entities(bids_filename)
|
|
232
|
+
"sub-101_task-flanker_bold.nii.gz"
|
|
233
|
+
"""
|
|
234
|
+
basename, _, ext = bids_filename.partition(".")
|
|
235
|
+
retained_entities = [
|
|
236
|
+
entity for entity in basename.split("_") if not entity.endswith("-None")
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
return f"{'_'.join(retained_entities)}.{ext}"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def create_dataset_description(dataset_name: str, bids_version: str = "1.0.0") -> dict:
|
|
243
|
+
"""
|
|
244
|
+
Generate a dataset description dictionary.
|
|
245
|
+
|
|
246
|
+
Creates a dictionary containing the name and BIDs version of a dataset.
|
|
247
|
+
|
|
248
|
+
.. versionadded:: 0.34.1
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
dataset_name: :obj:`str`
|
|
253
|
+
Name of the dataset.
|
|
254
|
+
|
|
255
|
+
bids_version: :obj:`str`,
|
|
256
|
+
Version of the BIDS dataset.
|
|
257
|
+
|
|
258
|
+
derivative: :obj:`bool`, default=False
|
|
259
|
+
Determines if "GeneratedBy" key is added to dictionary.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
dict
|
|
264
|
+
The dataset description dictionary
|
|
265
|
+
"""
|
|
266
|
+
return {"Name": dataset_name, "BIDSVersion": bids_version}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def save_dataset_description(dataset_description: dict[str, str], dst_dir: str) -> None:
|
|
270
|
+
"""
|
|
271
|
+
Save a dataset description dictionary.
|
|
272
|
+
|
|
273
|
+
Saves the dataset description dictionary as a file named "dataset_description.json" to the
|
|
274
|
+
directory specified by ``output_dir``.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
dataset_description: :obj:`dict`
|
|
279
|
+
The dataset description dictionary.
|
|
280
|
+
|
|
281
|
+
dst_dir: :obj:`str`
|
|
282
|
+
Path to save the JSON file to.
|
|
283
|
+
"""
|
|
284
|
+
with open(
|
|
285
|
+
os.path.join(dst_dir, "dataset_description.json"), "w", encoding="utf-8"
|
|
286
|
+
) as f:
|
|
287
|
+
json.dump(dataset_description, f)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Module for logging."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_logger(
|
|
10
|
+
logger_name: str = None, level: Optional[int] = None
|
|
11
|
+
) -> logging.Logger:
|
|
12
|
+
"""
|
|
13
|
+
Sets up the logger.
|
|
14
|
+
|
|
15
|
+
.. note::
|
|
16
|
+
Defaults to ``RichHandler`` if a module or root handler is
|
|
17
|
+
not detected.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
logger_name: :obj:`str`
|
|
22
|
+
Name of the logger to return, if None, the root logger is returned.
|
|
23
|
+
|
|
24
|
+
level: :obj:`int` or :obj:`None`
|
|
25
|
+
The logging level. If None, the logging level is not set
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
Logger
|
|
30
|
+
A ``Logger`` object.
|
|
31
|
+
"""
|
|
32
|
+
logger = logging.getLogger(logger_name)
|
|
33
|
+
if not _has_handler(logger):
|
|
34
|
+
logger = _add_default_handler(logger)
|
|
35
|
+
|
|
36
|
+
if level:
|
|
37
|
+
logger.setLevel(level)
|
|
38
|
+
|
|
39
|
+
return logger
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _has_handler(logger):
|
|
43
|
+
"""
|
|
44
|
+
Check if a handler is present.
|
|
45
|
+
|
|
46
|
+
Checks the root logger and module logger.
|
|
47
|
+
|
|
48
|
+
logger: :obj:`Logger`
|
|
49
|
+
A logging object.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
bool
|
|
54
|
+
True if a handler is present and False if no handler is present
|
|
55
|
+
"""
|
|
56
|
+
has_root_handler = logging.getLogger().hasHandlers()
|
|
57
|
+
has_module_handler = bool(logger.handlers)
|
|
58
|
+
|
|
59
|
+
return True if (has_root_handler or has_module_handler) else False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _add_default_handler(logger: logging.Logger, format: str | None = None):
|
|
63
|
+
"""
|
|
64
|
+
Add a default and format handler. Uses ``RichHandler`` as the default logger.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
logger: :obj:`Logger`
|
|
69
|
+
A logging object.
|
|
70
|
+
|
|
71
|
+
format: :obj:`str`
|
|
72
|
+
String specifying the format of the logged message.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
Logger
|
|
77
|
+
A logger object.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
format = format if format else "%(asctime)s %(name)s [%(levelname)s] %(message)s"
|
|
81
|
+
|
|
82
|
+
handler = RichHandler()
|
|
83
|
+
handler.setFormatter(logging.Formatter(format))
|
|
84
|
+
logger.addHandler(handler)
|
|
85
|
+
|
|
86
|
+
return logger
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Module for creating simulated data."""
|
|
2
|
+
|
|
3
|
+
import nibabel as nib, numpy as np
|
|
4
|
+
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def simulate_nifti_image(
|
|
9
|
+
img_shape: tuple[int, int, int] | tuple[int, int, int, int], affine: NDArray = None
|
|
10
|
+
) -> nib.Nifti1Image:
|
|
11
|
+
"""
|
|
12
|
+
Simulates a NIfTI image.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
img_shape: :obj:`tuple[int, int, int]` or :obj:`tuple[int, int, int, int]`
|
|
17
|
+
Shape of the NIfTI image.
|
|
18
|
+
|
|
19
|
+
affine: :obj:`NDArray`, default=None
|
|
20
|
+
The affine matrix.
|
|
21
|
+
|
|
22
|
+
.. important::
|
|
23
|
+
If None, creates an identity matrix.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
Nifti1Image
|
|
28
|
+
The NIfTI image with no header.
|
|
29
|
+
"""
|
|
30
|
+
if affine is None:
|
|
31
|
+
affine = create_affine(
|
|
32
|
+
xyz_diagonal_value=1, translation_vector=np.array([0, 0, 0, 1])
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return nib.Nifti1Image(np.random.rand(*img_shape), affine)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_affine(xyz_diagonal_value: int, translation_vector: NDArray) -> NDArray:
|
|
39
|
+
"""
|
|
40
|
+
Generate an 4x4 affine matrix.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
xyz_diagonal_value: :obj:`int`
|
|
45
|
+
The value assigned to the diagonal of the affine for x, y, and z.
|
|
46
|
+
|
|
47
|
+
translation_vector: :obj:`NDArray`
|
|
48
|
+
The translation vector/shift from the origin.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
NDArray
|
|
53
|
+
The affine matrix.
|
|
54
|
+
"""
|
|
55
|
+
affine = np.zeros((4, 4))
|
|
56
|
+
np.fill_diagonal(affine[:3, :3], xyz_diagonal_value)
|
|
57
|
+
affine[:, 3:] = translation_vector[:, np.newaxis]
|
|
58
|
+
|
|
59
|
+
return affine
|