antspymm 1.1.2__py3-none-any.whl → 1.1.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.
- antspymm/__init__.py +3 -0
- antspymm/mm.py +704 -35
- {antspymm-1.1.2.dist-info → antspymm-1.1.3.dist-info}/METADATA +18 -2
- antspymm-1.1.3.dist-info/RECORD +7 -0
- {antspymm-1.1.2.dist-info → antspymm-1.1.3.dist-info}/WHEEL +1 -1
- antspymm-1.1.2.dist-info/RECORD +0 -7
- {antspymm-1.1.2.dist-info → antspymm-1.1.3.dist-info}/LICENSE +0 -0
- {antspymm-1.1.2.dist-info → antspymm-1.1.3.dist-info}/top_level.txt +0 -0
antspymm/__init__.py
CHANGED
@@ -56,6 +56,8 @@ from .mm import average_mm_df
|
|
56
56
|
from .mm import get_names_from_data_frame
|
57
57
|
from .mm import read_mm_csv
|
58
58
|
from .mm import assemble_modality_specific_dataframes
|
59
|
+
from .mm import aggregate_antspymm_results
|
60
|
+
from .mm import aggregate_antspymm_results_sdf
|
59
61
|
from .mm import mc_denoise
|
60
62
|
from .mm import mc_reg
|
61
63
|
from .mm import dti_reg
|
@@ -93,3 +95,4 @@ from .mm import merge_wides_to_study_dataframe
|
|
93
95
|
from .mm import map_scalar_to_labels
|
94
96
|
from .mm import template_figure_with_overlay
|
95
97
|
from .mm import brainmap_figure
|
98
|
+
from .mm import bold_perfusion
|
antspymm/mm.py
CHANGED
@@ -79,6 +79,8 @@ __all__ = ['version',
|
|
79
79
|
'novelty_detection_loop',
|
80
80
|
'novelty_detection_quantile',
|
81
81
|
'generate_mm_dataframe',
|
82
|
+
'aggregate_antspymm_results',
|
83
|
+
'aggregate_antspymm_results_sdf',
|
82
84
|
'study_dataframe_from_matched_dataframe',
|
83
85
|
'merge_wides_to_study_dataframe',
|
84
86
|
'wmh']
|
@@ -159,11 +161,11 @@ def get_valid_modalities( long=False, asString=False, qc=False ):
|
|
159
161
|
asString - concat list to string
|
160
162
|
"""
|
161
163
|
if long:
|
162
|
-
mymod = ["T1w", "NM2DMT", "rsfMRI", "rsfMRI_LR", "rsfMRI_RL", "DTI", "DTI_LR","DTI_RL","T2Flair", "dwi", "func" ]
|
164
|
+
mymod = ["T1w", "NM2DMT", "rsfMRI", "rsfMRI_LR", "rsfMRI_RL", "DTI", "DTI_LR","DTI_RL","T2Flair", "dwi", "func", "perf" ]
|
163
165
|
elif qc:
|
164
|
-
mymod = [ 'T1w', 'T2Flair', 'NM2DMT','DTIdwi','DTIb0', 'rsfMRI']
|
166
|
+
mymod = [ 'T1w', 'T2Flair', 'NM2DMT','DTIdwi','DTIb0', 'rsfMRI', "perf" ]
|
165
167
|
else:
|
166
|
-
mymod = ["T1w", "NM2DMT", "DTI","T2Flair", "rsfMRI" ]
|
168
|
+
mymod = ["T1w", "NM2DMT", "DTI","T2Flair", "rsfMRI", "perf" ]
|
167
169
|
if not asString:
|
168
170
|
return mymod
|
169
171
|
else:
|
@@ -184,7 +186,8 @@ def generate_mm_dataframe(
|
|
184
186
|
flair_filename=[],
|
185
187
|
rsf_filenames=[],
|
186
188
|
dti_filenames=[],
|
187
|
-
nm_filenames=[]
|
189
|
+
nm_filenames=[],
|
190
|
+
perf_filename=[]
|
188
191
|
):
|
189
192
|
from os.path import exists
|
190
193
|
valid_modalities = get_valid_modalities()
|
@@ -219,6 +222,17 @@ def generate_mm_dataframe(
|
|
219
222
|
flair_filename=flair_filename[0]
|
220
223
|
if flair_filename is not None and not "lair" in flair_filename:
|
221
224
|
raise ValueError("flair is not flair filename " + flair_filename)
|
225
|
+
## perfusion
|
226
|
+
if perf_filename is not None:
|
227
|
+
if isinstance(perf_filename,list):
|
228
|
+
if (len(perf_filename) == 0):
|
229
|
+
perf_filename=None
|
230
|
+
else:
|
231
|
+
print("Take first entry from perf_filename list")
|
232
|
+
perf_filename=perf_filename[0]
|
233
|
+
if perf_filename is not None and not "perf" in perf_filename:
|
234
|
+
raise ValueError("perf_filename is not perf filename " + perf_filename)
|
235
|
+
|
222
236
|
for k in nm_filenames:
|
223
237
|
if k is not None:
|
224
238
|
if not "NM" in k:
|
@@ -231,7 +245,10 @@ def generate_mm_dataframe(
|
|
231
245
|
if k is not None:
|
232
246
|
if not "fMRI" in k and not "func" in k:
|
233
247
|
raise ValueError("rsfMRI/func is not rsfmri filename " + k)
|
234
|
-
|
248
|
+
if perf_filename is not None:
|
249
|
+
if not "perf" in perf_filename:
|
250
|
+
raise ValueError("perf_filename is not a valid perfusion (perf) filename " + k)
|
251
|
+
allfns = [t1_filename] + [flair_filename] + nm_filenames + dti_filenames + rsf_filenames + [perf_filename]
|
235
252
|
for k in allfns:
|
236
253
|
if k is not None:
|
237
254
|
if not isinstance(k, str):
|
@@ -247,7 +264,8 @@ def generate_mm_dataframe(
|
|
247
264
|
source_image_directory,
|
248
265
|
output_image_directory,
|
249
266
|
t1_filename,
|
250
|
-
flair_filename
|
267
|
+
flair_filename,
|
268
|
+
perf_filename]
|
251
269
|
mydata0 = coredata + rsf_filenames + dti_filenames
|
252
270
|
mydata = mydata0 + nm_filenames
|
253
271
|
corecols = [
|
@@ -259,7 +277,8 @@ def generate_mm_dataframe(
|
|
259
277
|
'sourcedir',
|
260
278
|
'outputdir',
|
261
279
|
'filename',
|
262
|
-
'flairid'
|
280
|
+
'flairid',
|
281
|
+
'perfid']
|
263
282
|
mycols0 = corecols + [
|
264
283
|
'rsfid1', 'rsfid2',
|
265
284
|
'dtid1', 'dtid2']
|
@@ -268,10 +287,6 @@ def generate_mm_dataframe(
|
|
268
287
|
'nmid6', 'nmid7','nmid8', 'nmid9', 'nmid10', 'nmid11'
|
269
288
|
]
|
270
289
|
mycols = mycols0 + nmext
|
271
|
-
print(len(mydata0))
|
272
|
-
print(len(nm_filenames))
|
273
|
-
print(len(mycols0))
|
274
|
-
print(len(nmext))
|
275
290
|
studycsv = pd.DataFrame([ mydata ],
|
276
291
|
columns=mycols)
|
277
292
|
return studycsv
|
@@ -343,6 +358,10 @@ def nrg_2_bids( nrg_filename ):
|
|
343
358
|
bids_modality_folder = 'func'
|
344
359
|
bids_modality_filename = 'func'
|
345
360
|
|
361
|
+
if nrg_modality == 'perf' :
|
362
|
+
bids_modality_folder = 'perf'
|
363
|
+
bids_modality_filename = 'perf'
|
364
|
+
|
346
365
|
bids_suffix = nrg_suffix[1:]
|
347
366
|
bids_filename = f'{bids_subject}_{bids_session}_{bids_modality_filename}.{bids_suffix}'
|
348
367
|
|
@@ -385,6 +404,9 @@ def bids_2_nrg( bids_filename, project_name, date, nrg_modality=None ):
|
|
385
404
|
if bids_modality == 'func' and nrg_modality is None :
|
386
405
|
nrg_modality = 'rsfMRI'
|
387
406
|
|
407
|
+
if bids_modality == 'perf' and nrg_modality is None :
|
408
|
+
nrg_modality = 'perf'
|
409
|
+
|
388
410
|
nrg_suffix = bids_suffix[1:]
|
389
411
|
nrg_filename = f'{project_name}-{nrg_subject_id}-{date}-{nrg_modality}-{nrg_image_id}.{nrg_suffix}'
|
390
412
|
|
@@ -2308,7 +2330,9 @@ def get_average_rsf( x, min_t=10, max_t=35 ):
|
|
2308
2330
|
bavg = bavg + ants.registration(oavg,b0,'Rigid',outprefix=ofn)['warpedmovout']
|
2309
2331
|
import shutil
|
2310
2332
|
shutil.rmtree(output_directory, ignore_errors=True )
|
2311
|
-
|
2333
|
+
bavg = ants.iMath( bavg, 'Normalize' )
|
2334
|
+
return bavg
|
2335
|
+
# return ants.n4_bias_field_correction(bavg, mask=ants.get_mask( bavg ) )
|
2312
2336
|
|
2313
2337
|
|
2314
2338
|
def get_average_dwi_b0( x, fixed_b0=None, fixed_dwi=None, fast=False ):
|
@@ -4335,7 +4359,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
|
|
4335
4359
|
reg_iterations=[40,20,5] )
|
4336
4360
|
if verbose:
|
4337
4361
|
print("End rsfmri motion correction")
|
4338
|
-
# ants.image_write( corrmo['motion_corrected'], '/tmp/temp.nii.gz' )
|
4339
4362
|
|
4340
4363
|
mytsnr = tsnr( corrmo['motion_corrected'], bmask )
|
4341
4364
|
mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
|
@@ -4345,8 +4368,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
|
|
4345
4368
|
t1reg = ants.registration( und, t1, "SyNBold" )
|
4346
4369
|
if verbose:
|
4347
4370
|
print("t1 2 bold done")
|
4348
|
-
# ants.image_write( und, '/tmp/template_bold_masked.nii.gz' )
|
4349
|
-
# ants.image_write( t1reg['warpedmovout'], '/tmp/t1tobold.nii.gz' )
|
4350
4371
|
boldseg = ants.apply_transforms( und, t1segmentation,
|
4351
4372
|
t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
|
4352
4373
|
gmseg = ants.threshold_image( t1segmentation, 2, 2 )
|
@@ -4524,6 +4545,216 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
|
|
4524
4545
|
return outdict
|
4525
4546
|
|
4526
4547
|
|
4548
|
+
def bold_perfusion( fmri, fmri_template, t1head, t1, t1segmentation, t1dktcit, f=[0.0,math.inf], FD_threshold=0.5, spa = 1.5, nc = 6, type_of_transform='Rigid', tc='alternating', deepmask=False, add_FD_to_nuisance=False, verbose=False ):
|
4549
|
+
"""
|
4550
|
+
Estimate perfusion from a BOLD time series image. Will attempt to figure out the T-C labels from the data.
|
4551
|
+
|
4552
|
+
Arguments
|
4553
|
+
---------
|
4554
|
+
fmri : BOLD fmri antsImage
|
4555
|
+
|
4556
|
+
fmri_template : reference space for BOLD
|
4557
|
+
|
4558
|
+
t1head : ANTsImage
|
4559
|
+
input 3-D T1 brain image (not brain extracted)
|
4560
|
+
|
4561
|
+
t1 : ANTsImage
|
4562
|
+
input 3-D T1 brain image (brain extracted)
|
4563
|
+
|
4564
|
+
t1segmentation : ANTsImage
|
4565
|
+
t1 segmentation - a six tissue segmentation image in T1 space
|
4566
|
+
|
4567
|
+
t1dktcit : ANTsImage
|
4568
|
+
t1 dkt cortex plus cit parcellation
|
4569
|
+
|
4570
|
+
f : band pass limits for frequency filtering
|
4571
|
+
|
4572
|
+
spa : gaussian smoothing for spatial and temporal component e.g. (1,1,1,0) in physical space coordinates
|
4573
|
+
|
4574
|
+
nc : number of components for compcor filtering
|
4575
|
+
|
4576
|
+
type_of_transform : SyN or Rigid
|
4577
|
+
|
4578
|
+
tc: string either alternating or split (default is alternating ie CTCTCT; split is CCCCTTTT)
|
4579
|
+
|
4580
|
+
deepmask: boolean
|
4581
|
+
|
4582
|
+
add_FD_to_nuisance: boolean
|
4583
|
+
|
4584
|
+
verbose : boolean
|
4585
|
+
|
4586
|
+
Returns
|
4587
|
+
---------
|
4588
|
+
a dictionary containing the derived network maps
|
4589
|
+
|
4590
|
+
"""
|
4591
|
+
import numpy as np
|
4592
|
+
import pandas as pd
|
4593
|
+
import re
|
4594
|
+
import math
|
4595
|
+
from sklearn.linear_model import LinearRegression
|
4596
|
+
|
4597
|
+
ex_path = os.path.expanduser( "~/.antspyt1w/" )
|
4598
|
+
cnxcsvfn = ex_path + "dkt_cortex_cit_deep_brain.csv"
|
4599
|
+
|
4600
|
+
def replicate_list(user_list, target_size):
|
4601
|
+
# Calculate the number of times the list should be replicated
|
4602
|
+
replication_factor = target_size // len(user_list)
|
4603
|
+
# Replicate the list and handle any remaining elements
|
4604
|
+
replicated_list = user_list * replication_factor
|
4605
|
+
remaining_elements = target_size % len(user_list)
|
4606
|
+
replicated_list += user_list[:remaining_elements]
|
4607
|
+
return replicated_list
|
4608
|
+
|
4609
|
+
def one_hot_encode(char_list):
|
4610
|
+
unique_chars = list(set(char_list))
|
4611
|
+
encoding_dict = {char: [1 if char == c else 0 for c in unique_chars] for char in unique_chars}
|
4612
|
+
encoded_matrix = np.array([encoding_dict[char] for char in char_list])
|
4613
|
+
return encoded_matrix
|
4614
|
+
|
4615
|
+
A = np.zeros((1,1))
|
4616
|
+
fmri = ants.iMath( fmri, 'Normalize' )
|
4617
|
+
if deepmask:
|
4618
|
+
bmask = antspynet.brain_extraction( fmri_template, 'bold' ).threshold_image(0.5,1).morphology("close",2).iMath("FillHoles")
|
4619
|
+
else:
|
4620
|
+
rig = ants.registration( fmri_template, t1head, 'BOLDRigid' )
|
4621
|
+
bmask = ants.apply_transforms( fmri_template, ants.threshold_image(t1segmentation,1,6), rig['fwdtransforms'][0], interpolator='genericLabel' )
|
4622
|
+
if verbose:
|
4623
|
+
print("Begin perfusion motion correction")
|
4624
|
+
mytrim=4 # trim will guarantee an even length
|
4625
|
+
if fmri.shape[3] % 2 == 1:
|
4626
|
+
mytrim = 5
|
4627
|
+
corrmo = timeseries_reg(
|
4628
|
+
fmri, fmri_template,
|
4629
|
+
type_of_transform=type_of_transform,
|
4630
|
+
total_sigma=0.0,
|
4631
|
+
fdOffset=2.0,
|
4632
|
+
trim = mytrim,
|
4633
|
+
output_directory=None,
|
4634
|
+
verbose=verbose,
|
4635
|
+
syn_metric='cc',
|
4636
|
+
syn_sampling=2,
|
4637
|
+
reg_iterations=[40,20,5] )
|
4638
|
+
if verbose:
|
4639
|
+
print("End rsfmri motion correction")
|
4640
|
+
|
4641
|
+
ntp = corrmo['motion_corrected'].shape[3]
|
4642
|
+
if tc == 'alternating':
|
4643
|
+
tclist = replicate_list( ['C','T'], ntp )
|
4644
|
+
else:
|
4645
|
+
tclist = replicate_list( ['C'], int(ntp/2) ) + replicate_list( ['T'], int(ntp/2) )
|
4646
|
+
|
4647
|
+
tclist = one_hot_encode( tclist )
|
4648
|
+
mytsnr = tsnr( corrmo['motion_corrected'], bmask )
|
4649
|
+
mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
|
4650
|
+
tsnrmask = ants.threshold_image( mytsnr, 0, mytsnrThresh ).morphology("close",2)
|
4651
|
+
bmask = bmask * ants.iMath( tsnrmask, "FillHoles" )
|
4652
|
+
und = fmri_template * bmask
|
4653
|
+
t1reg = ants.registration( und, t1, "SyNBold" )
|
4654
|
+
if verbose:
|
4655
|
+
print("t1 2 bold done")
|
4656
|
+
boldseg = ants.apply_transforms( und, t1segmentation,
|
4657
|
+
t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
|
4658
|
+
dktseg = ants.apply_transforms( und, t1dktcit,
|
4659
|
+
t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
|
4660
|
+
gmseg = ants.threshold_image( t1segmentation, 2, 2 )
|
4661
|
+
gmseg = gmseg + ants.threshold_image( t1segmentation, 4, 4 )
|
4662
|
+
gmseg = ants.threshold_image( gmseg, 1, 4 )
|
4663
|
+
gmseg = ants.iMath( gmseg, 'MD', 1 )
|
4664
|
+
gmseg = ants.apply_transforms( und, gmseg,
|
4665
|
+
t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
|
4666
|
+
csfAndWM = ( ants.threshold_image( t1segmentation, 1, 1 ) +
|
4667
|
+
ants.threshold_image( t1segmentation, 3, 3 ) ).morphology("erode",1)
|
4668
|
+
csfAndWM = ants.apply_transforms( und, csfAndWM,
|
4669
|
+
t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
|
4670
|
+
|
4671
|
+
mycompcor = ants.compcor( corrmo['motion_corrected'],
|
4672
|
+
ncompcor=nc, quantile=0.90, mask = csfAndWM,
|
4673
|
+
filter_type='polynomial', degree=2 )
|
4674
|
+
|
4675
|
+
nt = corrmo['motion_corrected'].shape[3]
|
4676
|
+
tr = ants.get_spacing( corrmo['motion_corrected'] )[3]
|
4677
|
+
highMotionTimes = np.where( corrmo['FD'] >= 1.0 )
|
4678
|
+
goodtimes = np.where( corrmo['FD'] < 0.5 )
|
4679
|
+
simg = ants.smooth_image(corrmo['motion_corrected'],
|
4680
|
+
spa, sigma_in_physical_coordinates = True )
|
4681
|
+
|
4682
|
+
nuisance = mycompcor[ 'components' ]
|
4683
|
+
nuisance = np.c_[ nuisance, mycompcor['basis'] ]
|
4684
|
+
if add_FD_to_nuisance:
|
4685
|
+
nuisance = np.c_[ nuisance, corrmo['FD'] ]
|
4686
|
+
|
4687
|
+
if verbose:
|
4688
|
+
print("make sure nuisance is independent of TC")
|
4689
|
+
nuisance = ants.regress_components( nuisance, tclist )
|
4690
|
+
|
4691
|
+
regression_mask = bmask.clone()
|
4692
|
+
gmmat = ants.timeseries_to_matrix( simg, regression_mask )
|
4693
|
+
if f[0] > 0 and f[1] < 1: # some would argue against this
|
4694
|
+
gmmat = ants.bandpass_filter_matrix( gmmat, tr = tr, lowf=f[0], highf=f[1] )
|
4695
|
+
# gmmat = ants.regress_components( gmmat, nuisance )
|
4696
|
+
# Perform linear regression to estimate perfusion
|
4697
|
+
regression_model = LinearRegression()
|
4698
|
+
regvars = np.hstack( (nuisance, tclist ))
|
4699
|
+
coefind = regvars.shape[1]-1
|
4700
|
+
regvars = regvars[:,range(coefind)]
|
4701
|
+
regression_model.fit( regvars, gmmat )
|
4702
|
+
coefind = regression_model.coef_.shape[1]-1
|
4703
|
+
perfimg = ants.make_image( regression_mask, regression_model.coef_[:,coefind] )
|
4704
|
+
meangmval = ( perfimg[ gmseg == 1 ] ).mean()
|
4705
|
+
if meangmval < 0:
|
4706
|
+
perfimg = perfimg * (-1.0)
|
4707
|
+
meangmval = ( perfimg[ gmseg == 1 ] ).mean()
|
4708
|
+
if verbose:
|
4709
|
+
print("Coefficients:", regression_model.coef_)
|
4710
|
+
print("Coef mean", regression_model.coef_.mean(axis=0))
|
4711
|
+
print( regression_model.coef_.shape )
|
4712
|
+
print( perfimg.max() )
|
4713
|
+
gsrbold = ants.matrix_to_timeseries(simg, gmmat, regression_mask)
|
4714
|
+
outdict = {}
|
4715
|
+
outdict['meanBold'] = und
|
4716
|
+
outdict['brainmask'] = bmask
|
4717
|
+
rsfNuisance = pd.DataFrame( nuisance )
|
4718
|
+
rsfNuisance['FD']=corrmo['FD']
|
4719
|
+
|
4720
|
+
|
4721
|
+
nonbrainmask = ants.iMath( bmask, "MD",2) - bmask
|
4722
|
+
trimmask = ants.iMath( bmask, "ME",2)
|
4723
|
+
edgemask = ants.iMath( bmask, "ME",1) - trimmask
|
4724
|
+
|
4725
|
+
if verbose:
|
4726
|
+
print("perfusion dataframe begin")
|
4727
|
+
df_perf = antspyt1w.map_intensity_to_dataframe(
|
4728
|
+
'dkt_cortex_cit_deep_brain',
|
4729
|
+
perfimg,
|
4730
|
+
dktseg)
|
4731
|
+
df_perf = antspyt1w.merge_hierarchical_csvs_to_wide_format(
|
4732
|
+
{'perf' : df_perf},
|
4733
|
+
col_names = ['Mean'] )
|
4734
|
+
if verbose:
|
4735
|
+
print("perfusion dataframe end")
|
4736
|
+
print( df_perf )
|
4737
|
+
|
4738
|
+
outdict['perfusion']=perfimg
|
4739
|
+
outdict['perfusion_gm_mean']=meangmval
|
4740
|
+
outdict['perf_dataframe']=df_perf
|
4741
|
+
outdict['motion_corrected'] = corrmo['motion_corrected']
|
4742
|
+
outdict['gmseg'] = gmseg
|
4743
|
+
outdict['gsrbold'] = gsrbold
|
4744
|
+
outdict['brain_mask'] = bmask
|
4745
|
+
outdict['nuisance'] = rsfNuisance
|
4746
|
+
outdict['tsnr'] = mytsnr
|
4747
|
+
outdict['ssnr'] = slice_snr( corrmo['motion_corrected'], csfAndWM, gmseg )
|
4748
|
+
outdict['dvars'] = dvars( corrmo['motion_corrected'], gmseg )
|
4749
|
+
outdict['high_motion_count'] = (rsfNuisance['FD'] > FD_threshold ).sum()
|
4750
|
+
outdict['high_motion_pct'] = (rsfNuisance['FD'] > FD_threshold ).sum() / rsfNuisance.shape[0]
|
4751
|
+
outdict['FD_max'] = rsfNuisance['FD'].max()
|
4752
|
+
outdict['FD_mean'] = rsfNuisance['FD'].mean()
|
4753
|
+
outdict['bold_evr'] = antspyt1w.patch_eigenvalue_ratio( und, 512, [16,16,16], evdepth = 0.9, mask = bmask )
|
4754
|
+
outdict['t1reg'] = t1reg
|
4755
|
+
return outdict
|
4756
|
+
|
4757
|
+
|
4527
4758
|
|
4528
4759
|
def write_bvals_bvecs(bvals, bvecs, prefix ):
|
4529
4760
|
''' Write FSL FDT bvals and bvecs files
|
@@ -4595,6 +4826,7 @@ def mm(
|
|
4595
4826
|
flair_image=None,
|
4596
4827
|
nm_image_list=None,
|
4597
4828
|
dw_image=[], bvals=[], bvecs=[],
|
4829
|
+
perfusion_image=None,
|
4598
4830
|
srmodel=None,
|
4599
4831
|
do_tractography = False,
|
4600
4832
|
do_kk = False,
|
@@ -4631,6 +4863,8 @@ def mm(
|
|
4631
4863
|
|
4632
4864
|
bvecs : list of bvecs file names
|
4633
4865
|
|
4866
|
+
perfusion_image : single perfusion image
|
4867
|
+
|
4634
4868
|
srmodel : optional srmodel
|
4635
4869
|
|
4636
4870
|
do_tractography : boolean
|
@@ -4699,7 +4933,8 @@ def mm(
|
|
4699
4933
|
'FA_summ' : None,
|
4700
4934
|
'MD_summ' : None,
|
4701
4935
|
'tractography' : None,
|
4702
|
-
'tractography_connectivity' : None
|
4936
|
+
'tractography_connectivity' : None,
|
4937
|
+
'perf' : None,
|
4703
4938
|
}
|
4704
4939
|
normalization_dict = {
|
4705
4940
|
'kk_norm': None,
|
@@ -4707,6 +4942,7 @@ def mm(
|
|
4707
4942
|
'DTI_norm': None,
|
4708
4943
|
'FA_norm' : None,
|
4709
4944
|
'MD_norm' : None,
|
4945
|
+
'perf_norm' : None,
|
4710
4946
|
'alff_norm' : None,
|
4711
4947
|
'falff_norm' : None,
|
4712
4948
|
'CinguloopercularTaskControl_norm' : None,
|
@@ -4727,6 +4963,19 @@ def mm(
|
|
4727
4963
|
print('kk')
|
4728
4964
|
output_dict['kk'] = antspyt1w.kelly_kapowski_thickness( hier['brain_n4_dnz'],
|
4729
4965
|
labels=hier['dkt_parc']['dkt_cortex'], iterations=45 )
|
4966
|
+
if perfusion_image is not None:
|
4967
|
+
boldTemplate=get_average_rsf(perfusion_image)
|
4968
|
+
if perfusion_image.shape[3] > 8: # FIXME - better heuristic?
|
4969
|
+
output_dict['perf'] = bold_perfusion(
|
4970
|
+
perfusion_image,
|
4971
|
+
boldTemplate,
|
4972
|
+
t1_image,
|
4973
|
+
hier['brain_n4_dnz'],
|
4974
|
+
t1atropos,
|
4975
|
+
hier['dkt_parc']['dkt_cortex'] + hier['cit168lab'],
|
4976
|
+
f=[0.0,math.inf],
|
4977
|
+
spa = (1.0, 1.0, 1.0, 0.0),
|
4978
|
+
nc = 6, verbose=verbose )
|
4730
4979
|
################################## do the rsf .....
|
4731
4980
|
if len(rsf_image) > 0:
|
4732
4981
|
rsf_image = [i for i in rsf_image if i is not None]
|
@@ -4907,7 +5156,7 @@ def mm(
|
|
4907
5156
|
if verbose:
|
4908
5157
|
print('normalization')
|
4909
5158
|
# might reconsider this template space - cropped and/or higher res?
|
4910
|
-
template = ants.resample_image( template, [1,1,1], use_voxels=False )
|
5159
|
+
# template = ants.resample_image( template, [1,1,1], use_voxels=False )
|
4911
5160
|
# t1reg = ants.registration( template, hier['brain_n4_dnz'], "antsRegistrationSyNQuickRepro[s]")
|
4912
5161
|
t1reg = do_normalization
|
4913
5162
|
if do_kk:
|
@@ -4933,6 +5182,11 @@ def mm(
|
|
4933
5182
|
normalization_dict[rsfkey] = ants.apply_transforms(
|
4934
5183
|
group_template, rsfpro[netid],
|
4935
5184
|
group_transform+rsfrig['fwdtransforms'] )
|
5185
|
+
if output_dict['perf'] is not None: # zizzer
|
5186
|
+
comptx = group_transform + output_dict['perf']['t1reg']['invtransforms']
|
5187
|
+
normalization_dict['perf_norm'] = ants.apply_transforms( group_template,
|
5188
|
+
output_dict['perf']['perfusion'], comptx,
|
5189
|
+
whichtoinvert=[False,False,True,False] )
|
4936
5190
|
if nm_image_list is not None:
|
4937
5191
|
nmpro = output_dict['NM']
|
4938
5192
|
nmrig = nmpro['t1_to_NM_transform'] # this is an inverse tx
|
@@ -4993,6 +5247,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
|
|
4993
5247
|
image_write_with_thumbnail( mm['NM'][mykey], tempfn, thumb=False )
|
4994
5248
|
|
4995
5249
|
faderk = mdderk = fat1derk = mdt1derk = None
|
5250
|
+
|
4996
5251
|
if mm['DTI'] is not None:
|
4997
5252
|
mydti = mm['DTI']
|
4998
5253
|
myop = output_prefix + separator
|
@@ -5105,6 +5360,26 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
|
|
5105
5360
|
# mm_wide.to_csv( fdfn )
|
5106
5361
|
else:
|
5107
5362
|
mm_wide['dti_FD_mean'] = mm_wide['dti_FD_max'] = 'NA'
|
5363
|
+
|
5364
|
+
if mm['perf'] is not None:
|
5365
|
+
perfpro = mm['perf']
|
5366
|
+
mm_wide['gm_mean'] = perfpro['perfusion_gm_mean']
|
5367
|
+
mm_wide['tsnr_mean'] = perfpro['tsnr'].mean()
|
5368
|
+
mm_wide['dvars_mean'] = perfpro['dvars'].mean()
|
5369
|
+
mm_wide['ssnr_mean'] = perfpro['ssnr'].mean()
|
5370
|
+
mm_wide['high_motion_count'] = perfpro['high_motion_count']
|
5371
|
+
mm_wide['evr'] = perfpro['bold_evr']
|
5372
|
+
mm_wide['FD_mean'] = perfpro['FD_mean']
|
5373
|
+
mm_wide['FD_max'] = perfpro['FD_max']
|
5374
|
+
if 'perf_dataframe' in perfpro.keys():
|
5375
|
+
pderk = perfpro['perf_dataframe'].iloc[: , 1:]
|
5376
|
+
mm_wide = pd.concat( [ mm_wide, pderk ], axis=1 )
|
5377
|
+
else:
|
5378
|
+
print("FIXME - perfusion dataframe")
|
5379
|
+
mykey='perfusion'
|
5380
|
+
tempfn = output_prefix + separator + mykey + '.nii.gz'
|
5381
|
+
image_write_with_thumbnail( mm['perf'][mykey], tempfn )
|
5382
|
+
|
5108
5383
|
mmwidefn = output_prefix + separator + 'mmwide.csv'
|
5109
5384
|
mm_wide.to_csv( mmwidefn )
|
5110
5385
|
if verbose:
|
@@ -5611,7 +5886,7 @@ def mm_nrg(
|
|
5611
5886
|
write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep, verbose=True )
|
5612
5887
|
for mykey in normPro.keys():
|
5613
5888
|
if normPro[mykey] is not None:
|
5614
|
-
if visualize and normPro[mykey].components == 1:
|
5889
|
+
if visualize and normPro[mykey].components == 1 and False:
|
5615
5890
|
ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
|
5616
5891
|
if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
|
5617
5892
|
return
|
@@ -5760,6 +6035,8 @@ def mm_csv(
|
|
5760
6035
|
imfns=['filename']
|
5761
6036
|
elif locmod == 'T2Flair':
|
5762
6037
|
imfns=['flairid']
|
6038
|
+
elif locmod == 'perf':
|
6039
|
+
imfns=['perfid']
|
5763
6040
|
elif locmod == 'NM2DMT':
|
5764
6041
|
imfns=[]
|
5765
6042
|
for i in range(11):
|
@@ -5918,6 +6195,9 @@ def mm_csv(
|
|
5918
6195
|
hier['brain_n4_dnz'],
|
5919
6196
|
normalization_template_transform_type,
|
5920
6197
|
outprefix = normout, verbose=False )
|
6198
|
+
myjac = ants.create_jacobian_determinant_image( template,
|
6199
|
+
greg['fwdtransforms'][0], do_log=True, geom=True )
|
6200
|
+
image_write_with_thumbnail( myjac, normout + "logjacobian.nii.gz", thumb=False )
|
5921
6201
|
if verbose:
|
5922
6202
|
print("end group template registration")
|
5923
6203
|
else:
|
@@ -6095,6 +6375,22 @@ def mm_csv(
|
|
6095
6375
|
axis=2, nslices=maxslice, ncol=7, crop=True, title='DefaultMode', filename=mymm+mysep+"boldDefaultMode.png" )
|
6096
6376
|
ants.plot( tabPro['rsf']['meanBold'], tabPro['rsf']['FrontoparietalTaskControl'],
|
6097
6377
|
axis=2, nslices=maxslice, ncol=7, crop=True, title='FrontoparietalTaskControl', filename=mymm+mysep+"boldFrontoparietalTaskControl.png" )
|
6378
|
+
if ( mymod == 'perf' ) and ishapelen == 4:
|
6379
|
+
dowrite=True
|
6380
|
+
tabPro, normPro = mm( t1, hier,
|
6381
|
+
perfusion_image=img,
|
6382
|
+
srmodel=None,
|
6383
|
+
do_tractography=False,
|
6384
|
+
do_kk=False,
|
6385
|
+
do_normalization=templateTx,
|
6386
|
+
group_template = normalization_template,
|
6387
|
+
group_transform = groupTx,
|
6388
|
+
test_run=test_run,
|
6389
|
+
verbose=True )
|
6390
|
+
if tabPro['perf'] is not None and visualize:
|
6391
|
+
maxslice = np.min( [21, tabPro['perf']['meanBold'].shape[2] ] )
|
6392
|
+
ants.plot( tabPro['perf']['perfusion'],
|
6393
|
+
axis=2, nslices=maxslice, ncol=7, crop=True, title='perfusion image', filename=mymm+mysep+"perfusion.png" )
|
6098
6394
|
if ( mymod == 'DTI_LR' or mymod == 'DTI_RL' or mymod == 'DTI' ) and ishapelen == 4:
|
6099
6395
|
bvalfn = re.sub( '.nii.gz', '.bval' , myimg )
|
6100
6396
|
bvecfn = re.sub( '.nii.gz', '.bvec' , myimg )
|
@@ -6158,7 +6454,7 @@ def mm_csv(
|
|
6158
6454
|
write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep )
|
6159
6455
|
for mykey in normPro.keys():
|
6160
6456
|
if normPro[mykey] is not None and normPro[mykey].components == 1:
|
6161
|
-
if visualize:
|
6457
|
+
if visualize and False:
|
6162
6458
|
ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
|
6163
6459
|
if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
|
6164
6460
|
return
|
@@ -6566,7 +6862,7 @@ progress=False, verbose=False ):
|
|
6566
6862
|
"""
|
6567
6863
|
extend a study data frame with wide outputs
|
6568
6864
|
|
6569
|
-
sdf : the input study dataframe
|
6865
|
+
sdf : the input study dataframe from antspymm QC output
|
6570
6866
|
|
6571
6867
|
processing_dir: the directory location of the processed data
|
6572
6868
|
|
@@ -6589,8 +6885,8 @@ progress=False, verbose=False ):
|
|
6589
6885
|
for k in range(len(musthavecols)):
|
6590
6886
|
if not musthavecols[k] in sdf.keys():
|
6591
6887
|
raise ValueError('sdf is missing column ' +musthavecols[k] + ' in merge_wides_to_study_dataframe' )
|
6592
|
-
possible_iids = [ 'imageID', 'imageID', 'imageID', 'flairid', 'dtid1', 'dtid2', 'rsfid1', 'rsfid2', 'nmid1', 'nmid2', 'nmid3', 'nmid4', 'nmid5', 'nmid6', 'nmid7', 'nmid8', 'nmid9', 'nmid10' ]
|
6593
|
-
modality_ids = [ 'T1wHierarchical', 'T1wHierarchicalSR', 'T1w', 'T2Flair', 'DTI', 'DTI', 'rsfMRI', 'rsfMRI', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT']
|
6888
|
+
possible_iids = [ 'imageID', 'imageID', 'imageID', 'flairid', 'dtid1', 'dtid2', 'rsfid1', 'rsfid2', 'nmid1', 'nmid2', 'nmid3', 'nmid4', 'nmid5', 'nmid6', 'nmid7', 'nmid8', 'nmid9', 'nmid10', 'perfid' ]
|
6889
|
+
modality_ids = [ 'T1wHierarchical', 'T1wHierarchicalSR', 'T1w', 'T2Flair', 'DTI', 'DTI', 'rsfMRI', 'rsfMRI', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'perf']
|
6594
6890
|
alldf=pd.DataFrame()
|
6595
6891
|
for myk in sdf.index:
|
6596
6892
|
if progress > 0 and int(myk) % int(progress) == 0:
|
@@ -7763,7 +8059,7 @@ def novelty_detection_quantile(df_train, df_test):
|
|
7763
8059
|
return myqs
|
7764
8060
|
|
7765
8061
|
|
7766
|
-
def brainmap_figure(statistical_df, data_dictionary_path, output_prefix,
|
8062
|
+
def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, brain_image, overlay_cmap='bwr', nslices=21, ncol=7, edge_image_dilation = 0, black_bg=True, axes = [0,1,2], fixed_overlay_range=None, crop=True, verbose=False ):
|
7767
8063
|
"""
|
7768
8064
|
Create figures based on statistical data and an underlying brain image.
|
7769
8065
|
|
@@ -7771,17 +8067,25 @@ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, edge_im
|
|
7771
8067
|
|
7772
8068
|
Parameters:
|
7773
8069
|
- statistical_df (pandas dataframe): with 2 columns named anat and value
|
8070
|
+
the anat column should have names that meet *partial matching* criterion
|
8071
|
+
with respect to regions that are measured in antspymm. value will be
|
8072
|
+
the value to be displayed. if two examples of a given region exist in
|
8073
|
+
statistical_df, then the largest absolute value will be taken for display.
|
7774
8074
|
- data_dictionary_path (str): Path to the data dictionary CSV file.
|
7775
8075
|
- output_prefix (str): Prefix for the output figure filenames.
|
7776
|
-
-
|
8076
|
+
- brain_image (antsImage): the brain image on which results will overlay.
|
7777
8077
|
- overlay_cmap (str): see matplotlib
|
7778
|
-
- nslices: number of slices to show
|
7779
|
-
- ncol: number of columns to show
|
7780
|
-
- edge_image_dilation: integer greater than or equal to zero
|
7781
|
-
-
|
8078
|
+
- nslices (int): number of slices to show
|
8079
|
+
- ncol (int): number of columns to show
|
8080
|
+
- edge_image_dilation (int): integer greater than or equal to zero
|
8081
|
+
- black_bg (bool): boolean
|
8082
|
+
- axes (list): integer list typically [0,1,2] sagittal coronal axial
|
8083
|
+
- fixed_overlay_range (list): scalar pair will try to keep a constant cbar and will truncate the overlay at these min/max values
|
8084
|
+
- crop (bool): crops the image to display by the extent of the overlay
|
8085
|
+
- verbose (bool): boolean
|
7782
8086
|
|
7783
8087
|
Returns:
|
7784
|
-
values mapped to the associated regions
|
8088
|
+
an image with values mapped to the associated regions
|
7785
8089
|
"""
|
7786
8090
|
|
7787
8091
|
# Read the statistical file
|
@@ -7792,7 +8096,7 @@ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, edge_im
|
|
7792
8096
|
mydict = mydict[~mydict['Measurement'].str.contains("tractography-based connectivity", na=False)]
|
7793
8097
|
|
7794
8098
|
# Load image and process it
|
7795
|
-
edgeimg = ants.
|
8099
|
+
edgeimg = ants.iMath(brain_image,"Normalize")
|
7796
8100
|
if edge_image_dilation > 0:
|
7797
8101
|
edgeimg = ants.iMath( edgeimg, "MD", edge_image_dilation)
|
7798
8102
|
|
@@ -7873,16 +8177,381 @@ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, edge_im
|
|
7873
8177
|
|
7874
8178
|
if verbose:
|
7875
8179
|
print('Done Adding')
|
7876
|
-
for axx in
|
8180
|
+
for axx in axes:
|
7877
8181
|
figfn=output_prefix+f"fig{col2viz}ax{axx}_py.jpg"
|
7878
|
-
|
7879
|
-
|
7880
|
-
|
8182
|
+
if crop:
|
8183
|
+
cmask = ants.threshold_image( addem,1e-5, 1e9 ).iMath("MD",3) + ants.threshold_image( addem,-1e9, -1e-5 ).iMath("MD",3)
|
8184
|
+
addemC = ants.crop_image( addem, cmask )
|
8185
|
+
edgeimgC = ants.crop_image( edgeimg, cmask )
|
8186
|
+
else:
|
8187
|
+
addemC = addem
|
8188
|
+
edgeimgC = edgeimg
|
8189
|
+
if fixed_overlay_range is not None:
|
8190
|
+
addemC[0:3,0:3,0:3]=fixed_overlay_range[0]
|
8191
|
+
addemC[4:7,4:7,4:7]=fixed_overlay_range[1]
|
8192
|
+
addemC[ addemC < fixed_overlay_range[0] ] = fixed_overlay_range[0]
|
8193
|
+
addemC[ addemC > fixed_overlay_range[1] ] = fixed_overlay_range[1]
|
7881
8194
|
ants.plot(edgeimgC, addemC, axis=axx, nslices=nslices, ncol=ncol,
|
7882
8195
|
overlay_cmap=overlay_cmap, resample=False,
|
7883
|
-
filename=figfn, cbar=
|
8196
|
+
filename=figfn, cbar=axx==axes[0], crop=True, black_bg=black_bg )
|
7884
8197
|
if verbose:
|
7885
8198
|
print(f"{col2viz} done")
|
7886
8199
|
if verbose:
|
7887
8200
|
print("DONE brain map figures")
|
7888
8201
|
return addem
|
8202
|
+
|
8203
|
+
|
8204
|
+
def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='date', image_col='imageID', date_column='ses-1', base_path="./Processed/ANTsExpArt/", hiervariable='T1wHierarchical', valid_modalities=None, verbose=False ):
|
8205
|
+
"""
|
8206
|
+
Aggregate ANTsPyMM results from the specified CSV file and save the aggregated results to a new CSV file.
|
8207
|
+
|
8208
|
+
Parameters:
|
8209
|
+
- input_csv (str): File path of the input CSV file containing ANTsPyMM QC results averaged and with outlier measurements.
|
8210
|
+
- subject_col (str): Name of the column to store subject IDs.
|
8211
|
+
- date_col (str): Name of the column to store date information.
|
8212
|
+
- image_col (str): Name of the column to store image IDs.
|
8213
|
+
- date_column (str): Name of the column representing the date information.
|
8214
|
+
- base_path (str): Base path for search paths. Defaults to "./Processed/ANTsExpArt/".
|
8215
|
+
- hiervariable (str) : the string variable denoting the Hierarchical output
|
8216
|
+
- valid_modalities (str array) : identifies for each modality; if None will be replaced by get_valid_modalities(long=True)
|
8217
|
+
- verbose : boolean
|
8218
|
+
|
8219
|
+
Note:
|
8220
|
+
This function is tested under limited circumstances. Use with caution.
|
8221
|
+
|
8222
|
+
Example usage:
|
8223
|
+
agg_df = aggregate_antspymm_results("qcdfaol.csv", subject_col='subjectID', date_col='date', image_col='imageID', date_column='ses-1', base_path="./Your/Custom/Path/")
|
8224
|
+
|
8225
|
+
Author:
|
8226
|
+
Avants and ChatGPT
|
8227
|
+
"""
|
8228
|
+
import pandas as pd
|
8229
|
+
import numpy as np
|
8230
|
+
from glob import glob
|
8231
|
+
|
8232
|
+
def filter_df( indf, myprefix ):
|
8233
|
+
nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
|
8234
|
+
indf = indf.loc[:, nums]
|
8235
|
+
indf=indf.loc[:, indf.dtypes != 'object' ]
|
8236
|
+
indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
|
8237
|
+
indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
|
8238
|
+
indf = indf.add_prefix( myprefix )
|
8239
|
+
return( indf )
|
8240
|
+
|
8241
|
+
def myread_csv(x, cnms):
|
8242
|
+
"""
|
8243
|
+
Reads a CSV file and returns a DataFrame excluding specified columns.
|
8244
|
+
|
8245
|
+
Parameters:
|
8246
|
+
- x (str): File path of the input CSV file describing the blind QC output
|
8247
|
+
- cnms (list): List of column names to exclude from the DataFrame.
|
8248
|
+
|
8249
|
+
Returns:
|
8250
|
+
pd.DataFrame: DataFrame with specified columns excluded.
|
8251
|
+
"""
|
8252
|
+
df = pd.read_csv(x)
|
8253
|
+
return df.loc[:, ~df.columns.isin(cnms)]
|
8254
|
+
|
8255
|
+
import warnings
|
8256
|
+
# Warning message for untested function
|
8257
|
+
warnings.warn("Warning: This function is not well tested. Use with caution.")
|
8258
|
+
|
8259
|
+
if valid_modalities is None:
|
8260
|
+
valid_modalities = get_valid_modalities('long')
|
8261
|
+
|
8262
|
+
# Read the input CSV file
|
8263
|
+
df = pd.read_csv(input_csv)
|
8264
|
+
|
8265
|
+
# Filter rows where modality is 'T1w'
|
8266
|
+
df = df[df['modality'] == 'T1w']
|
8267
|
+
badnames = get_names_from_data_frame( ['Unnamed'], df )
|
8268
|
+
df=df.drop(badnames, axis=1)
|
8269
|
+
|
8270
|
+
# Add new columns for subject ID, date, and image ID
|
8271
|
+
df[subject_col] = np.nan
|
8272
|
+
df[date_col] = date_column
|
8273
|
+
df[image_col] = np.nan
|
8274
|
+
df = df.astype({subject_col: str, date_col: str, image_col: str })
|
8275
|
+
|
8276
|
+
# if verbose:
|
8277
|
+
# print( df.shape )
|
8278
|
+
# print( df.dtypes )
|
8279
|
+
|
8280
|
+
# prefilter df for data that exists
|
8281
|
+
keep = np.tile( False, df.shape[0] )
|
8282
|
+
for x in range(df.shape[0]):
|
8283
|
+
temp = df['fn'].iloc[x].split("_")
|
8284
|
+
# Generalized search paths
|
8285
|
+
path_template = f"{base_path}{temp[0]}/{date_column}/*/*/*"
|
8286
|
+
hierfn = sorted(glob( path_template + "-" + hiervariable + "-*wide.csv" ) )
|
8287
|
+
if len( hierfn ) > 0:
|
8288
|
+
keep[x]=True
|
8289
|
+
|
8290
|
+
|
8291
|
+
df=df[keep]
|
8292
|
+
|
8293
|
+
if verbose:
|
8294
|
+
print( "original input had shape " + str( df.shape[0] ) + " (T1 only) and we find " + str( (keep).sum() ) + " with hierarchical output defined by variable: " + hiervariable )
|
8295
|
+
print( df.shape )
|
8296
|
+
|
8297
|
+
myct = 0
|
8298
|
+
for x in range( df.shape[0]):
|
8299
|
+
if verbose:
|
8300
|
+
print(f"{x}...")
|
8301
|
+
locind = df.index[x]
|
8302
|
+
temp = df['fn'].iloc[x].split("_")
|
8303
|
+
if verbose:
|
8304
|
+
print( temp )
|
8305
|
+
df[subject_col].iloc[x]=temp[0]
|
8306
|
+
df[date_col].iloc[x]=date_column
|
8307
|
+
df[image_col].iloc[x]=temp[1]
|
8308
|
+
|
8309
|
+
# Generalized search paths
|
8310
|
+
path_template = f"{base_path}{temp[0]}/{date_column}/*/*/*"
|
8311
|
+
if verbose:
|
8312
|
+
print(path_template)
|
8313
|
+
hierfn = sorted(glob( path_template + "-" + hiervariable + "-*wide.csv" ) )
|
8314
|
+
if len( hierfn ) > 0:
|
8315
|
+
hdf=t1df=dtdf=rsdf=perfdf=nmdf=flairdf=None
|
8316
|
+
if verbose:
|
8317
|
+
print(hierfn)
|
8318
|
+
hdf = pd.read_csv(hierfn[0])
|
8319
|
+
badnames = get_names_from_data_frame( ['Unnamed'], hdf )
|
8320
|
+
hdf=hdf.drop(badnames, axis=1)
|
8321
|
+
nums = [isinstance(hdf[col].iloc[0], (int, float)) for col in hdf.columns]
|
8322
|
+
corenames = list(np.array(hdf.columns)[nums])
|
8323
|
+
hdf.loc[:, nums] = hdf.loc[:, nums].add_prefix("T1Hier_")
|
8324
|
+
myct = myct + 1
|
8325
|
+
dflist = [hdf]
|
8326
|
+
|
8327
|
+
for mymod in valid_modalities:
|
8328
|
+
t1wfn = sorted(glob( path_template+ "-" + mymod + "-*wide.csv" ) )
|
8329
|
+
if len( t1wfn ) > 0 :
|
8330
|
+
if verbose:
|
8331
|
+
print(t1wfn)
|
8332
|
+
t1df = myread_csv(t1wfn[0], corenames)
|
8333
|
+
t1df = filter_df( t1df, mymod+'_')
|
8334
|
+
dflist = dflist + [t1df]
|
8335
|
+
|
8336
|
+
hdf = pd.concat( dflist, axis=1)
|
8337
|
+
if verbose:
|
8338
|
+
print( df.loc[locind,'fn'] )
|
8339
|
+
if myct == 1:
|
8340
|
+
subdf = df.iloc[[x]]
|
8341
|
+
hdf.index = subdf.index.copy()
|
8342
|
+
df = pd.concat( [df,hdf], axis=1)
|
8343
|
+
else:
|
8344
|
+
commcols = list(set(hdf.columns).intersection(df.columns))
|
8345
|
+
df.loc[locind, commcols] = hdf.loc[0, commcols]
|
8346
|
+
badnames = get_names_from_data_frame( ['Unnamed'], df )
|
8347
|
+
df=df.drop(badnames, axis=1)
|
8348
|
+
return( df )
|
8349
|
+
|
8350
|
+
def aggregate_antspymm_results_sdf(
|
8351
|
+
study_df,
|
8352
|
+
project_col='projectID',
|
8353
|
+
subject_col='subjectID',
|
8354
|
+
date_col='date',
|
8355
|
+
image_col='imageID',
|
8356
|
+
base_path="./",
|
8357
|
+
hiervariable='T1wHierarchical',
|
8358
|
+
splitsep='-',
|
8359
|
+
idsep='-',
|
8360
|
+
wild_card_modality_id=False,
|
8361
|
+
verbose=False ):
|
8362
|
+
"""
|
8363
|
+
Aggregate ANTsPyMM results from the specified study data frame and store the aggregated results in a new data frame. This assumes data is organized on disk
|
8364
|
+
as follows: rootdir/projectID/subjectID/date/outputid/imageid/ where
|
8365
|
+
outputid is modality-specific and created by ANTsPyMM processing.
|
8366
|
+
|
8367
|
+
Parameters:
|
8368
|
+
- study_df (pandas df): pandas data frame, output of generate_mm_dataframe.
|
8369
|
+
- project_col (str): Name of the column that stores the project ID
|
8370
|
+
- subject_col (str): Name of the column to store subject IDs.
|
8371
|
+
- date_col (str): Name of the column to store date information.
|
8372
|
+
- image_col (str): Name of the column to store image IDs.
|
8373
|
+
- base_path (str): Base path for searching for processing outputs of ANTsPyMM.
|
8374
|
+
- hiervariable (str) : the string variable denoting the Hierarchical output
|
8375
|
+
- splitsep (str): the separator used to split the filename
|
8376
|
+
- idsep (str): the separator used to partition subjectid date and imageid
|
8377
|
+
for example, if idsep is - then we have subjectid-date-imageid
|
8378
|
+
- wild_card_modality_id (bool): keep if False for safer execution
|
8379
|
+
- verbose : boolean
|
8380
|
+
|
8381
|
+
Note:
|
8382
|
+
This function is tested under limited circumstances. Use with caution.
|
8383
|
+
|
8384
|
+
Example usage:
|
8385
|
+
agg_df = aggregate_antspymm_results_sdf( studydf, subject_col='subjectID', date_col='date', image_col='imageID', base_path="./Your/Custom/Path/")
|
8386
|
+
|
8387
|
+
Author:
|
8388
|
+
Avants and ChatGPT
|
8389
|
+
"""
|
8390
|
+
import pandas as pd
|
8391
|
+
import numpy as np
|
8392
|
+
from glob import glob
|
8393
|
+
|
8394
|
+
def filter_df( indf, myprefix ):
|
8395
|
+
nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
|
8396
|
+
indf = indf.loc[:, nums]
|
8397
|
+
indf=indf.loc[:, indf.dtypes != 'object' ]
|
8398
|
+
indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
|
8399
|
+
indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
|
8400
|
+
indf = indf.add_prefix( myprefix )
|
8401
|
+
return( indf )
|
8402
|
+
|
8403
|
+
def myread_csv(x, cnms):
|
8404
|
+
"""
|
8405
|
+
Reads a CSV file and returns a DataFrame excluding specified columns.
|
8406
|
+
|
8407
|
+
Parameters:
|
8408
|
+
- x (str): File path of the input CSV file describing the blind QC output
|
8409
|
+
- cnms (list): List of column names to exclude from the DataFrame.
|
8410
|
+
|
8411
|
+
Returns:
|
8412
|
+
pd.DataFrame: DataFrame with specified columns excluded.
|
8413
|
+
"""
|
8414
|
+
df = pd.read_csv(x)
|
8415
|
+
return df.loc[:, ~df.columns.isin(cnms)]
|
8416
|
+
|
8417
|
+
import warnings
|
8418
|
+
# Warning message for untested function
|
8419
|
+
warnings.warn("Warning: This function is not well tested. Use with caution.")
|
8420
|
+
|
8421
|
+
# if valid_modalities is None:
|
8422
|
+
valid_modalities = get_valid_modalities('long')
|
8423
|
+
vmoddict = {}
|
8424
|
+
# Add key-value pairs
|
8425
|
+
vmoddict['imageID'] = 'T1w'
|
8426
|
+
vmoddict['flairid'] = 'T2Flair'
|
8427
|
+
vmoddict['perfid'] = 'perf'
|
8428
|
+
vmoddict['rsfid1'] = 'rsfMRI'
|
8429
|
+
vmoddict['dtid1'] = 'DTI'
|
8430
|
+
vmoddict['nmid1'] = 'NM2DMT'
|
8431
|
+
|
8432
|
+
# Filter rows where modality is 'T1w'
|
8433
|
+
df = study_df[study_df['modality'] == 'T1w']
|
8434
|
+
badnames = get_names_from_data_frame( ['Unnamed'], df )
|
8435
|
+
df=df.drop(badnames, axis=1)
|
8436
|
+
# prefilter df for data that exists
|
8437
|
+
keep = np.tile( False, df.shape[0] )
|
8438
|
+
for x in range(df.shape[0]):
|
8439
|
+
myfn = os.path.basename( df['filename'].iloc[x] )
|
8440
|
+
temp = myfn.split( splitsep )
|
8441
|
+
# Generalized search paths
|
8442
|
+
sid0 = temp[0]
|
8443
|
+
sid = str(df[subject_col].iloc[x])
|
8444
|
+
if sid0 != sid:
|
8445
|
+
warnings.warn("the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
|
8446
|
+
myproj = str(df[project_col].iloc[x])
|
8447
|
+
mydate = str(df[date_col].iloc[x])
|
8448
|
+
myid = str(df[image_col].iloc[x])
|
8449
|
+
path_template = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + hiervariable + '/' + str(myid) + "/"
|
8450
|
+
hierfn = sorted(glob( path_template + "*" + hiervariable + "*wide.csv" ) )
|
8451
|
+
if len( hierfn ) > 0:
|
8452
|
+
keep[x]=True
|
8453
|
+
|
8454
|
+
df=df[keep]
|
8455
|
+
|
8456
|
+
if not df.index.is_unique:
|
8457
|
+
warnings.warn("data frame does not have unique indices. we therefore reset the index to allow the function to continue on." )
|
8458
|
+
df = df.reset_index()
|
8459
|
+
|
8460
|
+
|
8461
|
+
if verbose:
|
8462
|
+
print( "original input had shape " + str( df.shape[0] ) + " (T1 only) and we find " + str( (keep).sum() ) + " with hierarchical output defined by variable: " + hiervariable )
|
8463
|
+
print( df.shape )
|
8464
|
+
|
8465
|
+
myct = 0
|
8466
|
+
for x in range( df.shape[0]):
|
8467
|
+
print("\n\n-------------------------------------------------")
|
8468
|
+
if verbose:
|
8469
|
+
print(f"{x}...")
|
8470
|
+
locind = df.index[x]
|
8471
|
+
myfn = os.path.basename( df['filename'].iloc[x] )
|
8472
|
+
sid = df[subject_col].iloc[x]
|
8473
|
+
if sid0 != sid:
|
8474
|
+
warnings.warn("the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
|
8475
|
+
myproj = str(df[project_col].iloc[x])
|
8476
|
+
mydate = str(df[date_col].iloc[x])
|
8477
|
+
myid = str(df[image_col].iloc[x])
|
8478
|
+
if verbose:
|
8479
|
+
print( myfn )
|
8480
|
+
print( temp )
|
8481
|
+
print( "id " + sid )
|
8482
|
+
path_template = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + hiervariable + '/' + str(myid) + "/"
|
8483
|
+
searchhier = path_template + "*" + hiervariable + "*wide.csv"
|
8484
|
+
if verbose:
|
8485
|
+
print( searchhier )
|
8486
|
+
hierfn = sorted( glob( searchhier ) )
|
8487
|
+
if len( hierfn ) > 1:
|
8488
|
+
raise ValueError("there are " + str( len( hierfn ) ) + " number of hier fns with search path " + searchhier )
|
8489
|
+
if len( hierfn ) == 1:
|
8490
|
+
hdf=t1df=dtdf=rsdf=perfdf=nmdf=flairdf=None
|
8491
|
+
if verbose:
|
8492
|
+
print(hierfn)
|
8493
|
+
hdf = pd.read_csv(hierfn[0])
|
8494
|
+
badnames = get_names_from_data_frame( ['Unnamed'], hdf )
|
8495
|
+
hdf=hdf.drop(badnames, axis=1)
|
8496
|
+
nums = [isinstance(hdf[col].iloc[0], (int, float)) for col in hdf.columns]
|
8497
|
+
corenames = list(np.array(hdf.columns)[nums])
|
8498
|
+
hdf.loc[:, nums] = hdf.loc[:, nums].add_prefix("T1Hier_")
|
8499
|
+
myct = myct + 1
|
8500
|
+
hdf = hdf.add_prefix( "T1Hier_" )
|
8501
|
+
dflist = [hdf]
|
8502
|
+
|
8503
|
+
for mymod in vmoddict.keys():
|
8504
|
+
print("\n\n************************* " + mymod + " *************************")
|
8505
|
+
modalityclass = vmoddict[ mymod ]
|
8506
|
+
if wild_card_modality_id:
|
8507
|
+
mymodid = '*'
|
8508
|
+
else:
|
8509
|
+
mymodid = str( df[mymod].iloc[x] )
|
8510
|
+
if mymodid.lower() != "nan" and mymodid.lower() != "na":
|
8511
|
+
mymodid = os.path.basename( mymodid )
|
8512
|
+
mymodid = os.path.splitext( mymodid )[0]
|
8513
|
+
mymodid = os.path.splitext( mymodid )[0]
|
8514
|
+
temp = mymodid.split( idsep )
|
8515
|
+
mymodid = temp[ len( temp )-1 ]
|
8516
|
+
else:
|
8517
|
+
print("missing")
|
8518
|
+
continue
|
8519
|
+
if verbose:
|
8520
|
+
print( "modality id is " + mymodid + " for modality " + modalityclass )
|
8521
|
+
modalityclasssearch = modalityclass
|
8522
|
+
if modalityclass in ['rsfMRI','DTI']:
|
8523
|
+
modalityclasssearch=modalityclass+"*"
|
8524
|
+
path_template_m = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + modalityclasssearch + '/' + mymodid + "/"
|
8525
|
+
modsearch = path_template_m + "*" + modalityclasssearch + "*wide.csv"
|
8526
|
+
if verbose:
|
8527
|
+
print( modsearch )
|
8528
|
+
t1wfn = sorted( glob( modsearch ) )
|
8529
|
+
if len( t1wfn ) > 1:
|
8530
|
+
raise ValueError("there are " + str( len( t1wfn ) ) + " number of wide fns with search path " + modsearch )
|
8531
|
+
if len( t1wfn ) == 1:
|
8532
|
+
if verbose:
|
8533
|
+
print(t1wfn)
|
8534
|
+
t1df = myread_csv(t1wfn[0], corenames)
|
8535
|
+
t1df = filter_df( t1df, modalityclass+'_')
|
8536
|
+
dflist = dflist + [t1df]
|
8537
|
+
else:
|
8538
|
+
if verbose:
|
8539
|
+
print( " cannot find " + modsearch )
|
8540
|
+
|
8541
|
+
hdf = pd.concat( dflist, axis=1)
|
8542
|
+
if verbose:
|
8543
|
+
print( "count: " + str( myct ) )
|
8544
|
+
if myct == 1:
|
8545
|
+
subdf = df.iloc[[x]]
|
8546
|
+
hdf.index = subdf.index.copy()
|
8547
|
+
print( hdf.index )
|
8548
|
+
print( df.index )
|
8549
|
+
df = pd.concat( [df,hdf], axis=1)
|
8550
|
+
else:
|
8551
|
+
commcols = list(set(hdf.columns).intersection(df.columns))
|
8552
|
+
df.loc[locind, commcols] = hdf.loc[0, commcols]
|
8553
|
+
badnames = get_names_from_data_frame( ['Unnamed'], df )
|
8554
|
+
df=df.drop(badnames, axis=1)
|
8555
|
+
return( df )
|
8556
|
+
|
8557
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: antspymm
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.3
|
4
4
|
Summary: multi-channel/time-series medical image processing with antspyx
|
5
5
|
Home-page: https://github.com/stnava/ANTsPyMM
|
6
6
|
Author: Avants, Gosselin, Tustison, Reardon
|
@@ -18,7 +18,7 @@ Requires-Dist: dipy
|
|
18
18
|
Requires-Dist: nibabel
|
19
19
|
Requires-Dist: scipy
|
20
20
|
Requires-Dist: siq
|
21
|
-
Requires-Dist:
|
21
|
+
Requires-Dist: scikit-learn
|
22
22
|
|
23
23
|
# ANTsPyMM
|
24
24
|
|
@@ -246,6 +246,13 @@ studycsv = antspymm.generate_mm_dataframe(
|
|
246
246
|
rsf_filenames=[fns[2]])
|
247
247
|
studycsv2 = studycsv.dropna(axis=1)
|
248
248
|
mmrun = antspymm.mm_csv( studycsv2, mysep='_' )
|
249
|
+
|
250
|
+
# aggregate the data after you've run on many subjects
|
251
|
+
# studycsv_all would be the vstacked studycsv2 data frames
|
252
|
+
zz=antspymm.aggregate_antspymm_results_sdf( studycsv_all,
|
253
|
+
subject_col='subjectID', date_col='date', image_col='imageID', base_path=bd,
|
254
|
+
splitsep='_', idsep='-', wild_card_modality_id=True, verbose=True)
|
255
|
+
|
249
256
|
```
|
250
257
|
|
251
258
|
## NRG example
|
@@ -429,6 +436,15 @@ for f in folders:
|
|
429
436
|
pdoc -o ./docs antspymm --html
|
430
437
|
```
|
431
438
|
|
439
|
+
## ssl error
|
440
|
+
|
441
|
+
if you get an odd certificate error when calling `force_download`, try:
|
442
|
+
|
443
|
+
```python
|
444
|
+
import ssl
|
445
|
+
ssl._create_default_https_context = ssl._create_unverified_context
|
446
|
+
``
|
447
|
+
|
432
448
|
## to publish a release
|
433
449
|
|
434
450
|
```
|
@@ -0,0 +1,7 @@
|
|
1
|
+
antspymm/__init__.py,sha256=wck10IhO1ZFxNqz4bTIHFP7cqYosRKBI8bKIsZzRwfs,3166
|
2
|
+
antspymm/mm.py,sha256=sYXNst5xri6vFDzvuqDB6avuwkKoq3GTEsvm0RLUuPU,347923
|
3
|
+
antspymm-1.1.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
|
+
antspymm-1.1.3.dist-info/METADATA,sha256=bFp7u3Bd85Sq4Nc_TvvzxfYDhyU9Uxj4T9yzDcDK26I,14153
|
5
|
+
antspymm-1.1.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
+
antspymm-1.1.3.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
|
7
|
+
antspymm-1.1.3.dist-info/RECORD,,
|
antspymm-1.1.2.dist-info/RECORD
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
antspymm/__init__.py,sha256=F7Dha4OCqkM7QhDCKXpO1fBHTXpUqxpIfDAn-NJG0r4,3045
|
2
|
-
antspymm/mm.py,sha256=bcB6EGrAiaqbfdccTv2dG449Q_qqPFx4D6Cjq05knUc,319706
|
3
|
-
antspymm-1.1.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
|
-
antspymm-1.1.2.dist-info/METADATA,sha256=hamN7KOuQ0A6hOpABVGdR93kHtw56LwFfe1NyRm9ABc,13639
|
5
|
-
antspymm-1.1.2.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
6
|
-
antspymm-1.1.2.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
|
7
|
-
antspymm-1.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|