clabtoolkit 0.3.3__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.
- clabtoolkit/__init__.py +5 -0
- clabtoolkit/bidstools.py +2260 -0
- clabtoolkit/config/bids.json +147 -0
- clabtoolkit/config/config.json +58 -0
- clabtoolkit/config/lobes.json +112 -0
- clabtoolkit/config/stats_mapping.json +140 -0
- clabtoolkit/config/viz_views.json +268 -0
- clabtoolkit/connectivitytools.py +0 -0
- clabtoolkit/dicomtools.py +941 -0
- clabtoolkit/dwitools.py +1999 -0
- clabtoolkit/freesurfertools.py +5378 -0
- clabtoolkit/imagetools.py +2734 -0
- clabtoolkit/misctools.py +5204 -0
- clabtoolkit/morphometrytools.py +2970 -0
- clabtoolkit/networktools.py +351 -0
- clabtoolkit/parcellationtools.py +3239 -0
- clabtoolkit/pipelinetools.py +1187 -0
- clabtoolkit/plottools.py +682 -0
- clabtoolkit/qcqatools.py +1305 -0
- clabtoolkit/segmentationtools.py +356 -0
- clabtoolkit/surfacetools.py +2785 -0
- clabtoolkit/visualizationtools.py +5575 -0
- clabtoolkit-0.3.3.dist-info/METADATA +212 -0
- clabtoolkit-0.3.3.dist-info/RECORD +28 -0
- clabtoolkit-0.3.3.dist-info/WHEEL +5 -0
- clabtoolkit-0.3.3.dist-info/licenses/AUTHORS.rst +13 -0
- clabtoolkit-0.3.3.dist-info/licenses/LICENSE +16 -0
- clabtoolkit-0.3.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# pylint: disable=import-error
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from glob import glob
|
|
7
|
+
from typing import Union
|
|
8
|
+
import numpy as np
|
|
9
|
+
import nibabel as nib
|
|
10
|
+
|
|
11
|
+
# Importing local modules
|
|
12
|
+
from . import misctools as cltmisc
|
|
13
|
+
from . import bidstools as cltbids
|
|
14
|
+
from . import imagetools as cltimg
|
|
15
|
+
from . import parcellationtools as cltparc
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
####################################################################################################
|
|
19
|
+
####################################################################################################
|
|
20
|
+
############ ############
|
|
21
|
+
############ ############
|
|
22
|
+
############ Section 1: Methods dedicated to image segmentation ############
|
|
23
|
+
############ ############
|
|
24
|
+
############ ############
|
|
25
|
+
####################################################################################################
|
|
26
|
+
####################################################################################################
|
|
27
|
+
def abased_parcellation(
|
|
28
|
+
t1: str,
|
|
29
|
+
t1_temp: str,
|
|
30
|
+
atlas: str,
|
|
31
|
+
out_parc: str,
|
|
32
|
+
xfm_output: str,
|
|
33
|
+
atlas_type: str = "spam",
|
|
34
|
+
interp: str = "Linear",
|
|
35
|
+
cont_tech: str = "local",
|
|
36
|
+
cont_image: str = None,
|
|
37
|
+
force: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Perform atlas-based parcellation using ANTs registration and transformation.
|
|
41
|
+
|
|
42
|
+
Registers individual T1-weighted image to template space, then applies inverse
|
|
43
|
+
transformation to bring atlas into subject space, creating subject-specific
|
|
44
|
+
parcellation based on template atlas.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
t1 : str
|
|
49
|
+
Path to input T1-weighted image.
|
|
50
|
+
|
|
51
|
+
t1_temp : str
|
|
52
|
+
Path to T1-weighted template image for registration target.
|
|
53
|
+
|
|
54
|
+
atlas : str
|
|
55
|
+
Path to atlas image (either SPAM probabilities or discrete labels).
|
|
56
|
+
|
|
57
|
+
out_parc : str
|
|
58
|
+
Path for output parcellation file.
|
|
59
|
+
|
|
60
|
+
xfm_output : str
|
|
61
|
+
Base path/name for transformation files. Extensions and descriptors
|
|
62
|
+
will be automatically added.
|
|
63
|
+
|
|
64
|
+
atlas_type : str, optional
|
|
65
|
+
Atlas format: 'spam' for probability maps or 'maxprob' for discrete labels.
|
|
66
|
+
Default is 'spam'.
|
|
67
|
+
|
|
68
|
+
interp : str, optional
|
|
69
|
+
Interpolation method: 'Linear', 'NearestNeighbor', 'BSpline'. Default is 'Linear'.
|
|
70
|
+
|
|
71
|
+
cont_tech : str, optional
|
|
72
|
+
Container technology: 'local', 'singularity', 'docker'. Default is 'local'.
|
|
73
|
+
|
|
74
|
+
cont_image : str, optional
|
|
75
|
+
Container image specification. Default is None.
|
|
76
|
+
|
|
77
|
+
force : bool, optional
|
|
78
|
+
Whether to overwrite existing files. Default is False.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
str
|
|
83
|
+
Path to the generated parcellation file.
|
|
84
|
+
|
|
85
|
+
Notes
|
|
86
|
+
-----
|
|
87
|
+
The method performs the following steps:
|
|
88
|
+
1. ANTs SyN registration between subject T1 and template
|
|
89
|
+
2. Generates affine and non-linear transformation files
|
|
90
|
+
3. Applies inverse transformation to atlas
|
|
91
|
+
4. For SPAM atlases, preserves probability values
|
|
92
|
+
5. For maxprob atlases, uses nearest neighbor interpolation
|
|
93
|
+
|
|
94
|
+
Generated transformation files follow BIDS naming conventions with
|
|
95
|
+
descriptors: affine, warp, iwarp.
|
|
96
|
+
|
|
97
|
+
Examples
|
|
98
|
+
--------
|
|
99
|
+
>>> # Basic SPAM atlas parcellation
|
|
100
|
+
>>> parc_file = abased_parcellation(
|
|
101
|
+
... t1='subject_T1w.nii.gz',
|
|
102
|
+
... t1_temp='MNI152_T1_1mm.nii.gz',
|
|
103
|
+
... atlas='AAL_SPAM.nii.gz',
|
|
104
|
+
... out_parc='subject_AAL.nii.gz',
|
|
105
|
+
... xfm_output='transforms/subject_to_MNI'
|
|
106
|
+
... )
|
|
107
|
+
>>>
|
|
108
|
+
>>> # Discrete atlas with nearest neighbor interpolation
|
|
109
|
+
>>> parc_file = abased_parcellation(
|
|
110
|
+
... t1='T1w.nii.gz',
|
|
111
|
+
... t1_temp='template.nii.gz',
|
|
112
|
+
... atlas='discrete_atlas.nii.gz',
|
|
113
|
+
... out_parc='parcellation.nii.gz',
|
|
114
|
+
... xfm_output='xfm/transform',
|
|
115
|
+
... atlas_type='maxprob'
|
|
116
|
+
... )
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
######## -- Registration to the template space ------------ #
|
|
120
|
+
# Creating spatial transformation folder
|
|
121
|
+
stransf_dir = Path(os.path.dirname(xfm_output))
|
|
122
|
+
stransf_name = os.path.basename(xfm_output)
|
|
123
|
+
out_parc_dir = Path(os.path.dirname(out_parc))
|
|
124
|
+
|
|
125
|
+
# If the directory does not exist create the directory and if it fails because it does not have write access send an error
|
|
126
|
+
try:
|
|
127
|
+
stransf_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
except PermissionError:
|
|
129
|
+
print(
|
|
130
|
+
"The directory to store the spatial transformations does not have write access."
|
|
131
|
+
)
|
|
132
|
+
sys.exit()
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
out_parc_dir.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
except PermissionError:
|
|
137
|
+
print("The directory to store the parcellation does not have write access.")
|
|
138
|
+
sys.exit()
|
|
139
|
+
|
|
140
|
+
# Spatial transformation files (Temporal).
|
|
141
|
+
tmp_xfm_basename = os.path.join(stransf_dir, "temp")
|
|
142
|
+
temp_xfm_affine = tmp_xfm_basename + "_0GenericAffine.mat"
|
|
143
|
+
temp_xfm_nl = tmp_xfm_basename + "_1Warp.nii.gz"
|
|
144
|
+
temp_xfm_invnl = tmp_xfm_basename + "_1InverseWarp.nii.gz"
|
|
145
|
+
temp_xfm_invnlw = tmp_xfm_basename + "_InverseWarped.nii.gz"
|
|
146
|
+
temp_xfm_nlw = tmp_xfm_basename + "_Warped.nii.gz"
|
|
147
|
+
|
|
148
|
+
if stransf_name.endswith(".nii.gz"):
|
|
149
|
+
stransf_name = stransf_name[:-7]
|
|
150
|
+
elif stransf_name.endswith(".nii") or stransf_name.endswith(".mat"):
|
|
151
|
+
stransf_name = stransf_name[:-4]
|
|
152
|
+
|
|
153
|
+
if stransf_name.endswith("_xfm"):
|
|
154
|
+
stransf_name = stransf_name[:-4]
|
|
155
|
+
|
|
156
|
+
if "_desc-" in stransf_name:
|
|
157
|
+
affine_name = cltbids.replace_entity_value(stransf_name, {"desc": "affine"})
|
|
158
|
+
nl_name = cltbids.replace_entity_value(stransf_name, {"desc": "warp"})
|
|
159
|
+
invnl_name = cltbids.replace_entity_value(stransf_name, {"desc": "iwarp"})
|
|
160
|
+
else:
|
|
161
|
+
affine_name = stransf_name + "_desc-affine"
|
|
162
|
+
nl_name = stransf_name + "_desc-warp"
|
|
163
|
+
invnl_name = stransf_name + "_desc-iwarp"
|
|
164
|
+
|
|
165
|
+
# Affine transformation filename
|
|
166
|
+
xfm_affine = os.path.join(stransf_dir, affine_name + "_xfm.mat")
|
|
167
|
+
|
|
168
|
+
# Non-linear transformation filename
|
|
169
|
+
xfm_nl = os.path.join(stransf_dir, nl_name + "_xfm.nii.gz")
|
|
170
|
+
|
|
171
|
+
# Filename for the inverse of the Non-linear transformation
|
|
172
|
+
xfm_invnl = os.path.join(stransf_dir, invnl_name + "_xfm.nii.gz")
|
|
173
|
+
|
|
174
|
+
if not os.path.isfile(xfm_invnl) or force:
|
|
175
|
+
# Registration to MNI template
|
|
176
|
+
|
|
177
|
+
cmd_bashargs = [
|
|
178
|
+
"antsRegistrationSyN.sh",
|
|
179
|
+
"-d",
|
|
180
|
+
"3",
|
|
181
|
+
"-f",
|
|
182
|
+
t1_temp,
|
|
183
|
+
"-m",
|
|
184
|
+
t1,
|
|
185
|
+
"-t",
|
|
186
|
+
"s",
|
|
187
|
+
"-o",
|
|
188
|
+
tmp_xfm_basename + "_",
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
192
|
+
cmd_bashargs, cont_tech, cont_image
|
|
193
|
+
) # Generating container command
|
|
194
|
+
subprocess.run(
|
|
195
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
196
|
+
) # Running container command
|
|
197
|
+
|
|
198
|
+
# Changing the names
|
|
199
|
+
cmd_bashargs = ["mv", temp_xfm_affine, xfm_affine]
|
|
200
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
201
|
+
cmd_bashargs, cont_tech, cont_image
|
|
202
|
+
) # Generating container command
|
|
203
|
+
subprocess.run(
|
|
204
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
205
|
+
) # Running container command
|
|
206
|
+
|
|
207
|
+
cmd_bashargs = ["mv", temp_xfm_nl, xfm_nl]
|
|
208
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
209
|
+
cmd_bashargs, cont_tech, cont_image
|
|
210
|
+
) # Generating container command
|
|
211
|
+
subprocess.run(
|
|
212
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
213
|
+
) # Running container command
|
|
214
|
+
|
|
215
|
+
cmd_bashargs = ["mv", temp_xfm_invnl, xfm_invnl]
|
|
216
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
217
|
+
cmd_bashargs, cont_tech, cont_image
|
|
218
|
+
) # Generating container command
|
|
219
|
+
subprocess.run(
|
|
220
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
221
|
+
) # Running container command
|
|
222
|
+
|
|
223
|
+
# Deleting the warped images
|
|
224
|
+
cmd_bashargs = ["rm", tmp_xfm_basename + "*Warped.nii.gz"]
|
|
225
|
+
warped_imgs = glob(tmp_xfm_basename + "*Warped.nii.gz")
|
|
226
|
+
if len(warped_imgs) > 0:
|
|
227
|
+
for w_img in warped_imgs:
|
|
228
|
+
os.remove(w_img)
|
|
229
|
+
|
|
230
|
+
# Applying spatial transform to the atlas
|
|
231
|
+
if not os.path.isfile(out_parc):
|
|
232
|
+
|
|
233
|
+
if atlas_type == "spam":
|
|
234
|
+
# Applying spatial transform
|
|
235
|
+
cmd_bashargs = [
|
|
236
|
+
"antsApplyTransforms",
|
|
237
|
+
"-d",
|
|
238
|
+
"3",
|
|
239
|
+
"-e",
|
|
240
|
+
"3",
|
|
241
|
+
"-i",
|
|
242
|
+
atlas,
|
|
243
|
+
"-o",
|
|
244
|
+
out_parc,
|
|
245
|
+
"-r",
|
|
246
|
+
t1,
|
|
247
|
+
"-t",
|
|
248
|
+
xfm_invnl,
|
|
249
|
+
"-t",
|
|
250
|
+
"[" + xfm_affine + ",1]",
|
|
251
|
+
"-n",
|
|
252
|
+
interp,
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
256
|
+
cmd_bashargs, cont_tech, cont_image
|
|
257
|
+
) # Generating container command
|
|
258
|
+
subprocess.run(
|
|
259
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
260
|
+
) # Running container command
|
|
261
|
+
|
|
262
|
+
elif atlas_type == "maxprob":
|
|
263
|
+
# Applying spatial transform
|
|
264
|
+
cmd_bashargs = [
|
|
265
|
+
"antsApplyTransforms",
|
|
266
|
+
"-d",
|
|
267
|
+
"3",
|
|
268
|
+
"-i",
|
|
269
|
+
atlas,
|
|
270
|
+
"-o",
|
|
271
|
+
out_parc,
|
|
272
|
+
"-r",
|
|
273
|
+
t1,
|
|
274
|
+
"-t",
|
|
275
|
+
xfm_invnl,
|
|
276
|
+
"-t",
|
|
277
|
+
"[" + xfm_affine + ",1]",
|
|
278
|
+
"-n",
|
|
279
|
+
"NearestNeighbor",
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
cmd_cont = cltmisc.generate_container_command(
|
|
283
|
+
cmd_bashargs, cont_tech, cont_image
|
|
284
|
+
) # Generating container command
|
|
285
|
+
subprocess.run(
|
|
286
|
+
cmd_cont, stdout=subprocess.PIPE, universal_newlines=True
|
|
287
|
+
) # Running container command
|
|
288
|
+
|
|
289
|
+
tmp_parc = cltparc.Parcellation(parc_file=out_parc)
|
|
290
|
+
tmp_parc.save_parcellation(
|
|
291
|
+
out_file=out_parc,
|
|
292
|
+
affine=tmp_parc.affine,
|
|
293
|
+
save_lut=False,
|
|
294
|
+
save_tsv=False,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Removing the Warped images
|
|
298
|
+
if os.path.isfile(temp_xfm_invnlw):
|
|
299
|
+
os.remove(temp_xfm_invnlw)
|
|
300
|
+
|
|
301
|
+
if os.path.isfile(temp_xfm_nlw):
|
|
302
|
+
os.remove(temp_xfm_nlw)
|
|
303
|
+
|
|
304
|
+
return out_parc
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
######################################################################################################
|
|
308
|
+
@staticmethod
|
|
309
|
+
def tissue_seg_table(tsv_filename):
|
|
310
|
+
"""
|
|
311
|
+
Create standard tissue segmentation lookup table.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
tsv_filename : str
|
|
316
|
+
Output TSV file path.
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
pd.DataFrame
|
|
321
|
+
DataFrame with tissue segmentation information (CSF, GM, WM).
|
|
322
|
+
|
|
323
|
+
Examples
|
|
324
|
+
--------
|
|
325
|
+
>>> seg_df = Parcellation.tissue_seg_table('tissues.tsv')
|
|
326
|
+
>>> print(seg_df)
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
# Table for tissue segmentation
|
|
330
|
+
# 1. Default values for tissues segmentation table
|
|
331
|
+
seg_rgbcol = np.array([[172, 0, 0], [0, 153, 76], [0, 102, 204]])
|
|
332
|
+
seg_codes = np.array([1, 2, 3])
|
|
333
|
+
seg_names = ["cerebro_spinal_fluid", "gray_matter", "white_matter"]
|
|
334
|
+
seg_acron = ["CSF", "GM", "WM"]
|
|
335
|
+
|
|
336
|
+
# 2. Converting colors to hexidecimal string
|
|
337
|
+
seg_hexcol = []
|
|
338
|
+
nrows, ncols = seg_rgbcol.shape
|
|
339
|
+
for i in np.arange(0, nrows):
|
|
340
|
+
seg_hexcol.append(
|
|
341
|
+
cltmisc.rgb2hex(seg_rgbcol[i, 0], seg_rgbcol[i, 1], seg_rgbcol[i, 2])
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
seg_df = pd.DataFrame(
|
|
345
|
+
{
|
|
346
|
+
"index": seg_codes,
|
|
347
|
+
"name": seg_names,
|
|
348
|
+
"abbreviation": seg_acron,
|
|
349
|
+
"color": seg_hexcol,
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
# Save the tsv table
|
|
353
|
+
with open(tsv_filename, "w+") as tsv_file:
|
|
354
|
+
tsv_file.write(seg_df.to_csv(sep="\t", index=False))
|
|
355
|
+
|
|
356
|
+
return seg_df
|