11"""Spatial regularization functions as developed in Total Activation."""
22
3+ import nibabel as nib
34import numpy as np
45from nilearn .masking import apply_mask , unmask
56
@@ -39,8 +40,11 @@ def spatial_tikhonov(estimates, data, masker, niter, dim, lambda_, mu):
3940 Estimates of activity-inducing or innovation signal after spatial regularization.
4041 """
4142 # Transform data from 2D into 4D
42- estimates_vol = masker .inverse_transform (estimates )
43- data_vol = masker .inverse_trasnform (data )
43+ # NiftiMasker expects (n_samples, n_features), but input is (n_features, n_samples)
44+ estimates_img = masker .inverse_transform (estimates .T )
45+ data_img = masker .inverse_transform (data .T )
46+ estimates_vol = estimates_img .get_fdata ()
47+ data_vol = data_img .get_fdata ()
4448
4549 if dim == 2 :
4650 h = generate_delta (dim = dim )
@@ -61,13 +65,13 @@ def spatial_tikhonov(estimates, data, masker, niter, dim, lambda_, mu):
6165 estimates_vol [:, :, slice_idx , time_idx ],
6266 (estimates_vol .shape [0 ], estimates_vol .shape [1 ]),
6367 )
64- )
68+ ). real
6569 )
6670
6771 elif dim == 3 :
6872 h = generate_delta (dim = dim )
6973
70- h = np .fft .fftn (h , estimates_vol .shape [:2 ])
74+ h = np .fft .fftn (h , estimates_vol .shape [:3 ])
7175
7276 for iter_idx in range (niter ):
7377 for time_idx in range (estimates_vol .shape [- 1 ]):
@@ -79,11 +83,17 @@ def spatial_tikhonov(estimates, data, masker, niter, dim, lambda_, mu):
7983 * np .fft .ifftn (
8084 h
8185 * np .conj (h )
82- * np .fft .fftn (estimates_vol [:, :, :, time_idx ], estimates_vol .shape [:2 ])
83- )
86+ * np .fft .fftn (estimates_vol [:, :, :, time_idx ], estimates_vol .shape [:3 ])
87+ ). real
8488 )
8589
86- final_estimates = masker .fit_trasform (estimates_vol )
90+ # Take real part (FFT operations can introduce small imaginary components)
91+ estimates_vol = np .real (estimates_vol )
92+
93+ # Create image from array and transform back to 2D
94+ estimates_img_out = nib .Nifti1Image (estimates_vol , estimates_img .affine )
95+ # transform returns (n_samples, n_features), we need (n_features, n_samples)
96+ final_estimates = masker .transform (estimates_img_out ).T
8797
8898 return final_estimates
8999
@@ -121,16 +131,21 @@ def spatial_structured_sparsity(estimates, data, mask, niter, dims, lambda_):
121131 Estimates of activity-inducing or innovation signal after spatial regularization.
122132 """
123133 # Transform data from 2D into 4D
124- estimates_vol = unmask (estimates , mask )
125- data_vol = unmask (data , mask )
134+ # unmask expects (samples, features) so we transpose (n_features, n_samples) -> (n_samples, n_features)
135+ # unmask returns nibabel image, need to get data array
136+ estimates_vol = unmask (estimates .T , mask ).get_fdata ()
137+ data_vol = unmask (data .T , mask ).get_fdata ()
138+
139+ # Get mask array for clip function
140+ mask_data = mask .get_fdata ().astype (int )
126141
127142 z = np .zeros (estimates_vol .shape )
128143
129144 h = generate_delta (dim = 3 )
130145
131146 max_eig = 144
132147
133- h = np .fft .fftn (h , estimates_vol .shape [:2 ])
148+ h = np .fft .fftn (h , estimates_vol .shape [:3 ])
134149
135150 # Perform structured sparsity regularization
136151 for time_idx in range (estimates_vol .shape [- 1 ]):
@@ -139,17 +154,21 @@ def spatial_structured_sparsity(estimates, data, mask, niter, dims, lambda_):
139154 z [:, :, :, time_idx ]
140155 + 1
141156 / (lambda_ * max_eig )
142- * np .fft .ifftn (h * np .fft .fftn (data_vol [:, :, :, time_idx ], dims [:2 ]))
143- - np .fft .ifftn (h * np .conj (h ) * np .fft .fftn (z [:, :, :, time_idx ], dims [:2 ]))
157+ * np .fft .ifftn (h * np .fft .fftn (data_vol [:, :, :, time_idx ], dims [:3 ])). real
158+ - np .fft .ifftn (h * np .conj (h ) * np .fft .fftn (z [:, :, :, time_idx ], dims [:3 ])). real
144159 / max_eig ,
145- mask ,
160+ mask_data ,
146161 )
147- estimates_vol [:, :, :, time_idx ] = data_vol [:, :, :, time_idx ] - lambda_ * np .fft .ifftn (
148- np .conj (h ) * np .fft .fttn (z [:, :, :, time_idx ], dims [:2 ])
162+ estimates_vol [:, :, :, time_idx ] = (
163+ data_vol [:, :, :, time_idx ]
164+ - lambda_ * np .fft .ifftn (np .conj (h ) * np .fft .fftn (z [:, :, :, time_idx ], dims [:3 ])).real
149165 )
150166
151167 # Transform data from 4D into 2D
152- final_estimates = apply_mask (estimates_vol , mask )
168+ # Create nibabel image from array, then apply_mask
169+ # apply_mask returns (samples, features), we need (features, samples) so transpose
170+ estimates_img_out = nib .Nifti1Image (estimates_vol , mask .affine )
171+ final_estimates = apply_mask (estimates_img_out , mask ).T
153172
154173 return final_estimates
155174
0 commit comments