-
Notifications
You must be signed in to change notification settings - Fork 0
/
nidit.py
282 lines (201 loc) · 10.9 KB
/
nidit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Feb 10 14:03:14 2020
@author: jwiesner
"""
import numpy as np
from nilearn.image import new_img_like, iter_img, math_img
from nilearn.masking import apply_mask,unmask
from nilearn.regions import connected_regions
from nilearn import plotting
from sklearn.utils import check_random_state
def region_standardize(stat_img,mask_img,extract_type='connected_components',min_region_size=1,
smoothing_fwhm=None,ignore_zeros=True,verbose=False):
"""Standardizes a statistical image region-wise.
Regions in the statistical image will be extracted using nilearn.regions.connected_regions.
Voxels are then z-standardized according to the region-wise mean and standard deviation.
By default this function is set to be exhaustive (using extract_type = 'connected_components'
and min_region_size=1) that means every voxel is assigned to one particular region.
If a region contains of only one voxel, the voxel is standardized according to the mean and
standard deviation of the whole brain.
Voxels that have not been assigned to a region are also standardized according to the mean
and standard deviation of the whole brain.
Parameters
----------
stat_img: Niimg-like object
A statistical image.
extract_type: str {‘connected_components’, ‘local_regions’}, optional
See nilearn.masking.connected_regions
Default: 'connected_components'
min_region_size: int, optional
See nilearn.masking.connected_regions
Default: 1 mm^3
smoothing_fwhm: int, optional
See nilearn.masking.connected_regions
Default: None
mask_img: Niimg-like object
A binary image used to mask the data.
ignore_zeros: boolean
If True, zero-voxels inside the statistical image will be ignored
when calculating descriptive statistics. This makes sense if the
statistical image contains lots of zero voxels (e.g. because
it is already thresholded).
verbose: boolean
Print information region sizes and percentage of overall assigned voxels
Returns
-------
region_standardized_img: Niimg-like object
The region standardized version of the original statistical image.
region_sizes: list
A list showing how many regions where extracted (length of the list)
and how many voxels are assigned to each region.
See also
--------
nilearn.regions.connected_regions
"""
# find regions in statistical image
region_imgs,indices = connected_regions(stat_img,
extract_type=extract_type,
min_region_size=min_region_size,
smoothing_fwhm=smoothing_fwhm)
region_sizes = []
n_assigned_voxels = 0
stat_img_data = np.asarray(stat_img.dataobj)
# get mean and standard deviation of whole brain
stat_img_data_masked = apply_mask(stat_img,mask_img)
if ignore_zeros is True:
stat_img_data_masked = stat_img_data_masked[np.nonzero(stat_img_data_masked)]
whole_brain_mean = stat_img_data_masked.mean()
whole_brain_std = stat_img_data_masked.std()
region_standardized_stat_img_data = np.zeros_like(stat_img_data)
region_standardized_indices = np.zeros_like(stat_img_data)
# z-standardize values region-wise
for idx,region_img in enumerate(iter_img(region_imgs)):
region_img_data = region_img.get_fdata()
# for user infomration get size of every region and count overall number
# of voxels that have been assigned to a region
region_img_size = np.count_nonzero(region_img_data)
region_sizes.append(region_img_size)
n_assigned_voxels += region_img_size
# get region indices and corresponding values
region_img_indices = np.where(region_img_data != 0 )
stat_img_data_region_values = stat_img_data[region_img_indices]
# save indices
region_standardized_indices[region_img_indices] = 1
# get the size of each region
region_size = len(region_img_indices[0])
# if region consists of more than one voxel calculate the mean and
# standard deviation from all voxels within that region
if region_size > 1:
stat_img_data_region_mean = stat_img_data_region_values.mean()
stat_img_data_region_std = stat_img_data_region_values.std()
# if region consists of only one voxel, standardize that voxel
# according to mean and standard deviation of whole brain
else:
stat_img_data_region_mean = whole_brain_mean
stat_img_data_region_std = whole_brain_std
# z-standardize region values
stat_img_data_region_values_std = ((stat_img_data_region_values - stat_img_data_region_mean) / stat_img_data_region_std)
# impute z-standardized values into predefined region standardized data array
region_standardized_stat_img_data[region_img_indices] = stat_img_data_region_values_std
# unassigned voxels are standardized according to whole brain mean and sd
# get indices of voxels that were not assigned to a region
# FIXME: Currently function ignores zero-voxels that means zero-voxels
# within the brain are also ignored. Allow to decide if zero-voxels should
# be ignored ore not by using ignore_zeros = True/False. For that you
# have to work with masked data (zero voxels outside brain should
# always be ignored). Masked data is a 1D array so you have to work with
# transformations from 1D to 3D.
unassigned_voxel_indices = np.where((region_standardized_indices != 1) & (stat_img_data != 0))
unassigned_voxel_values = stat_img_data[unassigned_voxel_indices]
unassigned_voxel_values_std = ((unassigned_voxel_values - whole_brain_mean) / whole_brain_std)
region_standardized_stat_img_data[unassigned_voxel_indices] = unassigned_voxel_values_std
# create image from region standardized data
region_standardized_img = new_img_like(stat_img,region_standardized_stat_img_data)
if verbose == True:
for region_idx in range(0,len(region_sizes)):
print('Region {}: {} voxels'.format(region_idx,region_sizes[region_idx]))
percentage_assigned_voxels = n_assigned_voxels / np.count_nonzero(stat_img_data) * 100
print('{} % of all voxels have been assigned to a region.\n'.format(round(percentage_assigned_voxels,2)))
return region_standardized_img,region_sizes
def scramble_img(img,mask_img,random_state=None):
'''Shuffles the voxel values within a Niimg-like object
img: Niimg-like object
Input image where the voxels should be randomly shuffled.
mask_img: Niimg-like object
A mask which masks the data to brain space
random_state: int, RandomState instance or None, optional, default=None
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used by np.random.
Used when shuffle == True.
'''
rng = check_random_state(random_state)
# mask image to MNI152 template
img_data = apply_mask(img,mask_img).flatten()
n_voxels = img_data.shape[0]
# shuffle data
img_data_scrambled = rng.choice(img_data,n_voxels,replace=False)
# transform back to image
img_scrambled = unmask(img_data_scrambled,mask_img)
return img_scrambled
def _get_voxel_volume(voxel_sizes) :
result = 1
for dim in voxel_sizes:
result *= dim
return result
def cluster_binary_img(binary_img,mask_img,min_region_size='exhaustive'):
# get voxel resolution in binary_img
# NOTE: function currently assumes equal width in x,y,z direction
voxel_sizes = binary_img.header.get_zooms()
# if not specfied by user, cluster exhaustive, i.e. assign each and every
# voxel to one and only one cluster
if min_region_size == 'exhaustive':
min_region_size_ = _get_voxel_volume(voxel_sizes) - 1
else:
min_region_size_ = min_region_size
# count overall number of 1s in the binary image
total_n_voxels = np.count_nonzero(binary_img.get_fdata())
# extract clusters in binary image
cluster_imgs,indices = connected_regions(maps_img=binary_img,
min_region_size=min_region_size_,
extract_type='connected_components',
smoothing_fwhm=None,
mask_img=mask_img)
# Get sizes of clusters (number of voxels that have been assigned to each region)
# As a sanity check + for user information get size of every region and
# count overall number of voxels that have been assigned to that region
cluster_sizes = []
total_n_voxels_assigned = 0
for idx,cluster_img in enumerate(iter_img(cluster_imgs)):
cluster_img_data = cluster_img.get_fdata()
cluster_img_size = np.count_nonzero(cluster_img_data)
cluster_sizes.append(cluster_img_size)
total_n_voxels_assigned += cluster_img_size
if total_n_voxels_assigned != total_n_voxels:
raise ValueError('Number of voxels in output clustered image is different from total number of voxels in input binary image ')
# Collapse the extracted cluster images to one cluster atlas image
cluster_imgs_labeled = []
for idx,cluster_img in enumerate(iter_img(cluster_imgs),start=1):
cluster_img_labeled = math_img(f"np.where(cluster_img == 1,{idx},cluster_img)",cluster_img=cluster_img)
cluster_imgs_labeled.append(cluster_img_labeled)
cluster_img_atlas = math_img("np.sum(imgs,axis=3)",imgs=cluster_imgs_labeled)
# plot the cluster atlas image
plotting.plot_roi(cluster_img_atlas,
title='Clustered Binary Image',
draw_cross=False)
return cluster_sizes,cluster_img_atlas
def swap_img_data(img,mask_img,absolute_values=True):
'''Swap data in a nifti image. User can choose to do swapping by taking
the real or absolute values in the image into account'''
img_data = apply_mask(img,mask_img)
if absolute_values == True:
map_dict = dict(zip(sorted(set(img_data),key=abs),sorted(set(img_data),key=abs,reverse=True)))
else:
map_dict = dict(zip(sorted(set(img_data)),sorted(set(img_data),reverse=True)))
img_data_swapped = np.array([map_dict[x] for x in img_data])
img_swapped = unmask(img_data_swapped,mask_img)
return img_swapped
if __name__ == '__main__':
pass