2
2
PCA and related signal decomposition methods for tedana
3
3
"""
4
4
import logging
5
- import os .path as op
6
5
from numbers import Number
7
6
8
7
import numpy as np
@@ -48,8 +47,8 @@ def low_mem_pca(data):
48
47
49
48
50
49
def tedpca (data_cat , data_oc , combmode , mask , adaptive_mask , t2sG ,
51
- ref_img , tes , algorithm = 'mdl' , kdaw = 10. , rdaw = 1. ,
52
- out_dir = '.' , verbose = False , low_mem = False ):
50
+ io_generator , tes , algorithm = 'mdl' , kdaw = 10. , rdaw = 1. ,
51
+ verbose = False , low_mem = False ):
53
52
"""
54
53
Use principal components analysis (PCA) to identify and remove thermal
55
54
noise from multi-echo data.
@@ -73,8 +72,8 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
73
72
For more information on thresholding, see `make_adaptive_mask`.
74
73
t2sG : (S,) array_like
75
74
Map of voxel-wise T2* estimates.
76
- ref_img : :obj:`str` or img_like
77
- Reference image to dictate how outputs are saved to disk
75
+ io_generator : :obj:`tedana.io.OutputGenerator`
76
+ The output generation object for this workflow
78
77
tes : :obj:`list`
79
78
List of echo times associated with `data_cat`, in milliseconds
80
79
algorithm : {'kundu', 'kundu-stabilize', 'mdl', 'aic', 'kic', float}, optional
@@ -91,8 +90,6 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
91
90
rdaw : :obj:`float`, optional
92
91
Dimensionality augmentation weight for Rho calculations. Must be a
93
92
non-negative float, or -1 (a special value). Default is 1.
94
- out_dir : :obj:`str`, optional
95
- Output directory.
96
93
verbose : :obj:`bool`, optional
97
94
Whether to output files from fitmodels_direct or not. Default: False
98
95
low_mem : :obj:`bool`, optional
@@ -147,19 +144,20 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
147
144
Outputs:
148
145
149
146
This function writes out several files:
150
-
151
- ====================== =================================================
152
- Filename Content
153
- ====================== =================================================
154
- pca_decomposition.json PCA component table.
155
- pca_mixing.tsv PCA mixing matrix.
156
- pca_components.nii.gz Component weight maps.
157
- ====================== =================================================
147
+ =========================== =============================================
148
+ Default Filename Content
149
+ =========================== =============================================
150
+ desc-PCA_decomposition.json PCA component table
151
+ desc-PCA_mixing.tsv PCA mixing matrix
152
+ desc-PCA_components.nii.gz Component weight maps
153
+ =========================== =============================================
158
154
159
155
See Also
160
156
--------
161
- :func:`tedana.utils.make_adaptive_mask` : The function used to create the ``adaptive_mask``
162
- parameter.
157
+ :func:`tedana.utils.make_adaptive_mask` : The function used to create
158
+ the ``adaptive_mask` parameter.
159
+ :module:`tedana.constants` : The module describing the filenames for
160
+ various naming conventions
163
161
"""
164
162
if algorithm == 'kundu' :
165
163
alg_str = ("followed by the Kundu component selection decision "
@@ -204,8 +202,8 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
204
202
data_z = (data_z - data_z .mean ()) / data_z .std () # var normalize everything
205
203
206
204
if algorithm in ['mdl' , 'aic' , 'kic' ]:
207
- data_img = io .new_nii_like (ref_img , utils .unmask (data , mask ))
208
- mask_img = io .new_nii_like (ref_img , mask .astype (int ))
205
+ data_img = io .new_nii_like (io_generator . reference_img , utils .unmask (data , mask ))
206
+ mask_img = io .new_nii_like (io_generator . reference_img , mask .astype (int ))
209
207
voxel_comp_weights , varex , varex_norm , comp_ts = ma_pca (
210
208
data_img , mask_img , algorithm , normalize = True )
211
209
elif isinstance (algorithm , Number ):
@@ -231,10 +229,11 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
231
229
# Compute Kappa and Rho for PCA comps
232
230
# Normalize each component's time series
233
231
vTmixN = stats .zscore (comp_ts , axis = 0 )
234
- comptable , _ , _ , _ = metrics .dependence_metrics (
235
- data_cat , data_oc , comp_ts , adaptive_mask , tes , ref_img ,
236
- reindex = False , mmixN = vTmixN , algorithm = None ,
237
- label = 'mepca_' , out_dir = out_dir , verbose = verbose )
232
+ comptable , _ , metric_metadata , _ , _ = metrics .dependence_metrics (
233
+ data_cat , data_oc , comp_ts , adaptive_mask , tes , io_generator ,
234
+ reindex = False , mmixN = vTmixN , algorithm = None ,
235
+ label = 'PCA' , verbose = verbose
236
+ )
238
237
239
238
# varex_norm from PCA overrides varex_norm from dependence_metrics,
240
239
# but we retain the original
@@ -243,38 +242,68 @@ def tedpca(data_cat, data_oc, combmode, mask, adaptive_mask, t2sG,
243
242
comptable ['normalized variance explained' ] = varex_norm
244
243
245
244
# write component maps to 4D image
246
- comp_ts_z = stats .zscore (comp_ts , axis = 0 )
247
- comp_maps = utils .unmask (computefeats2 (data_oc , comp_ts_z , mask ), mask )
248
- io .filewrite (comp_maps , op .join (out_dir , 'pca_components.nii.gz' ), ref_img )
245
+ comp_maps = utils .unmask (computefeats2 (data_oc , comp_ts , mask ), mask )
246
+ io_generator .save_file (comp_maps , 'z-scored PCA components img' )
249
247
250
248
# Select components using decision tree
251
249
if algorithm == 'kundu' :
252
- comptable = kundu_tedpca (comptable , n_echos , kdaw , rdaw , stabilize = False )
250
+ comptable , metric_metadata = kundu_tedpca (
251
+ comptable ,
252
+ metric_metadata ,
253
+ n_echos ,
254
+ kdaw ,
255
+ rdaw ,
256
+ stabilize = False ,
257
+ )
253
258
elif algorithm == 'kundu-stabilize' :
254
- comptable = kundu_tedpca (comptable , n_echos , kdaw , rdaw , stabilize = True )
259
+ comptable , metric_metadata = kundu_tedpca (
260
+ comptable ,
261
+ metric_metadata ,
262
+ n_echos ,
263
+ kdaw ,
264
+ rdaw ,
265
+ stabilize = True ,
266
+ )
255
267
else :
256
268
alg_str = "variance explained-based" if isinstance (algorithm , Number ) else algorithm
257
269
LGR .info ('Selected {0} components with {1} dimensionality '
258
270
'detection' .format (comptable .shape [0 ], alg_str ))
259
271
comptable ['classification' ] = 'accepted'
260
272
comptable ['rationale' ] = ''
261
273
262
- # Save decomposition
274
+ # Save decomposition files
263
275
comp_names = [io .add_decomp_prefix (comp , prefix = 'pca' , max_value = comptable .index .max ())
264
276
for comp in comptable .index .values ]
265
277
266
278
mixing_df = pd .DataFrame (data = comp_ts , columns = comp_names )
267
- mixing_df .to_csv (op .join (out_dir , 'pca_mixing.tsv' ), sep = '\t ' , index = False )
268
-
269
- comptable ['Description' ] = 'PCA fit to optimally combined data.'
270
- mmix_dict = {}
271
- mmix_dict ['Method' ] = ('Principal components analysis implemented by '
272
- 'sklearn. Components are sorted by variance '
273
- 'explained in descending order. '
274
- 'Component signs are flipped to best match the '
275
- 'data.' )
276
- io .save_comptable (comptable , op .join (out_dir , 'pca_decomposition.json' ),
277
- label = 'pca' , metadata = mmix_dict )
279
+ io_generator .save_file (mixing_df , "PCA mixing tsv" )
280
+
281
+ # Save component table and associated json
282
+ temp_comptable = comptable .set_index ("Component" , inplace = False )
283
+ io_generator .save_file (temp_comptable , "PCA metrics tsv" )
284
+
285
+ metric_metadata ["Component" ] = {
286
+ "LongName" : "Component identifier" ,
287
+ "Description" : (
288
+ "The unique identifier of each component. "
289
+ "This identifier matches column names in the mixing matrix TSV file."
290
+ ),
291
+ }
292
+ io_generator .save_file (metric_metadata , "PCA metrics json" )
293
+
294
+ decomp_metadata = {
295
+ "Method" : (
296
+ "Principal components analysis implemented by sklearn. "
297
+ "Components are sorted by variance explained in descending order. "
298
+ "Component signs are flipped to best match the data."
299
+ ),
300
+ }
301
+ for comp_name in comp_names :
302
+ decomp_metadata [comp_name ] = {
303
+ "Description" : "PCA fit to optimally combined data." ,
304
+ "Method" : "tedana" ,
305
+ }
306
+ io_generator .save_file (decomp_metadata , "PCA decomposition json" )
278
307
279
308
acc = comptable [comptable .classification == 'accepted' ].index .values
280
309
n_components = acc .size
0 commit comments