nifti2bids 0.2.7__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.
- nifti2bids-0.2.7/LICENSE +21 -0
- nifti2bids-0.2.7/PKG-INFO +67 -0
- nifti2bids-0.2.7/README.md +29 -0
- nifti2bids-0.2.7/nifti2bids/__init__.py +23 -0
- nifti2bids-0.2.7/nifti2bids/_constants.py +24 -0
- nifti2bids-0.2.7/nifti2bids/_decorators.py +82 -0
- nifti2bids-0.2.7/nifti2bids/_exceptions.py +51 -0
- nifti2bids-0.2.7/nifti2bids/_helpers.py +6 -0
- nifti2bids-0.2.7/nifti2bids/bids.py +313 -0
- nifti2bids-0.2.7/nifti2bids/io.py +141 -0
- nifti2bids-0.2.7/nifti2bids/logging.py +86 -0
- nifti2bids-0.2.7/nifti2bids/metadata.py +1026 -0
- nifti2bids-0.2.7/nifti2bids/parsers.py +223 -0
- nifti2bids-0.2.7/nifti2bids/simulate.py +64 -0
- nifti2bids-0.2.7/nifti2bids.egg-info/PKG-INFO +67 -0
- nifti2bids-0.2.7/nifti2bids.egg-info/SOURCES.txt +26 -0
- nifti2bids-0.2.7/nifti2bids.egg-info/dependency_links.txt +1 -0
- nifti2bids-0.2.7/nifti2bids.egg-info/requires.txt +12 -0
- nifti2bids-0.2.7/nifti2bids.egg-info/top_level.txt +1 -0
- nifti2bids-0.2.7/pyproject.toml +78 -0
- nifti2bids-0.2.7/setup.cfg +4 -0
- nifti2bids-0.2.7/tests/test_bids.py +131 -0
- nifti2bids-0.2.7/tests/test_decorators.py +45 -0
- nifti2bids-0.2.7/tests/test_io.py +47 -0
- nifti2bids-0.2.7/tests/test_logging.py +30 -0
- nifti2bids-0.2.7/tests/test_metadata.py +393 -0
- nifti2bids-0.2.7/tests/test_parsers.py +94 -0
- nifti2bids-0.2.7/tests/test_simulate.py +25 -0
nifti2bids-0.2.7/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.2.7
|
|
4
|
+
Summary: Post-hoc BIDS conversion toolkit for unstructured NIfTI datasets.
|
|
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/stable/?badge=stable)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
A toolkit for post hoc BIDS-ification unstructured 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/stable/?badge=stable)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
A toolkit for post hoc BIDS-ification unstructured 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,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Post-hoc BIDS conversion toolkit for NIfTI datasets without original DICOMs.
|
|
3
|
+
----------------------------------------------------------------------------
|
|
4
|
+
Documentation can be found at https://nifti2bids.readthedocs.io.
|
|
5
|
+
|
|
6
|
+
Submodules
|
|
7
|
+
----------
|
|
8
|
+
bids -- Operations related to initializing and creating BIDs compliant files
|
|
9
|
+
|
|
10
|
+
io -- Generic operations related to loading NIfTI data
|
|
11
|
+
|
|
12
|
+
logging -- Set up a logger using ``RichHandler`` as the default handler if a root or
|
|
13
|
+
module specific handler is not available
|
|
14
|
+
|
|
15
|
+
metadata -- Operations related to extracting or creating metadata information from NIfTI images
|
|
16
|
+
|
|
17
|
+
parsers -- Operations related to standardizing and parsing information logs created by stimulus
|
|
18
|
+
presentation software such as Presentation
|
|
19
|
+
|
|
20
|
+
simulate -- Simulate a basic NIfTI image for testing purposes
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__version__ = "0.2.7"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for constants.
|
|
3
|
+
|
|
4
|
+
Note: The CLI command for converting edat3 files to text files can be found at the
|
|
5
|
+
following link: https://support.pstnet.com/hc/en-us/articles/360020316014-DATA-Using-E-DataAid-exe-with-Command-Line-Interpreters-25323
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
EDATAAID_PATH = r"C:\Program Files (x86)\PST\E-Prime 3.0\Program\E-DataAid.exe"
|
|
9
|
+
|
|
10
|
+
EDATAAID_CONTROL_FILE = """Inheritance=NULL
|
|
11
|
+
InFile={edat_path}
|
|
12
|
+
OutFile={dst_path}
|
|
13
|
+
ColFlags=0
|
|
14
|
+
ColNames=1
|
|
15
|
+
Comments=0
|
|
16
|
+
BegCommentLine=0
|
|
17
|
+
EndCommentLine=0
|
|
18
|
+
DataSeparator=\t
|
|
19
|
+
VarSeparator=\t
|
|
20
|
+
BegDataLine=0
|
|
21
|
+
EndDataLine=0
|
|
22
|
+
MissingData=nan
|
|
23
|
+
Unicode=0
|
|
24
|
+
"""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Decorator functions."""
|
|
2
|
+
|
|
3
|
+
import functools, inspect
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Optional
|
|
6
|
+
|
|
7
|
+
from ._helpers import list_to_str
|
|
8
|
+
from .io import get_nifti_header
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check_all_none(parameter_names: list[str]) -> Callable:
|
|
12
|
+
"""
|
|
13
|
+
Checks if specific parameters are assigned ``None``.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
parameter_names: :obj:`list[str]`
|
|
18
|
+
List of parameter names to check.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
Callable
|
|
23
|
+
Decorator function wrapping target function.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def decorator(func: Callable) -> Callable:
|
|
27
|
+
signature = inspect.signature(func)
|
|
28
|
+
if invalid_params := [
|
|
29
|
+
param
|
|
30
|
+
for param in parameter_names
|
|
31
|
+
if param not in signature.parameters.keys()
|
|
32
|
+
]:
|
|
33
|
+
raise NameError(
|
|
34
|
+
"Error in ``parameter_names`` of decorator. The following "
|
|
35
|
+
f"parameters are not in the signature of '{func.__name__}': "
|
|
36
|
+
f"{list_to_str(invalid_params)}."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@functools.wraps(func)
|
|
40
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
41
|
+
bound_args = signature.bind(*args, **kwargs)
|
|
42
|
+
bound_args.apply_defaults()
|
|
43
|
+
all_param_values = [bound_args.arguments[name] for name in parameter_names]
|
|
44
|
+
if all(value is None for value in all_param_values):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"All of the following arguments cannot be None, "
|
|
47
|
+
f"one must be specified: {list_to_str(parameter_names)}."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return func(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
return wrapper
|
|
53
|
+
|
|
54
|
+
return decorator
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_nifti(nifti_param_name: Optional[str] = None) -> Callable:
|
|
58
|
+
def decorator(func: Callable) -> Callable:
|
|
59
|
+
@functools.wraps(func)
|
|
60
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
61
|
+
bound_args = inspect.signature(func).bind(*args, **kwargs)
|
|
62
|
+
bound_args.apply_defaults()
|
|
63
|
+
|
|
64
|
+
param_name = (
|
|
65
|
+
"nifti_file_or_img" if not nifti_param_name else nifti_param_name
|
|
66
|
+
)
|
|
67
|
+
img_val = bound_args.arguments.get(param_name)
|
|
68
|
+
if img_val:
|
|
69
|
+
hdr = get_nifti_header(img_val)
|
|
70
|
+
|
|
71
|
+
# sform takes precedence over qform
|
|
72
|
+
code = "sform_code" if hdr["sform_code"] else "qform_code"
|
|
73
|
+
if hdr[code] != 1:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"The {code} is not set to 'scanner' and is not a raw NIfTI image."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return func(*args, **kwargs)
|
|
79
|
+
|
|
80
|
+
return wrapper
|
|
81
|
+
|
|
82
|
+
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,313 @@
|
|
|
1
|
+
"""Module for creating BIDS compliant files."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from nifti2bids.io import _copy_file, glob_contents
|
|
11
|
+
from nifti2bids.parsers import load_presentation_log, _convert_time
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_bids_file(
|
|
15
|
+
nifti_file: str | Path,
|
|
16
|
+
subj_id: str | int,
|
|
17
|
+
desc: str,
|
|
18
|
+
ses_id: Optional[str | int] = None,
|
|
19
|
+
task_id: Optional[str] = None,
|
|
20
|
+
run_id: Optional[str | int] = None,
|
|
21
|
+
dst_dir: str | Path = None,
|
|
22
|
+
remove_src_file: bool = False,
|
|
23
|
+
return_bids_filename: bool = False,
|
|
24
|
+
) -> Path | None:
|
|
25
|
+
"""
|
|
26
|
+
Create a BIDS compliant filename with required and optional entities.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
nifti_file: :obj:`str` or :obj:`Path`
|
|
31
|
+
Path to NIfTI image.
|
|
32
|
+
|
|
33
|
+
sub_id: :obj:`str` or :obj:`int`
|
|
34
|
+
Subject ID (i.e. 01, 101, etc).
|
|
35
|
+
|
|
36
|
+
desc: :obj:`str`
|
|
37
|
+
Description of the file (i.e., T1w, bold, etc).
|
|
38
|
+
|
|
39
|
+
ses_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
40
|
+
Session ID (i.e. 001, 1, etc). Optional entity.
|
|
41
|
+
|
|
42
|
+
ses_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
43
|
+
Session ID (i.e. 001, 1, etc). Optional entity.
|
|
44
|
+
|
|
45
|
+
task_id: :obj:`str` or :obj:`None`, default=None
|
|
46
|
+
Task ID (i.e. flanker, n_back, etc). Optional entity.
|
|
47
|
+
|
|
48
|
+
run_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
49
|
+
Run ID (i.e. 001, 1, etc). Optional entity.
|
|
50
|
+
|
|
51
|
+
dst_dir: :obj:`str`, :obj:`Path`, or :obj:`None`, default=None
|
|
52
|
+
Directory name to copy the BIDS file to. If None, then the
|
|
53
|
+
BIDS file is copied to the same directory as
|
|
54
|
+
|
|
55
|
+
remove_src_file: :obj:`str`, default=False
|
|
56
|
+
Delete the source file if True.
|
|
57
|
+
|
|
58
|
+
return_bids_filename: :obj:`str`, default=False
|
|
59
|
+
Returns the full BIDS filename if True.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
Path or None
|
|
64
|
+
If ``return_bids_filename`` is True, then the BIDS filename is
|
|
65
|
+
returned.
|
|
66
|
+
|
|
67
|
+
Note
|
|
68
|
+
----
|
|
69
|
+
There are additional entities that can be used that are
|
|
70
|
+
not included in this function.
|
|
71
|
+
"""
|
|
72
|
+
bids_filename = f"sub-{subj_id}_ses-{ses_id}_task-{task_id}_" f"run-{run_id}_{desc}"
|
|
73
|
+
bids_filename = _strip_none_entities(bids_filename)
|
|
74
|
+
|
|
75
|
+
ext = f"{str(nifti_file).partition('.')[-1]}"
|
|
76
|
+
bids_filename += f"{ext}"
|
|
77
|
+
bids_filename = (
|
|
78
|
+
Path(nifti_file).parent / bids_filename
|
|
79
|
+
if dst_dir is None
|
|
80
|
+
else Path(dst_dir) / bids_filename
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
_copy_file(nifti_file, bids_filename, remove_src_file)
|
|
84
|
+
|
|
85
|
+
return bids_filename if return_bids_filename else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _strip_none_entities(bids_filename: str | Path) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Removes entities with None in a BIDS compliant filename.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
bids_filename: :obj:`str` or :obj:`Path`
|
|
95
|
+
The BIDS filename.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
str
|
|
100
|
+
BIDS filename with entities ending in None removed.
|
|
101
|
+
|
|
102
|
+
Example
|
|
103
|
+
-------
|
|
104
|
+
>>> from bidsrep.io import _strip_none_entities
|
|
105
|
+
>>> bids_filename = "sub-101_ses-None_task-flanker_bold.nii.gz"
|
|
106
|
+
>>> _strip_none_entities(bids_filename)
|
|
107
|
+
"sub-101_task-flanker_bold.nii.gz"
|
|
108
|
+
"""
|
|
109
|
+
basename, _, ext = str(bids_filename).partition(".")
|
|
110
|
+
retained_entities = [
|
|
111
|
+
entity for entity in basename.split("_") if not entity.endswith("-None")
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
return f"{'_'.join(retained_entities)}.{ext}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_dataset_description(
|
|
118
|
+
dataset_name: str, bids_version: str = "1.0.0"
|
|
119
|
+
) -> dict[str, str]:
|
|
120
|
+
"""
|
|
121
|
+
Generate a dataset description dictionary.
|
|
122
|
+
|
|
123
|
+
Creates a dictionary containing the name and BIDs version of a dataset.
|
|
124
|
+
|
|
125
|
+
.. versionadded:: 0.34.1
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
dataset_name: :obj:`str`
|
|
130
|
+
Name of the dataset.
|
|
131
|
+
|
|
132
|
+
bids_version: :obj:`str`,
|
|
133
|
+
Version of the BIDS dataset.
|
|
134
|
+
|
|
135
|
+
derivative: :obj:`bool`, default=False
|
|
136
|
+
Determines if "GeneratedBy" key is added to dictionary.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
dict[str, str]
|
|
141
|
+
The dataset description dictionary
|
|
142
|
+
"""
|
|
143
|
+
return {"Name": dataset_name, "BIDSVersion": bids_version}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def save_dataset_description(
|
|
147
|
+
dataset_description: dict[str, str], dst_dir: str | Path
|
|
148
|
+
) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Save a dataset description dictionary.
|
|
151
|
+
|
|
152
|
+
Saves the dataset description dictionary as a file named "dataset_description.json" to the
|
|
153
|
+
directory specified by ``output_dir``.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
dataset_description: :obj:`dict`
|
|
158
|
+
The dataset description dictionary.
|
|
159
|
+
|
|
160
|
+
dst_dir: :obj:`str` or :obj:`Path`
|
|
161
|
+
Path to save the JSON file to.
|
|
162
|
+
"""
|
|
163
|
+
with open(Path(dst_dir) / "dataset_description.json", "w", encoding="utf-8") as f:
|
|
164
|
+
json.dump(dataset_description, f)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def create_participant_tsv(
|
|
168
|
+
bids_dir: str | Path, save_df: bool = False, return_df: bool = True
|
|
169
|
+
) -> pd.DataFrame | None:
|
|
170
|
+
"""
|
|
171
|
+
Creates a basic participant dataframe for the "participants.tsv" file.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
bids_dir: :obj:`str` or :obj:`Path`
|
|
176
|
+
The root of BIDS compliant directory.
|
|
177
|
+
|
|
178
|
+
save_df: :obj:`bool`, bool=False
|
|
179
|
+
Save the dataframe to the root of the BIDS compliant directory.
|
|
180
|
+
|
|
181
|
+
return_df: :obj:`str`
|
|
182
|
+
Returns dataframe if True else return None.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
pd.DataFrame or None
|
|
187
|
+
The dataframe if ``return_df`` is True.
|
|
188
|
+
"""
|
|
189
|
+
participants = [folder.name for folder in glob_contents(bids_dir, "*sub-*")]
|
|
190
|
+
df = pd.DataFrame({"participant_id": participants})
|
|
191
|
+
|
|
192
|
+
if save_df:
|
|
193
|
+
df.to_csv(Path(bids_dir) / "participants.tsv", sep="\t", index=None)
|
|
194
|
+
|
|
195
|
+
return df if return_df else None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def presentation_log_to_bids(
|
|
199
|
+
presentation_log_or_df: str | Path | pd.DataFrame,
|
|
200
|
+
convert_to_seconds: Optional[list[str]],
|
|
201
|
+
trial_types: Optional[list[str]],
|
|
202
|
+
experimental_design: Literal["block", "event"],
|
|
203
|
+
rest_block_code: Optional[list[str]] = None,
|
|
204
|
+
include_response: bool = False,
|
|
205
|
+
initial_column_headers: tuple[str] = ("Trial", "Event Type"),
|
|
206
|
+
) -> pd.DataFrame:
|
|
207
|
+
"""
|
|
208
|
+
Creates BIDs compliant events dataframe from Presentation log or dataframe.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
presentation_log_or_df: :obj:`str`, :obj:`Path`, :obj:`pd.DataFrame`
|
|
213
|
+
The presentation log as a file path or the presentation DataFrame
|
|
214
|
+
returned by :code:`nifti2bids.parsers.load_presentation_log`.
|
|
215
|
+
|
|
216
|
+
convert_to_seconds: :obj:`list[str]` or :obj:`None`, default=None
|
|
217
|
+
Convert the time resolution of the specified columns from 0.1ms to seconds.
|
|
218
|
+
|
|
219
|
+
.. note:: Recommend time resolution of the "Time" column to be converted.
|
|
220
|
+
|
|
221
|
+
trial_types: :obj:`list[str]` or :obj:`None`
|
|
222
|
+
The names of the trial types (i.e "congruentleft", "seen").
|
|
223
|
+
If None, the code will identify the trial types.
|
|
224
|
+
|
|
225
|
+
.. important::
|
|
226
|
+
``trial_types=["congruent", "incongruent"]`` will identify
|
|
227
|
+
all trial types beginning with "congruent" and "incongruent"
|
|
228
|
+
|
|
229
|
+
experiment_design: :obj:`Literal["block", "event"]
|
|
230
|
+
The experimental design. Options are "block" or "event".
|
|
231
|
+
|
|
232
|
+
.. important::
|
|
233
|
+
Duration for "block" is computed as the difference between the
|
|
234
|
+
start of the block and the start of the interstimulus time.
|
|
235
|
+
The duration of "event" is computed as the difference between
|
|
236
|
+
the event stimulus and the response.
|
|
237
|
+
|
|
238
|
+
rest_block_code: :obj:`str`, default=None
|
|
239
|
+
The name of the code for the rest block. Only used
|
|
240
|
+
when ``experiment_design`` is "block".
|
|
241
|
+
|
|
242
|
+
include_response: :obj:`bool`, default=False
|
|
243
|
+
Includes a response column. Only used when
|
|
244
|
+
``experiment_design`` is "event".
|
|
245
|
+
|
|
246
|
+
initial_column_headers: :obj:`tuple[str]`, default=("Trial", "Event Type")
|
|
247
|
+
The initial column headers for data. Only used when
|
|
248
|
+
``presentation_log_or_df`` is a file path.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
pandas.DataFrame
|
|
253
|
+
The event dataframe which includes columns specifying the onset, duration,
|
|
254
|
+
and trial type. If ``include_response`` is True and ``experimental_design``
|
|
255
|
+
is True then a "response" column is added.
|
|
256
|
+
"""
|
|
257
|
+
assert experimental_design in [
|
|
258
|
+
"event",
|
|
259
|
+
"block",
|
|
260
|
+
], "Valid inputs for ``experimental_design`` are 'event' and 'block'."
|
|
261
|
+
assert not (
|
|
262
|
+
experimental_design == "block" and rest_block_code is None
|
|
263
|
+
), "``rest_block_code` cannot be None when ``experimental_design`` is 'block'."
|
|
264
|
+
|
|
265
|
+
if not isinstance(presentation_log_or_df, pd.DataFrame):
|
|
266
|
+
presentation_df = load_presentation_log(
|
|
267
|
+
presentation_log_or_df,
|
|
268
|
+
convert_to_seconds=convert_to_seconds,
|
|
269
|
+
initial_column_headers=tuple(initial_column_headers),
|
|
270
|
+
)
|
|
271
|
+
elif convert_to_seconds:
|
|
272
|
+
presentation_df = _convert_time(
|
|
273
|
+
presentation_df, convert_to_seconds=convert_to_seconds, divisor=10000
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
presentation_df = presentation_log_or_df
|
|
277
|
+
|
|
278
|
+
if experimental_design == "block":
|
|
279
|
+
rest_block_indxs = presentation_df[
|
|
280
|
+
presentation_df["Code"].str.startswith(rest_block_code)
|
|
281
|
+
].index.tolist()
|
|
282
|
+
rest_block_indxs = np.array(rest_block_indxs)
|
|
283
|
+
|
|
284
|
+
# Get the first pulse which corresponds to scanner start time
|
|
285
|
+
scanner_start = presentation_df.loc[
|
|
286
|
+
presentation_df["Event Type"] == "Pulse", "Time"
|
|
287
|
+
].values[0]
|
|
288
|
+
events = []
|
|
289
|
+
for row_indx, row in presentation_df.iterrows():
|
|
290
|
+
onset = row["Time"] - scanner_start
|
|
291
|
+
if row["Event Type"] == "Picture" and row["Code"].startswith(
|
|
292
|
+
tuple(trial_types)
|
|
293
|
+
):
|
|
294
|
+
if experimental_design == "event":
|
|
295
|
+
trial_num = row["Trial"]
|
|
296
|
+
response_row = presentation_df[
|
|
297
|
+
(presentation_df["Trial"] == trial_num)
|
|
298
|
+
& (presentation_df["Event Type"] == "Response")
|
|
299
|
+
]
|
|
300
|
+
duration = response_row.iloc[0]["Time"] - row["Time"]
|
|
301
|
+
response = row["Stim Type"]
|
|
302
|
+
else:
|
|
303
|
+
rest_indx = rest_block_indxs[rest_block_indxs > row_indx][0]
|
|
304
|
+
rest_row = presentation_df.loc[rest_indx, :]
|
|
305
|
+
duration = rest_row["Time"] - row["Time"]
|
|
306
|
+
|
|
307
|
+
data = {"onset": onset, "duration": duration, "trial_type": row["Code"]}
|
|
308
|
+
if experimental_design == "event" and include_response:
|
|
309
|
+
data.update({"response": response})
|
|
310
|
+
|
|
311
|
+
events.append(data)
|
|
312
|
+
|
|
313
|
+
return pd.DataFrame(events)
|