clabtoolkit 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clabtoolkit-0.3.0/HISTORY.rst +8 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/PKG-INFO +3 -3
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit/__init__.py +1 -1
- clabtoolkit-0.3.0/clabtoolkit/bidstools.py +590 -0
- clabtoolkit-0.3.0/clabtoolkit/dicomtools.py +560 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit/dwitools.py +1 -1
- clabtoolkit-0.3.0/clabtoolkit/freesurfertools.py +1847 -0
- clabtoolkit-0.3.0/clabtoolkit/imagetools.py +449 -0
- clabtoolkit-0.3.0/clabtoolkit/misctools.py +963 -0
- clabtoolkit-0.3.0/clabtoolkit/parcellationtools.py +1487 -0
- clabtoolkit-0.3.0/clabtoolkit/segmentationtools.py +336 -0
- clabtoolkit-0.3.0/clabtoolkit/surfacetools.py +92 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit.egg-info/PKG-INFO +3 -3
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit.egg-info/SOURCES.txt +2 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/setup.cfg +1 -1
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/setup.py +1 -1
- clabtoolkit-0.1.0/HISTORY.rst +0 -8
- clabtoolkit-0.1.0/clabtoolkit/bidstools.py +0 -156
- clabtoolkit-0.1.0/clabtoolkit/dicomtools.py +0 -161
- clabtoolkit-0.1.0/clabtoolkit/freesurfertools.py +0 -518
- clabtoolkit-0.1.0/clabtoolkit/misctools.py +0 -72
- clabtoolkit-0.1.0/clabtoolkit/parcellationtools.py +0 -89
- clabtoolkit-0.1.0/clabtoolkit/surfacetools.py +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/AUTHORS.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/CONTRIBUTING.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/LICENSE +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/MANIFEST.in +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/README.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit/examples.py +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit.egg-info/dependency_links.txt +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit.egg-info/not-zip-safe +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/clabtoolkit.egg-info/top_level.txt +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/Makefile +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/authors.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/conf.py +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/contributing.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/history.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/index.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/installation.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/make.bat +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/readme.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/docs/usage.rst +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/tests/__init__.py +0 -0
- {clabtoolkit-0.1.0 → clabtoolkit-0.3.0}/tests/test_clabtoolkit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: clabtoolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: This package contains a set of useful tools for image processing
|
|
5
5
|
Home-page: https://github.com/connectomicslab/clabtoolkit
|
|
6
6
|
Author: Yasser Alemán-Gómez
|
|
@@ -62,7 +62,7 @@ This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypack
|
|
|
62
62
|
History
|
|
63
63
|
=======
|
|
64
64
|
|
|
65
|
-
0.0
|
|
65
|
+
0.3.0 (2024-05-25)
|
|
66
66
|
------------------
|
|
67
67
|
|
|
68
|
-
*
|
|
68
|
+
* Third release on PyPI.
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import clabtoolkit.misctools as cltmisc
|
|
5
|
+
from typing import Union, Dict, List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
####################################################################################################
|
|
9
|
+
####################################################################################################
|
|
10
|
+
############ ############
|
|
11
|
+
############ ############
|
|
12
|
+
############ Methods dedicated to work with BIDs naming conventions ############
|
|
13
|
+
############ ############
|
|
14
|
+
############ ############
|
|
15
|
+
####################################################################################################
|
|
16
|
+
####################################################################################################
|
|
17
|
+
def str2entity(string: str) -> dict:
|
|
18
|
+
"""
|
|
19
|
+
Converts a formatted string into a dictionary.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
string : str
|
|
24
|
+
String to convert, with the format `key1-value1_key2-value2...suffix.extension`.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
dict
|
|
29
|
+
Dictionary containing the entities extracted from the string.
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
--------
|
|
33
|
+
>>> str2entity("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz")
|
|
34
|
+
Returns: {'sub': '01', 'ses': 'M00', 'acq': '3T', 'dir': 'AP', 'run': '01', 'suffix': 'T1w', 'extension': 'nii.gz'}
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
ent_dict = {}
|
|
38
|
+
suffix, extension = "", ""
|
|
39
|
+
|
|
40
|
+
# Split the string into entities based on underscores.
|
|
41
|
+
ent_list = string.split("_")
|
|
42
|
+
|
|
43
|
+
# Detect suffix and extension
|
|
44
|
+
for ent in ent_list[:]:
|
|
45
|
+
if "-" not in ent:
|
|
46
|
+
# If entity does not contain a '-', it's a suffix or extension.
|
|
47
|
+
if "." in ent:
|
|
48
|
+
# Split suffix and extension parts
|
|
49
|
+
suffix, extension = ent.split(".", 1)
|
|
50
|
+
else:
|
|
51
|
+
suffix = ent
|
|
52
|
+
ent_list.remove(ent)
|
|
53
|
+
|
|
54
|
+
# Process the remaining entities
|
|
55
|
+
for ent in ent_list:
|
|
56
|
+
key, value = ent.split("-", 1) # Split each entity on the first "-"
|
|
57
|
+
ent_dict[key] = value
|
|
58
|
+
|
|
59
|
+
# Add suffix and extension to the dictionary if they were found
|
|
60
|
+
if suffix:
|
|
61
|
+
ent_dict["suffix"] = suffix
|
|
62
|
+
if extension:
|
|
63
|
+
ent_dict["extension"] = extension
|
|
64
|
+
|
|
65
|
+
return ent_dict
|
|
66
|
+
|
|
67
|
+
####################################################################################################
|
|
68
|
+
def entity2str(entity: dict) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Converts an entity dictionary to a string representation.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
entity : dict
|
|
75
|
+
Dictionary containing the entities.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
String containing the entities in the format `key1-value1_key2-value2...suffix.extension`.
|
|
81
|
+
|
|
82
|
+
Examples
|
|
83
|
+
--------
|
|
84
|
+
>>> entity2str({'sub': '01', 'ses': 'M00', 'acq': '3T', 'dir': 'AP', 'run': '01', 'suffix': 'T1w', 'extension': 'nii.gz'})
|
|
85
|
+
Returns: "sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz"
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
# Make a copy of the entity dictionary to avoid mutating the original.
|
|
89
|
+
entity = entity.copy()
|
|
90
|
+
|
|
91
|
+
# Extract optional 'suffix' and 'extension' fields if present.
|
|
92
|
+
suffix = entity.pop("suffix", "")
|
|
93
|
+
extension = entity.pop("extension", "")
|
|
94
|
+
|
|
95
|
+
# Construct the main part of the string by joining key-value pairs with '_'
|
|
96
|
+
ent_string = "_".join(f"{key}-{str(value)}" for key, value in entity.items())
|
|
97
|
+
|
|
98
|
+
# Append suffix if it exists
|
|
99
|
+
if suffix:
|
|
100
|
+
ent_string += '_' + suffix
|
|
101
|
+
else:
|
|
102
|
+
ent_string = ent_string.rstrip("_") # Remove trailing underscore if no suffix
|
|
103
|
+
|
|
104
|
+
# Append extension if it exists
|
|
105
|
+
if extension:
|
|
106
|
+
ent_string += f".{extension}"
|
|
107
|
+
|
|
108
|
+
return ent_string
|
|
109
|
+
|
|
110
|
+
####################################################################################################
|
|
111
|
+
def delete_entity(entity: Union[dict, str],
|
|
112
|
+
key2rem: Union[List[str], str]) -> Union[dict, str]:
|
|
113
|
+
"""
|
|
114
|
+
Removes specified keys from an entity dictionary or string representation.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
entity : dict or str
|
|
119
|
+
Dictionary or string containing the entities.
|
|
120
|
+
key2rem : list or str
|
|
121
|
+
Key(s) to remove from the entity dictionary or string.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
Union[dict, str]
|
|
126
|
+
The updated entity as a dictionary or string (matching the input type).
|
|
127
|
+
|
|
128
|
+
Examples
|
|
129
|
+
--------
|
|
130
|
+
>>> delete_entity("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz", "acq")
|
|
131
|
+
Returns: "sub-01_ses-M00_dir-AP_run-01_T1w.nii.gz"
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
# Determine if `entity` is a string and convert if necessary.
|
|
135
|
+
is_string = isinstance(entity, str)
|
|
136
|
+
if is_string:
|
|
137
|
+
entity_out = str2entity(entity)
|
|
138
|
+
elif isinstance(entity, dict):
|
|
139
|
+
entity_out = entity.copy()
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError("The entity must be a dictionary or a string.")
|
|
142
|
+
|
|
143
|
+
# Ensure `key2rem` is a list for uniform processing.
|
|
144
|
+
if isinstance(key2rem, str):
|
|
145
|
+
key2rem = [key2rem]
|
|
146
|
+
|
|
147
|
+
# Remove specified keys from the entity dictionary.
|
|
148
|
+
for key in key2rem:
|
|
149
|
+
entity_out.pop(key, None) # `pop` with default `None` avoids KeyErrors.
|
|
150
|
+
|
|
151
|
+
# Convert back to string format if original input was a string.
|
|
152
|
+
if is_string:
|
|
153
|
+
return entity2str(entity_out)
|
|
154
|
+
|
|
155
|
+
return entity_out
|
|
156
|
+
|
|
157
|
+
####################################################################################################
|
|
158
|
+
def replace_entity_value(entity: Union[dict, str],
|
|
159
|
+
ent2replace: dict,
|
|
160
|
+
verbose: bool = False) -> Union[dict, str]:
|
|
161
|
+
"""
|
|
162
|
+
Replaces values in an entity dictionary or string representation.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
entity : dict or str
|
|
167
|
+
Dictionary or string containing the entities.
|
|
168
|
+
|
|
169
|
+
ent2replace : dict
|
|
170
|
+
Dictionary of entities to replace with new values.
|
|
171
|
+
|
|
172
|
+
verbose : bool, optional
|
|
173
|
+
If True, prints warnings for non-existent or empty values.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
Union[dict, str]
|
|
178
|
+
Updated entity as a dictionary or string (matching the input type).
|
|
179
|
+
|
|
180
|
+
Examples
|
|
181
|
+
--------
|
|
182
|
+
>>> replace_entity_value("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz", {"acq": "7T"})
|
|
183
|
+
Returns: "sub-01_ses-M00_acq-7T_dir-AP_run-01_T1w.nii.gz"
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
# Determine if `entity` is a string and convert if necessary.
|
|
187
|
+
is_string = isinstance(entity, str)
|
|
188
|
+
if is_string:
|
|
189
|
+
entity_out = str2entity(entity)
|
|
190
|
+
elif isinstance(entity, dict):
|
|
191
|
+
entity_out = entity.copy()
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError("The entity must be a dictionary or a string.")
|
|
194
|
+
|
|
195
|
+
# Remove any empty keys or values from `ent2replace`.
|
|
196
|
+
ent2replace = {k: v for k, v in ent2replace.items() if v}
|
|
197
|
+
|
|
198
|
+
# Replace values in `entity_out` based on `ent2replace`.
|
|
199
|
+
for key, new_value in ent2replace.items():
|
|
200
|
+
if key in entity_out:
|
|
201
|
+
if new_value:
|
|
202
|
+
entity_out[key] = new_value
|
|
203
|
+
elif verbose:
|
|
204
|
+
print(f"Warning: Replacement value for '{key}' is empty.")
|
|
205
|
+
elif verbose:
|
|
206
|
+
print(f"Warning: Entity '{key}' not found in entity dictionary.")
|
|
207
|
+
|
|
208
|
+
# Convert back to string format if original input was a string.
|
|
209
|
+
if is_string:
|
|
210
|
+
return entity2str(entity_out)
|
|
211
|
+
|
|
212
|
+
return entity_out
|
|
213
|
+
|
|
214
|
+
####################################################################################################
|
|
215
|
+
def replace_entity_key(entity: Union[dict, str],
|
|
216
|
+
keys2replace: Dict[str, str],
|
|
217
|
+
verbose: bool = False) -> Union[dict, str]:
|
|
218
|
+
"""
|
|
219
|
+
Replaces specified keys in an entity dictionary or string representation.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
entity : dict or str
|
|
224
|
+
Dictionary containing the entities or a string that follows the BIDS naming specifications.
|
|
225
|
+
|
|
226
|
+
keys2replace : dict
|
|
227
|
+
Dictionary mapping old keys to new keys.
|
|
228
|
+
|
|
229
|
+
verbose : bool, optional
|
|
230
|
+
If True, prints warnings for keys in `keys2replace` that are not found in `entity`.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
Union[dict, str]
|
|
235
|
+
Updated entity as a dictionary or string (matching the input type).
|
|
236
|
+
|
|
237
|
+
Examples
|
|
238
|
+
--------
|
|
239
|
+
>>> replace_entity_key("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz", {"acq": "TESTrep1", "dir": "TESTrep2"})
|
|
240
|
+
Returns: "sub-01_ses-M00_TESTrep1-3T_TESTrep2-AP_run-01_T1w.nii.gz"
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
# Convert `entity` to a dictionary if it's a string
|
|
244
|
+
is_string = isinstance(entity, str)
|
|
245
|
+
if is_string:
|
|
246
|
+
entity = str2entity(entity)
|
|
247
|
+
elif not isinstance(entity, dict):
|
|
248
|
+
raise ValueError("The entity must be a dictionary or a string.")
|
|
249
|
+
|
|
250
|
+
# Validate that `keys2replace` is a dictionary
|
|
251
|
+
if not isinstance(keys2replace, dict):
|
|
252
|
+
raise ValueError("The keys2replace parameter must be a dictionary.")
|
|
253
|
+
|
|
254
|
+
# Filter out any empty keys or values from `keys2replace`
|
|
255
|
+
keys2replace = {k: v for k, v in keys2replace.items() if k and v}
|
|
256
|
+
|
|
257
|
+
# Replace key names in the entity
|
|
258
|
+
entity_out = {}
|
|
259
|
+
for key, value in entity.items():
|
|
260
|
+
# Use the new key if it exists in `keys2replace`, otherwise keep the original key
|
|
261
|
+
new_key = keys2replace.get(key, key)
|
|
262
|
+
entity_out[new_key] = value
|
|
263
|
+
|
|
264
|
+
# Verbose output if the key to replace does not exist in the entity
|
|
265
|
+
if verbose and key in keys2replace and key not in entity:
|
|
266
|
+
print(f"Warning: Key '{key}' not found in the original dictionary.")
|
|
267
|
+
|
|
268
|
+
# Convert back to string format if the original input was a string
|
|
269
|
+
if is_string:
|
|
270
|
+
return entity2str(entity_out)
|
|
271
|
+
|
|
272
|
+
return entity_out
|
|
273
|
+
|
|
274
|
+
####################################################################################################
|
|
275
|
+
def insert_entity(entity: Union[dict, str],
|
|
276
|
+
entity2add: Dict[str, str],
|
|
277
|
+
prev_entity: str = None) -> Union[dict, str]:
|
|
278
|
+
"""
|
|
279
|
+
Adds entities to an existing entity dictionary or string representation.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
entity : dict or str
|
|
284
|
+
Dictionary containing the entities or a string that follows the BIDS naming specifications.
|
|
285
|
+
|
|
286
|
+
entity2add : dict
|
|
287
|
+
Dictionary containing the entities to add.
|
|
288
|
+
|
|
289
|
+
prev_entity : str, optional
|
|
290
|
+
Key in `entity` after which to insert the new entities.
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
Union[dict, str]
|
|
295
|
+
Updated entity with the new entities added (matching the input type).
|
|
296
|
+
|
|
297
|
+
Examples
|
|
298
|
+
--------
|
|
299
|
+
>>> insert_entity("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz", {"task": "rest"})
|
|
300
|
+
Returns: "sub-01_ses-M00_acq-3T_dir-AP_run-01_task-rest_T1w.nii.gz"
|
|
301
|
+
|
|
302
|
+
>>> insert_entity("sub-01_ses-M00_acq-3T_dir-AP_run-01_T1w.nii.gz", {"task": "rest"}, prev_entity="ses")
|
|
303
|
+
Returns: "sub-01_ses-M00_task-rest_acq-3T_dir-AP_run-01_T1w.nii.gz"
|
|
304
|
+
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
# Determine if `entity` is a string and convert if necessary
|
|
308
|
+
is_string = isinstance(entity, str)
|
|
309
|
+
if is_string:
|
|
310
|
+
entity = str2entity(entity)
|
|
311
|
+
elif not isinstance(entity, dict):
|
|
312
|
+
raise ValueError("The entity must be a dictionary or a string.")
|
|
313
|
+
|
|
314
|
+
# Clean `entity2add` by removing any empty keys or values
|
|
315
|
+
entity2add = {k: v for k, v in entity2add.items() if k and v}
|
|
316
|
+
|
|
317
|
+
# Validate `prev_entity` if provided
|
|
318
|
+
if prev_entity is not None and prev_entity not in entity:
|
|
319
|
+
raise ValueError(f"Reference entity '{prev_entity}' is not in the entity dictionary.")
|
|
320
|
+
|
|
321
|
+
# Temporarily remove `suffix` and `extension` if they exist
|
|
322
|
+
suffix = entity.pop("suffix", None)
|
|
323
|
+
extension = entity.pop("extension", None)
|
|
324
|
+
|
|
325
|
+
# Build `ent_out` by adding items from `entity`, and insert `entity2add` after `prev_entity` if specified
|
|
326
|
+
ent_out = {}
|
|
327
|
+
for key, value in entity.items():
|
|
328
|
+
ent_out[key] = value
|
|
329
|
+
if key == prev_entity:
|
|
330
|
+
ent_out.update(entity2add) # Insert new entities immediately after `prev_entity`
|
|
331
|
+
|
|
332
|
+
# If no `prev_entity` is specified or if `prev_entity` is "suffix", append `entity2add` at the end
|
|
333
|
+
if prev_entity is None or prev_entity == "suffix":
|
|
334
|
+
ent_out.update(entity2add)
|
|
335
|
+
|
|
336
|
+
# Restore `suffix` and `extension` if they were removed
|
|
337
|
+
if suffix:
|
|
338
|
+
ent_out["suffix"] = suffix
|
|
339
|
+
if extension:
|
|
340
|
+
ent_out["extension"] = extension
|
|
341
|
+
|
|
342
|
+
# Convert back to string format if the original input was a string
|
|
343
|
+
if is_string:
|
|
344
|
+
return entity2str(ent_out)
|
|
345
|
+
|
|
346
|
+
return ent_out
|
|
347
|
+
|
|
348
|
+
####################################################################################################
|
|
349
|
+
def recursively_replace_entity_value(root_dir:str,
|
|
350
|
+
dict2old: Union[dict, str],
|
|
351
|
+
dict2new: Union[dict, str]):
|
|
352
|
+
|
|
353
|
+
"""
|
|
354
|
+
This method replaces the values of certain entities in all the files and folders of a BIDs dataset.
|
|
355
|
+
|
|
356
|
+
Parameters:
|
|
357
|
+
----------
|
|
358
|
+
root_dir: str
|
|
359
|
+
Root directory of the BIDs dataset
|
|
360
|
+
|
|
361
|
+
dict2old: dict or str
|
|
362
|
+
Dictionary containing the entities to replace and their old values
|
|
363
|
+
|
|
364
|
+
dict2new: dict or str
|
|
365
|
+
Dictionary containing the entities to replace and their new values
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
# Detect if the BIDs directory exists
|
|
371
|
+
if not os.path.isdir(root_dir):
|
|
372
|
+
raise ValueError("The BIDs directory does not exist.")
|
|
373
|
+
|
|
374
|
+
# Convert the strings to dictionaries
|
|
375
|
+
if isinstance(dict2old, str):
|
|
376
|
+
dict2old = str2entity(dict2old)
|
|
377
|
+
if isinstance(dict2new, str):
|
|
378
|
+
dict2new = str2entity(dict2new)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# Leave in the dictionaries only the keys that are common
|
|
382
|
+
dict2old = {k: dict2old[k] for k in dict2old if k in dict2new}
|
|
383
|
+
dict2new = {k: dict2new[k] for k in dict2new if k in dict2old}
|
|
384
|
+
|
|
385
|
+
# Order the dictionaries alphabetically by key
|
|
386
|
+
dict2old = dict(sorted(dict2old.items()))
|
|
387
|
+
dict2new = dict(sorted(dict2new.items()))
|
|
388
|
+
|
|
389
|
+
# Creating the list of strings
|
|
390
|
+
dict2old_list = [f"{key}-{value}" for key, value in dict2old.items()]
|
|
391
|
+
dict2new_list = [f"{key}-{value}" for key, value in dict2new.items()]
|
|
392
|
+
|
|
393
|
+
# Find all the files and folders that contain a certain string any of the key values in the dictionary dict2old
|
|
394
|
+
|
|
395
|
+
# Walk through the directory from bottom to top (reverse)
|
|
396
|
+
for root, dirs, files in os.walk(root_dir, topdown=False):
|
|
397
|
+
# Rename files
|
|
398
|
+
for file_name in files:
|
|
399
|
+
|
|
400
|
+
for i, subst_x in enumerate(dict2old_list):
|
|
401
|
+
subst_y = dict2new_list[i]
|
|
402
|
+
if subst_x in file_name:
|
|
403
|
+
old_path = os.path.join(root, file_name)
|
|
404
|
+
new_name = file_name.replace(subst_x, subst_y)
|
|
405
|
+
new_path = os.path.join(root, new_name)
|
|
406
|
+
os.rename(old_path, new_path)
|
|
407
|
+
file_name = new_name
|
|
408
|
+
|
|
409
|
+
# the file is the tsv open the file and replace the string
|
|
410
|
+
if file_name.endswith('sessions.tsv'):
|
|
411
|
+
tsv_file = os.path.join(root,file_name)
|
|
412
|
+
# Read line by line and replace the string
|
|
413
|
+
# Load the TSV file
|
|
414
|
+
df = pd.read_csv(tsv_file, sep='\t')
|
|
415
|
+
|
|
416
|
+
# Replace subst_x with subst_y in all string columns
|
|
417
|
+
df = df.applymap(lambda x: x.replace(subst_x, subst_y) if isinstance(x, str) else x)
|
|
418
|
+
|
|
419
|
+
# Save the modified DataFrame as a TSV file
|
|
420
|
+
df.to_csv(tsv_file, sep='\t', index=False)
|
|
421
|
+
|
|
422
|
+
# Rename directories
|
|
423
|
+
for dir_name in dirs:
|
|
424
|
+
if subst_x in dir_name:
|
|
425
|
+
old_path = os.path.join(root, dir_name)
|
|
426
|
+
new_name = dir_name.replace(subst_x, subst_y)
|
|
427
|
+
new_path = os.path.join(root, new_name)
|
|
428
|
+
os.rename(old_path, new_path)
|
|
429
|
+
|
|
430
|
+
####################################################################################################
|
|
431
|
+
####################################################################################################
|
|
432
|
+
############ ############
|
|
433
|
+
############ ############
|
|
434
|
+
############ Methods dedicated to work with BIDs file organization ############
|
|
435
|
+
############ ############
|
|
436
|
+
############ ############
|
|
437
|
+
####################################################################################################
|
|
438
|
+
####################################################################################################
|
|
439
|
+
|
|
440
|
+
# This function copies the BIDs folder and its derivatives for e given subjects to a new location
|
|
441
|
+
def copy_bids_folder(
|
|
442
|
+
bids_dir: str,
|
|
443
|
+
out_dir: str,
|
|
444
|
+
fold2copy: list = ["anat"],
|
|
445
|
+
subjs2copy: str = None,
|
|
446
|
+
deriv_dir: str = None,
|
|
447
|
+
include_derivatives: bool = False,
|
|
448
|
+
):
|
|
449
|
+
"""
|
|
450
|
+
Copy full bids folders
|
|
451
|
+
@params:
|
|
452
|
+
bids_dir - Required : BIDs dataset directory:
|
|
453
|
+
out_dir - Required : Output directory:
|
|
454
|
+
fold2copy - Optional : List of folders to copy: default = ['anat']
|
|
455
|
+
subjs2copy - Optional : List of subjects to copy:
|
|
456
|
+
deriv_dir - Optional : Derivatives directory: default = None
|
|
457
|
+
include_derivatives - Optional : Include derivatives folder: default = False
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
# Listing the subject ids inside the dicom folder
|
|
461
|
+
if subjs2copy is None:
|
|
462
|
+
my_list = os.listdir(bids_dir)
|
|
463
|
+
subj_ids = []
|
|
464
|
+
for it in my_list:
|
|
465
|
+
if "sub-" in it:
|
|
466
|
+
subj_ids.append(it)
|
|
467
|
+
subj_ids.sort()
|
|
468
|
+
else:
|
|
469
|
+
subj_ids = subjs2copy
|
|
470
|
+
|
|
471
|
+
# Selecting the derivatives folder
|
|
472
|
+
if include_derivatives:
|
|
473
|
+
if deriv_dir is None:
|
|
474
|
+
deriv_dir = os.path.join(bids_dir, "derivatives")
|
|
475
|
+
|
|
476
|
+
if not os.path.isdir(deriv_dir):
|
|
477
|
+
# Lunch a warning message if the derivatives folder does not exist
|
|
478
|
+
print("WARNING: The derivatives folder does not exist.")
|
|
479
|
+
print("WARNING: The derivatives folder will not be copied.")
|
|
480
|
+
include_derivatives = False
|
|
481
|
+
|
|
482
|
+
# Selecting all the derivatives folders
|
|
483
|
+
der_pipe_folders = []
|
|
484
|
+
directories = os.listdir(deriv_dir)
|
|
485
|
+
der_pipe_folders = []
|
|
486
|
+
for directory in directories:
|
|
487
|
+
pipe_dir = os.path.join(deriv_dir, directory)
|
|
488
|
+
if not directory.startswith(".") and os.path.isdir(pipe_dir):
|
|
489
|
+
der_pipe_folders.append(pipe_dir)
|
|
490
|
+
|
|
491
|
+
# Failed sessions and derivatives
|
|
492
|
+
fail_sess = []
|
|
493
|
+
fail_deriv = []
|
|
494
|
+
|
|
495
|
+
# Loop around all the subjects
|
|
496
|
+
nsubj = len(subj_ids)
|
|
497
|
+
for i, subj_id in enumerate(subj_ids): # Loop along the IDs
|
|
498
|
+
subj_dir = os.path.join(bids_dir, subj_id)
|
|
499
|
+
out_subj_dir = os.path.join(out_dir, subj_id)
|
|
500
|
+
|
|
501
|
+
cltmisc.printprogressbar(
|
|
502
|
+
i + 1,
|
|
503
|
+
nsubj,
|
|
504
|
+
"Processing subject "
|
|
505
|
+
+ subj_id
|
|
506
|
+
+ ": "
|
|
507
|
+
+ "("
|
|
508
|
+
+ str(i + 1)
|
|
509
|
+
+ "/"
|
|
510
|
+
+ str(nsubj)
|
|
511
|
+
+ ")",
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# Loop along all the sessions inside the subject directory
|
|
515
|
+
for ses_id in os.listdir(subj_dir): # Loop along the session
|
|
516
|
+
ses_dir = os.path.join(subj_dir, ses_id)
|
|
517
|
+
out_ses_dir = os.path.join(out_subj_dir, ses_id)
|
|
518
|
+
|
|
519
|
+
# print('Copying SubjectId: ' + subjId + ' ======> Session: ' + sesId)
|
|
520
|
+
|
|
521
|
+
if fold2copy[0] == "all":
|
|
522
|
+
directories = os.listdir(ses_dir)
|
|
523
|
+
fold2copy = []
|
|
524
|
+
for directory in directories:
|
|
525
|
+
if not directory.startswith(".") and os.path.isdir(
|
|
526
|
+
os.path.join(ses_dir, directory)
|
|
527
|
+
):
|
|
528
|
+
print(directory)
|
|
529
|
+
fold2copy.append(directory)
|
|
530
|
+
|
|
531
|
+
for fc in fold2copy:
|
|
532
|
+
# Copying the anat folder
|
|
533
|
+
if os.path.isdir(ses_dir):
|
|
534
|
+
fold_to_copy = os.path.join(ses_dir, fc)
|
|
535
|
+
|
|
536
|
+
try:
|
|
537
|
+
# Creating destination directory using make directory
|
|
538
|
+
dest_dir = os.path.join(out_ses_dir, fc)
|
|
539
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
540
|
+
|
|
541
|
+
shutil.copytree(fold_to_copy, dest_dir, dirs_exist_ok=True)
|
|
542
|
+
|
|
543
|
+
except:
|
|
544
|
+
fail_sess.append(fold_to_copy)
|
|
545
|
+
|
|
546
|
+
if include_derivatives:
|
|
547
|
+
# Copying the derivatives folder
|
|
548
|
+
|
|
549
|
+
for pipe_dir in der_pipe_folders:
|
|
550
|
+
if os.path.isdir(pipe_dir):
|
|
551
|
+
|
|
552
|
+
out_pipe_dir = os.path.join(
|
|
553
|
+
out_dir, "derivatives", os.path.basename(pipe_dir)
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
pipe_indiv_subj_in = os.path.join(pipe_dir, subj_id, ses_id)
|
|
557
|
+
pipe_indiv_subj_out = os.path.join(
|
|
558
|
+
out_pipe_dir, subj_id, ses_id
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
if os.path.isdir(pipe_indiv_subj_in):
|
|
562
|
+
try:
|
|
563
|
+
# Creating destination directory using make directory
|
|
564
|
+
os.makedirs(pipe_indiv_subj_out, exist_ok=True)
|
|
565
|
+
|
|
566
|
+
# Copying the folder
|
|
567
|
+
shutil.copytree(
|
|
568
|
+
pipe_indiv_subj_in,
|
|
569
|
+
pipe_indiv_subj_out,
|
|
570
|
+
dirs_exist_ok=True,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
except:
|
|
574
|
+
fail_deriv.append(pipe_indiv_subj_in)
|
|
575
|
+
|
|
576
|
+
# Print the failed sessions and derivatives
|
|
577
|
+
print(" ")
|
|
578
|
+
if fail_sess:
|
|
579
|
+
print("THE PROCESS FAILED COPYING THE FOLLOWING SESSIONS:")
|
|
580
|
+
for i in fail_sess:
|
|
581
|
+
print(i)
|
|
582
|
+
print(" ")
|
|
583
|
+
|
|
584
|
+
if fail_deriv:
|
|
585
|
+
print("THE PROCESS FAILED COPYING THE FOLLOWING DERIVATIVES:")
|
|
586
|
+
for i in fail_deriv:
|
|
587
|
+
print(i)
|
|
588
|
+
print(" ")
|
|
589
|
+
|
|
590
|
+
print("End of copying the files.")
|