nifti2bids 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nifti2bids might be problematic. Click here for more details.
- nifti2bids/__init__.py +20 -0
- nifti2bids/_decorators.py +53 -0
- nifti2bids/_exceptions.py +51 -0
- nifti2bids/_helpers.py +6 -0
- nifti2bids/bids.py +192 -0
- nifti2bids/io.py +135 -0
- nifti2bids/logging.py +86 -0
- nifti2bids/metadata.py +758 -0
- nifti2bids/simulate.py +59 -0
- nifti2bids-0.1.2.dist-info/METADATA +67 -0
- nifti2bids-0.1.2.dist-info/RECORD +14 -0
- nifti2bids-0.1.2.dist-info/WHEEL +5 -0
- nifti2bids-0.1.2.dist-info/licenses/LICENSE +21 -0
- nifti2bids-0.1.2.dist-info/top_level.txt +1 -0
nifti2bids/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
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 metadata information from NIfTI images
|
|
16
|
+
|
|
17
|
+
simulate -- Simulate a basic NIfTI image for testing purposes
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.2"
|
|
@@ -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
|
nifti2bids/_helpers.py
ADDED
nifti2bids/bids.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Module for creating BIDS compliant files."""
|
|
2
|
+
|
|
3
|
+
import os, json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from nifti2bids.io import _copy_file, glob_contents
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_bids_file(
|
|
12
|
+
nifti_file: str,
|
|
13
|
+
subj_id: str | int,
|
|
14
|
+
desc: str,
|
|
15
|
+
ses_id: Optional[str | int] = None,
|
|
16
|
+
task_id: Optional[str] = None,
|
|
17
|
+
run_id: Optional[str | int] = None,
|
|
18
|
+
dst_dir: str = None,
|
|
19
|
+
remove_src_file: bool = False,
|
|
20
|
+
return_bids_filename: bool = False,
|
|
21
|
+
) -> str | None:
|
|
22
|
+
"""
|
|
23
|
+
Create a BIDS compliant filename with required and optional entities.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
nifti_file: :obj:`str`
|
|
28
|
+
Path to NIfTI image.
|
|
29
|
+
|
|
30
|
+
sub_id: :obj:`str` or :obj:`int`
|
|
31
|
+
Subject ID (i.e. 01, 101, etc).
|
|
32
|
+
|
|
33
|
+
desc: :obj:`str`
|
|
34
|
+
Description of the file (i.e., T1w, bold, etc).
|
|
35
|
+
|
|
36
|
+
ses_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
37
|
+
Session ID (i.e. 001, 1, etc). Optional entity.
|
|
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
|
+
task_id: :obj:`str` or :obj:`None`, default=None
|
|
43
|
+
Task ID (i.e. flanker, n_back, etc). Optional entity.
|
|
44
|
+
|
|
45
|
+
run_id: :obj:`str` or :obj:`int` or :obj:`None`, default=None
|
|
46
|
+
Run ID (i.e. 001, 1, etc). Optional entity.
|
|
47
|
+
|
|
48
|
+
dst_dir: :obj:`str`, default=None
|
|
49
|
+
Directory name to copy the BIDS file to. If None, then the
|
|
50
|
+
BIDS file is copied to the same directory as
|
|
51
|
+
|
|
52
|
+
remove_src_file: :obj:`str`, default=False
|
|
53
|
+
Delete the source file if True.
|
|
54
|
+
|
|
55
|
+
return_bids_filename: :obj:`str`, default=False
|
|
56
|
+
Returns the full BIDS filename if True.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
None or str
|
|
61
|
+
If ``return_bids_filename`` is True, then the BIDS filename is
|
|
62
|
+
returned.
|
|
63
|
+
|
|
64
|
+
Note
|
|
65
|
+
----
|
|
66
|
+
There are additional entities that can be used that are
|
|
67
|
+
not included in this function.
|
|
68
|
+
"""
|
|
69
|
+
bids_filename = f"sub-{subj_id}_ses-{ses_id}_task-{task_id}_" f"run-{run_id}_{desc}"
|
|
70
|
+
bids_filename = _strip_none_entities(bids_filename)
|
|
71
|
+
|
|
72
|
+
ext = f"{nifti_file.partition('.')[-1]}"
|
|
73
|
+
bids_filename += f"{ext}"
|
|
74
|
+
bids_filename = (
|
|
75
|
+
os.path.join(os.path.dirname(nifti_file), bids_filename)
|
|
76
|
+
if dst_dir is None
|
|
77
|
+
else os.path.join(dst_dir, bids_filename)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
_copy_file(nifti_file, bids_filename, remove_src_file)
|
|
81
|
+
|
|
82
|
+
return bids_filename if return_bids_filename else None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _strip_none_entities(bids_filename: str) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Removes entities with None in a BIDS compliant filename.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
bids_filename: :obj:`str`
|
|
92
|
+
The BIDS filename.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
str
|
|
97
|
+
BIDS filename with entities ending in None removed.
|
|
98
|
+
|
|
99
|
+
Example
|
|
100
|
+
-------
|
|
101
|
+
>>> from bidsrep.io import _strip_none_entities
|
|
102
|
+
>>> bids_filename = "sub-101_ses-None_task-flanker_bold.nii.gz"
|
|
103
|
+
>>> _strip_none_entities(bids_filename)
|
|
104
|
+
"sub-101_task-flanker_bold.nii.gz"
|
|
105
|
+
"""
|
|
106
|
+
basename, _, ext = bids_filename.partition(".")
|
|
107
|
+
retained_entities = [
|
|
108
|
+
entity for entity in basename.split("_") if not entity.endswith("-None")
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
return f"{'_'.join(retained_entities)}.{ext}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def create_dataset_description(dataset_name: str, bids_version: str = "1.0.0") -> dict:
|
|
115
|
+
"""
|
|
116
|
+
Generate a dataset description dictionary.
|
|
117
|
+
|
|
118
|
+
Creates a dictionary containing the name and BIDs version of a dataset.
|
|
119
|
+
|
|
120
|
+
.. versionadded:: 0.34.1
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
dataset_name: :obj:`str`
|
|
125
|
+
Name of the dataset.
|
|
126
|
+
|
|
127
|
+
bids_version: :obj:`str`,
|
|
128
|
+
Version of the BIDS dataset.
|
|
129
|
+
|
|
130
|
+
derivative: :obj:`bool`, default=False
|
|
131
|
+
Determines if "GeneratedBy" key is added to dictionary.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
dict
|
|
136
|
+
The dataset description dictionary
|
|
137
|
+
"""
|
|
138
|
+
return {"Name": dataset_name, "BIDSVersion": bids_version}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def save_dataset_description(dataset_description: dict[str, str], dst_dir: str) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Save a dataset description dictionary.
|
|
144
|
+
|
|
145
|
+
Saves the dataset description dictionary as a file named "dataset_description.json" to the
|
|
146
|
+
directory specified by ``output_dir``.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
dataset_description: :obj:`dict`
|
|
151
|
+
The dataset description dictionary.
|
|
152
|
+
|
|
153
|
+
dst_dir: :obj:`str`
|
|
154
|
+
Path to save the JSON file to.
|
|
155
|
+
"""
|
|
156
|
+
with open(
|
|
157
|
+
os.path.join(dst_dir, "dataset_description.json"), "w", encoding="utf-8"
|
|
158
|
+
) as f:
|
|
159
|
+
json.dump(dataset_description, f)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_participant_tsv(
|
|
163
|
+
bids_dir: str, save_df: bool = False, return_df: bool = True
|
|
164
|
+
) -> pd.DataFrame | None:
|
|
165
|
+
"""
|
|
166
|
+
Creates a basic participant dataframe for the "participants.tsv" file.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
bids_dir: :obj:`str`
|
|
171
|
+
The root of BIDS compliant directory.
|
|
172
|
+
|
|
173
|
+
save_df: :obj:`bool`, bool=False
|
|
174
|
+
Save the dataframe to the root of the BIDS compliant directory.
|
|
175
|
+
|
|
176
|
+
return_df: :obj:`str`
|
|
177
|
+
Whether or not to return the dataframe.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
pd.DataFrame or None
|
|
182
|
+
The dataframe if ``return_df`` is True.
|
|
183
|
+
"""
|
|
184
|
+
participants = [
|
|
185
|
+
os.path.basename(folder) for folder in glob_contents(bids_dir, "*sub-*")
|
|
186
|
+
]
|
|
187
|
+
df = pd.DataFrame({"participant_id": participants})
|
|
188
|
+
|
|
189
|
+
if save_df:
|
|
190
|
+
df.to_csv(os.path.join(bids_dir, "participants.tsv"), sep="\t", index=None)
|
|
191
|
+
|
|
192
|
+
return df if return_df else None
|
nifti2bids/io.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Module for input/output operations."""
|
|
2
|
+
|
|
3
|
+
import glob, os, shutil
|
|
4
|
+
|
|
5
|
+
import nibabel as nib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_nifti(
|
|
9
|
+
nifti_file_or_img: str | nib.nifti1.Nifti1Image,
|
|
10
|
+
) -> nib.nifti1.Nifti1Image:
|
|
11
|
+
"""
|
|
12
|
+
Loads a NIfTI image.
|
|
13
|
+
|
|
14
|
+
Loads NIfTI image when not a ``Nifti1Image`` object or
|
|
15
|
+
returns the image if already loaded in.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
20
|
+
Path to the NIfTI file or a NIfTI image.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
nib.nifti1.Nifti1Image
|
|
25
|
+
The loaded in NIfTI image.
|
|
26
|
+
"""
|
|
27
|
+
nifti_img = (
|
|
28
|
+
nifti_file_or_img
|
|
29
|
+
if isinstance(nifti_file_or_img, nib.nifti1.Nifti1Image)
|
|
30
|
+
else nib.load(nifti_file_or_img)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return nifti_img
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def compress_image(nifti_file: str, remove_src_file: bool = False) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Compresses a ".nii" image to a ".nii.gz" image.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
nifti_file: :obj:`str`
|
|
43
|
+
Path to the NIfTI image.
|
|
44
|
+
|
|
45
|
+
remove_src_file: :obj:`bool`
|
|
46
|
+
Deletes the original source image file.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
None
|
|
51
|
+
"""
|
|
52
|
+
img = nib.load(nifti_file)
|
|
53
|
+
nib.save(img, nifti_file.replace(".nii", ".nii.gz"))
|
|
54
|
+
|
|
55
|
+
if remove_src_file:
|
|
56
|
+
os.remove(nifti_file)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def glob_contents(src_dir: str, pattern: str) -> list[str]:
|
|
60
|
+
"""
|
|
61
|
+
Use glob to get contents with specific patterns.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
src_dir: :obj:`str`
|
|
66
|
+
The source directory.
|
|
67
|
+
|
|
68
|
+
ext: :obj:`str`
|
|
69
|
+
The extension.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
list[str]
|
|
74
|
+
List of contents with the pattern specified by ``pattern``.
|
|
75
|
+
"""
|
|
76
|
+
return glob.glob(os.path.join(src_dir, f"*{pattern}"))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_nifti_header(nifti_file_or_img):
|
|
80
|
+
"""
|
|
81
|
+
Get header from a NIfTI image.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
86
|
+
Path to the NIfTI file or a NIfTI image.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
nib.nifti1.Nifti1Image
|
|
91
|
+
The header from a NIfTI image.
|
|
92
|
+
"""
|
|
93
|
+
return load_nifti(nifti_file_or_img).header
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_nifti_affine(nifti_file_or_img):
|
|
97
|
+
"""
|
|
98
|
+
Get the affine matrix from a NIfTI image.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
nifti_file_or_img: :obj:`str` or :obj:`Nifti1Image`
|
|
103
|
+
Path to the NIfTI file or a NIfTI image.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
nib.nifti1.Nifti1Image
|
|
108
|
+
The header from a NIfTI image.
|
|
109
|
+
"""
|
|
110
|
+
return load_nifti(nifti_file_or_img).affine
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _copy_file(src_file: str, dst_file: str, remove_src_file: bool) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Copy a file and optionally remove the source file.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
src_file: :obj:`str`
|
|
120
|
+
The source file to be copied
|
|
121
|
+
|
|
122
|
+
dst_file: :obj:`str`
|
|
123
|
+
The new destination file.
|
|
124
|
+
|
|
125
|
+
remove_src_file: :obj:`bool`
|
|
126
|
+
Delete the source file if True.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
None
|
|
131
|
+
"""
|
|
132
|
+
shutil.copy(src_file, dst_file)
|
|
133
|
+
|
|
134
|
+
if remove_src_file:
|
|
135
|
+
os.remove(src_file)
|
nifti2bids/logging.py
ADDED
|
@@ -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
|