antspymm 1.1.8__py3-none-any.whl → 1.1.9__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 CHANGED
@@ -108,5 +108,9 @@ from .mm import remove_elements_from_numpy_array
108
108
  from .mm import remove_volumes_from_timeseries
109
109
  from .mm import impute_timeseries
110
110
  from .mm import impute_dwi
111
+ from .mm import scrub_dwi
111
112
  from .mm import timeseries_n3
112
113
  from .mm import calculate_CBF
114
+ from .mm import estimate_optimal_pca_components
115
+ from .mm import filter_df
116
+
antspymm/mm.py CHANGED
@@ -111,7 +111,7 @@ import re
111
111
  import datetime as dt
112
112
  from collections import Counter
113
113
  import tempfile
114
-
114
+ import warnings
115
115
 
116
116
  from dipy.core.histeq import histeq
117
117
  import dipy.reconst.dti as dti
@@ -686,7 +686,7 @@ def collect_blind_qc_by_modality( modality_path, set_index_to_fn=True ):
686
686
  temp=pd.read_csv(fns[k])
687
687
  if not 'filename' in temp.keys():
688
688
  temp['filename']=fns[k]
689
- jdf=pd.concat( [jdf,temp])
689
+ jdf=pd.concat( [jdf,temp], axis=0, ignore_index=False )
690
690
  if set_index_to_fn:
691
691
  jdf.reset_index(drop=True)
692
692
  if "Unnamed: 0" in jdf.columns:
@@ -3151,7 +3151,8 @@ def joint_dti_recon(
3151
3151
  dewarp_modality = 'FA',
3152
3152
  denoise=False,
3153
3153
  fit_method='WLS',
3154
- impute = True,
3154
+ impute = False,
3155
+ scrub = True,
3155
3156
  verbose = False ):
3156
3157
  """
3157
3158
  1. pass in subject data and 1mm JHU atlas/labels
@@ -3204,6 +3205,8 @@ def joint_dti_recon(
3204
3205
 
3205
3206
  impute : boolean
3206
3207
 
3208
+ scrub : boolean
3209
+
3207
3210
  verbose : boolean
3208
3211
 
3209
3212
  Returns
@@ -3291,8 +3294,12 @@ def joint_dti_recon(
3291
3294
 
3292
3295
  if impute:
3293
3296
  img_LRdwp=impute_dwi( img_LRdwp, verbose=True )
3297
+ elif scrub:
3298
+ img_LRdwp, reg_LR['bvals'], reg_LR['bvecs'] = scrub_dwi( img_LRdwp, reg_LR['bvals'], reg_LR['bvecs'], verbose=True )
3294
3299
  if impute and img_RL is not None:
3295
3300
  img_RLdwp=impute_dwi( img_RLdwp, verbose=True )
3301
+ elif scrub and img_RL is not None:
3302
+ img_RLdwp, reg_RL['bvals'], reg_RL['bvecs'] = scrub_dwi( img_RLdwp, reg_RL['bvals'], reg_RL['bvecs'], verbose=True )
3296
3303
 
3297
3304
  if img_RL is not None:
3298
3305
  img_LRdwp, bval_LR, bvec_LR = merge_dwi_data(
@@ -4171,7 +4178,7 @@ def dwi_streamline_connectivity_old(
4171
4178
  pathdfw,
4172
4179
  Mdfw,
4173
4180
  Tdfw,
4174
- Ctdfw ], axis=1 )
4181
+ Ctdfw ], axis=1, ignore_index=False )
4175
4182
 
4176
4183
  return {
4177
4184
  'connectivity': allconnexwide,
@@ -4236,7 +4243,7 @@ def hierarchical_modality_summary(
4236
4243
  if verbose:
4237
4244
  print( mappedw.keys() )
4238
4245
  if mydf.shape[0] > 0:
4239
- mydf = pd.concat( [ mydf, mappedw], axis=1 )
4246
+ mydf = pd.concat( [ mydf, mappedw], axis=1, ignore_index=False )
4240
4247
  else:
4241
4248
  mydf = mappedw
4242
4249
  return mydf
@@ -4596,9 +4603,52 @@ def neuromelanin( list_nm_images, t1, t1_head, t1lab, brain_stem_dilation=8,
4596
4603
  'NM_count': len( list_nm_images )
4597
4604
  }
4598
4605
 
4606
+
4607
+
4608
+ def estimate_optimal_pca_components(data, variance_threshold=0.80, plot=False):
4609
+ """
4610
+ Estimate the optimal number of PCA components to represent the given data.
4611
+
4612
+ :param data: The data matrix (samples x features).
4613
+ :param variance_threshold: Threshold for cumulative explained variance (default 0.95).
4614
+ :param plot: If True, plot the cumulative explained variance graph (default False).
4615
+ :return: The optimal number of principal components.
4616
+ """
4617
+ import numpy as np
4618
+ from sklearn.decomposition import PCA
4619
+ import matplotlib.pyplot as plt
4620
+
4621
+ # Perform PCA
4622
+ pca = PCA()
4623
+ pca.fit(data)
4624
+
4625
+ # Calculate cumulative explained variance
4626
+ cumulative_variance = np.cumsum(pca.explained_variance_ratio_)
4627
+
4628
+ # Determine the number of components for desired explained variance
4629
+ n_components = np.where(cumulative_variance >= variance_threshold)[0][0] + 1
4630
+
4631
+ # Optionally plot the explained variance
4632
+ if plot:
4633
+ plt.figure(figsize=(8, 4))
4634
+ plt.plot(cumulative_variance, linewidth=2)
4635
+ plt.axhline(y=variance_threshold, color='r', linestyle='--')
4636
+ plt.axvline(x=n_components - 1, color='r', linestyle='--')
4637
+ plt.xlabel('Number of Components')
4638
+ plt.ylabel('Cumulative Explained Variance')
4639
+ plt.title('Explained Variance by Number of Principal Components')
4640
+ plt.show()
4641
+
4642
+ return n_components
4643
+
4644
+
4599
4645
  def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4600
- f=[0.01,0.1], FD_threshold=0.5, spa = 1.0, spt = 0.5, nc = 6, type_of_transform='Rigid',
4601
- outlier_threshold=0.8,
4646
+ f=[0.008,0.1], FD_threshold=0.5, spa = None, spt = None,
4647
+ nc = 0.90, type_of_transform='Rigid',
4648
+ outlier_threshold=0.50,
4649
+ ica_components = 0,
4650
+ impute = False,
4651
+ scrub = True,
4602
4652
  verbose=False ):
4603
4653
  """
4604
4654
  Compute resting state network correlation maps based on the J Power labels.
@@ -4618,14 +4668,20 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4618
4668
 
4619
4669
  f : band pass limits for frequency filtering
4620
4670
 
4621
- spa : gaussian smoothing for spatial component
4671
+ spa : gaussian smoothing for spatial component (physical coordinates)
4622
4672
 
4623
4673
  spt : gaussian smoothing for temporal component
4624
4674
 
4625
- nc : number of components for compcor filtering
4675
+ nc : number of components for compcor filtering; if less than 1 we estimate on the fly based on explained variance
4626
4676
 
4627
4677
  type_of_transform : SyN or Rigid
4628
4678
 
4679
+ ica_components : integer if greater than 0 then include ica components
4680
+
4681
+ impute : boolean if True, then use imputation
4682
+
4683
+ scrub : boolean if True, then use censoring (scrubbing)
4684
+
4629
4685
  verbose : boolean
4630
4686
 
4631
4687
  Returns
@@ -4633,16 +4689,66 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4633
4689
  a dictionary containing the derived network maps
4634
4690
 
4635
4691
  """
4692
+
4693
+ import numpy as np
4694
+ # Assuming core and utils are modules or packages with necessary functions
4695
+
4696
+ def compute_tSTD(M, quantile, x=0, axis=0):
4697
+ stdM = np.std(M, axis=axis)
4698
+ # set bad values to x
4699
+ stdM[stdM == 0] = x
4700
+ stdM[np.isnan(stdM)] = x
4701
+ tt = round(quantile * 100)
4702
+ threshold_std = np.percentile(stdM, tt)
4703
+ return {'tSTD': stdM, 'threshold_std': threshold_std}
4704
+
4705
+ def get_compcor_matrix(boldImage, mask, quantile):
4706
+ """
4707
+ Compute the compcor matrix.
4708
+
4709
+ :param boldImage: The bold image.
4710
+ :param mask: The mask to apply, if None, it will be computed.
4711
+ :param quantile: Quantile for computing threshold in tSTD.
4712
+ :return: The compor matrix.
4713
+ """
4714
+ if mask is None:
4715
+ temp = ants.slice_image(boldImage, axis=boldImage.dimension - 1, idx=0)
4716
+ mask = ants.get_mask(temp)
4717
+
4718
+ imagematrix = ants.timeseries_to_matrix(boldImage, mask)
4719
+ temp = compute_tSTD(imagematrix, quantile, 0)
4720
+ tsnrmask = ants.make_image(mask, temp['tSTD'])
4721
+ tsnrmask = ants.threshold_image(tsnrmask, temp['threshold_std'], temp['tSTD'].max())
4722
+ M = ants.timeseries_to_matrix(boldImage, tsnrmask)
4723
+ return M
4724
+
4725
+
4726
+ from sklearn.decomposition import FastICA
4727
+ def find_indices(lst, value):
4728
+ return [index for index, element in enumerate(lst) if element > value]
4729
+
4730
+ def mean_of_list(lst):
4731
+ if not lst: # Check if the list is not empty
4732
+ return 0 # Return 0 or appropriate value for an empty list
4733
+ return sum(lst) / len(lst)
4734
+ fmrispc = list(ants.get_spacing( fmri ))
4735
+ if spa is None:
4736
+ spa = mean_of_list( fmrispc[0:3] ) * 1.0
4737
+ if spt is None:
4738
+ spt = fmrispc[3] * 0.5
4739
+
4636
4740
  import numpy as np
4637
4741
  import pandas as pd
4638
4742
  import re
4639
4743
  import math
4744
+ # point data resources
4640
4745
  A = np.zeros((1,1))
4641
4746
  powers_areal_mni_itk = pd.read_csv( get_data('powers_mni_itk', target_extension=".csv")) # power coordinates
4642
4747
  fmri = ants.iMath( fmri, 'Normalize' )
4643
4748
  bmask = antspynet.brain_extraction( fmri_template, 'bold' ).threshold_image(0.5,1).iMath("FillHoles")
4644
4749
  if verbose:
4645
4750
  print("Begin rsfmri motion correction")
4751
+ # mot-co
4646
4752
  corrmo = timeseries_reg(
4647
4753
  fmri, fmri_template,
4648
4754
  type_of_transform=type_of_transform,
@@ -4654,18 +4760,21 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4654
4760
  syn_metric='cc',
4655
4761
  syn_sampling=2,
4656
4762
  reg_iterations=[40,20,5] )
4763
+
4657
4764
  if verbose:
4658
4765
  print("End rsfmri motion correction")
4766
+ print("big code block below does anatomically based mapping")
4659
4767
 
4660
- if outlier_threshold < 1.0 and outlier_threshold > 0.0:
4661
- fmrimotcorr, hlinds = loop_timeseries_censoring( corrmo['motion_corrected'], threshold=outlier_threshold, verbose=verbose )
4662
- corrmo['FD'] = replace_elements_in_numpy_array( corrmo['FD'], hlinds, corrmo['FD'].mean() )
4663
- corrmo['motion_corrected'] = impute_timeseries( corrmo['motion_corrected'], hlinds, method='linear')
4664
-
4768
+ high_motion_count=(corrmo['FD'] > FD_threshold ).sum()
4769
+ high_motion_pct=high_motion_count / fmri.shape[3]
4770
+
4771
+ # filter mask based on TSNR
4665
4772
  mytsnr = tsnr( corrmo['motion_corrected'], bmask )
4666
4773
  mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
4667
4774
  tsnrmask = ants.threshold_image( mytsnr, 0, mytsnrThresh ).morphology("close",2)
4668
4775
  bmask = bmask * tsnrmask
4776
+
4777
+ # anatomical mapping
4669
4778
  und = fmri_template * bmask
4670
4779
  t1reg = ants.registration( und, t1, "SyNBold" )
4671
4780
  if verbose:
@@ -4682,14 +4791,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4682
4791
  ants.threshold_image( t1segmentation, 3, 3 ) ).morphology("erode",1)
4683
4792
  csfAndWM = ants.apply_transforms( und, csfAndWM,
4684
4793
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
4685
-
4686
- # get falff and alff
4687
- mycompcor = ants.compcor( corrmo['motion_corrected'],
4688
- ncompcor=nc, quantile=0.90, mask = csfAndWM,
4689
- filter_type='polynomial', degree=2 )
4690
-
4691
4794
  nt = corrmo['motion_corrected'].shape[3]
4692
-
4693
4795
  myvoxes = range(powers_areal_mni_itk.shape[0])
4694
4796
  anat = powers_areal_mni_itk['Anatomy']
4695
4797
  syst = powers_areal_mni_itk['SystemName']
@@ -4703,26 +4805,98 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4703
4805
  locations = pts2bold.iloc[:,:3].values
4704
4806
  ptImg = ants.make_points_image( locations, bmask, radius = 2 )
4705
4807
 
4808
+ # optional smoothing
4706
4809
  tr = ants.get_spacing( corrmo['motion_corrected'] )[3]
4707
- highMotionTimes = np.where( corrmo['FD'] >= 1.0 )
4708
- goodtimes = np.where( corrmo['FD'] < 0.5 )
4709
- smth = ( spa, spa, spa, spt ) # this is for sigmaInPhysicalCoordinates = F
4710
- simg = ants.smooth_image(corrmo['motion_corrected'], smth, sigma_in_physical_coordinates = False )
4810
+ smth = ( spa, spa, spa, spt ) # this is for sigmaInPhysicalCoordinates = TRUE
4811
+ simg = ants.smooth_image( corrmo['motion_corrected'], smth, sigma_in_physical_coordinates = True )
4812
+
4813
+ ants.image_write(corrmo['motion_corrected'],'/tmp/tempZZZ.nii.gz')
4711
4814
 
4815
+ # collect censoring indices
4816
+ hlinds = find_indices( corrmo['FD'], FD_threshold )
4817
+ if verbose:
4818
+ print("high motion indices")
4819
+ print( hlinds )
4820
+ if outlier_threshold < 1.0 and outlier_threshold > 0.0:
4821
+ fmrimotcorr, hlinds2 = loop_timeseries_censoring( corrmo['motion_corrected'],
4822
+ threshold=outlier_threshold, verbose=verbose )
4823
+ hlinds.extend( hlinds2 )
4824
+ hlinds = list(set(hlinds)) # make unique
4825
+
4826
+ # nuisance
4827
+ globalmat = ants.timeseries_to_matrix( corrmo['motion_corrected'], bmask )
4828
+ globalsignal = globalmat.mean( axis = 1 )
4829
+ del globalmat
4830
+ compcorquantile=0.975
4831
+ if nc < 1:
4832
+ globalmat = get_compcor_matrix( corrmo['motion_corrected'], csfAndWM, compcorquantile )
4833
+ nc = estimate_optimal_pca_components( data=globalmat, variance_threshold=nc)
4834
+ del globalmat
4835
+ if verbose:
4836
+ print("include compcor components as nuisance: " + str(nc))
4837
+ mycompcor = ants.compcor( corrmo['motion_corrected'],
4838
+ ncompcor=nc, quantile=compcorquantile, mask = csfAndWM,
4839
+ filter_type='polynomial', degree=1 )
4712
4840
  nuisance = mycompcor[ 'components' ]
4841
+
4842
+ if ica_components > 0:
4843
+ if verbose:
4844
+ print("include ica components as nuisance: " + str(ica_components))
4845
+ ica = FastICA(n_components=ica_components, max_iter=10000, tol=0.001, random_state=42 )
4846
+ globalmat = ants.timeseries_to_matrix( corrmo['motion_corrected'], csfAndWM )
4847
+ nuisance_ica = ica.fit_transform(globalmat) # Reconstruct signals
4848
+ nuisance = np.c_[ nuisance, nuisance_ica ]
4849
+ del globalmat
4850
+
4851
+ # concat all nuisance data
4713
4852
  nuisance = np.c_[ nuisance, mycompcor['basis'] ]
4714
4853
  nuisance = np.c_[ nuisance, corrmo['FD'] ]
4854
+ nuisance = np.c_[ nuisance, globalsignal ]
4855
+
4856
+ # bandpass any data collected before here -- if bandpass requested
4857
+ if f[0] > 0 and f[1] < 1.0:
4858
+ if verbose:
4859
+ print( "bandpass: " + str(f[0]) + " <=> " + str( f[1] ) )
4860
+ globalmat = ants.timeseries_to_matrix( corrmo['motion_corrected'], bmask )
4861
+ globalmat = ants.bandpass_filter_matrix( globalmat, tr = tr, lowf=f[0], highf=f[1] ) # some would argue against this
4862
+ corrmo['motion_corrected'] = ants.matrix_to_timeseries( corrmo['motion_corrected'], globalmat, bmask )
4863
+ globalmat = ants.timeseries_to_matrix( simg, bmask )
4864
+ globalmat = ants.bandpass_filter_matrix( globalmat, tr = tr, lowf=f[0], highf=f[1] ) # some would argue against this
4865
+ simg = ants.matrix_to_timeseries( simg, globalmat, bmask )
4866
+ nuisance = ants.bandpass_filter_matrix( nuisance, tr = tr, lowf=f[0], highf=f[1] ) # some would argue against this
4867
+
4868
+ nuisanceall = nuisance.copy()
4869
+ if len( hlinds ) > 0 :
4870
+ if impute and not scrub:
4871
+ corrmo['FD'] = replace_elements_in_numpy_array( corrmo['FD'], hlinds, corrmo['FD'].mean() )
4872
+ corrmo['motion_corrected'] = impute_timeseries( corrmo['motion_corrected'], hlinds, method='linear')
4873
+ simg = simgimp = impute_timeseries( simg, hlinds, method='linear')
4874
+ elif scrub:
4875
+ corrmo['FD'] = remove_elements_from_numpy_array( corrmo['FD'], hlinds )
4876
+ corrmo['motion_corrected'] = remove_volumes_from_timeseries( corrmo['motion_corrected'], hlinds )
4877
+ simgimp = impute_timeseries( simg, hlinds, method='linear')
4878
+ nuisance = remove_elements_from_numpy_array( nuisance, hlinds )
4879
+ simg = remove_volumes_from_timeseries( simg, hlinds )
4880
+ else:
4881
+ simgimp = simg
4882
+ else:
4883
+ simgimp = simg
4884
+
4885
+ if verbose:
4886
+ print("now regress nuisance")
4715
4887
 
4716
- gmmat = ants.timeseries_to_matrix( simg, gmseg )
4717
- gmmat = ants.bandpass_filter_matrix( gmmat, tr = tr, lowf=f[0], highf=f[1] ) # some would argue against this
4718
- gmsignal = gmmat.mean( axis = 1 )
4719
- nuisance = np.c_[ nuisance, gmsignal ]
4888
+ gmmat = ants.timeseries_to_matrix( simgimp, bmask )
4889
+ gmmat = ants.regress_components( gmmat, nuisanceall )
4890
+ simgimp = ants.matrix_to_timeseries(simgimp, gmmat, bmask)
4891
+
4892
+ gmmat = ants.timeseries_to_matrix( simg, bmask )
4720
4893
  gmmat = ants.regress_components( gmmat, nuisance )
4721
- # turn data following nuisance and gsr back to image format
4722
- gsrbold = ants.matrix_to_timeseries(simg, gmmat, gmseg)
4894
+ simg = ants.matrix_to_timeseries(simg, gmmat, bmask)
4723
4895
 
4724
- myfalff=alff_image( simg, bmask, flo=f[0], fhi=f[1], nuisance=nuisance )
4896
+ # falff/alff stuff
4897
+ myfalff=alff_image( simgimp, bmask )
4725
4898
 
4899
+ # structure the output data
4726
4900
  outdict = {}
4727
4901
  outdict['meanBold'] = und
4728
4902
  outdict['pts2bold'] = pts2bold
@@ -4741,7 +4915,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4741
4915
  roiLabel = "ROI" + str(pts2bold.loc[i,'ROI']) + '_' + netLabel
4742
4916
  roiNames.append( roiLabel )
4743
4917
  ptImage = ants.make_points_image(pts2bold.iloc[[i],:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4744
- meanROI[:,i] = ants.timeseries_to_matrix( gsrbold, ptImage).mean(axis=1)
4918
+ meanROI[:,i] = ants.timeseries_to_matrix( simg, ptImage).mean(axis=1)
4745
4919
 
4746
4920
  # get full correlation matrix
4747
4921
  corMat = np.corrcoef(meanROI, rowvar=False)
@@ -4768,14 +4942,12 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4768
4942
  dfnImg = ants.make_points_image(pts2bold.iloc[ww,:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4769
4943
  if dfnImg.max() >= 1:
4770
4944
  dfnmat = ants.timeseries_to_matrix( simg, ants.threshold_image( dfnImg, 1, dfnImg.max() ) )
4771
- dfnmat = ants.bandpass_filter_matrix( dfnmat, tr = tr, lowf=f[0], highf=f[1] )
4772
- dfnmat = ants.regress_components( dfnmat, nuisance )
4773
4945
  dfnsignal = dfnmat.mean( axis = 1 )
4774
4946
  gmmatDFNCorr = np.zeros( gmmat.shape[1] )
4775
4947
  for k in range( gmmat.shape[1] ):
4776
4948
  gmmatDFNCorr[ k ] = pearsonr( dfnsignal, gmmat[:,k] )[0]
4777
- corrImg = ants.make_image( gmseg, gmmatDFNCorr )
4778
- outdict[ netname ] = corrImg
4949
+ corrImg = ants.make_image( bmask, gmmatDFNCorr )
4950
+ outdict[ netname ] = corrImg * gmseg
4779
4951
  else:
4780
4952
  outdict[ netname ] = None
4781
4953
  ct = ct + 1
@@ -4789,7 +4961,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4789
4961
  netnamei = re.sub( " ", "", networks[numofnets[i]] )
4790
4962
  netnamei = re.sub( "-", "", netnamei )
4791
4963
  newnames.append( netnamei )
4792
- binmask = ants.threshold_image( outdict[ netnamei ], 0.2, 1.0 )
4793
4964
  ww = np.where( powers_areal_mni_itk['SystemName'] == networks[numofnets[i]] )[0]
4794
4965
  dfnImg = ants.make_points_image(pts2bold.iloc[ww,:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4795
4966
  for j in range( len( numofnets ) ):
@@ -4808,6 +4979,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4808
4979
  outdict['corr'] = A
4809
4980
  outdict['corr_wide'] = A_wide
4810
4981
  outdict['brainmask'] = bmask
4982
+ outdict['gmmask'] = gmseg
4811
4983
  outdict['alff'] = myfalff['alff']
4812
4984
  outdict['falff'] = myfalff['falff']
4813
4985
  # add global mean and standard deviation for post-hoc z-scoring
@@ -4834,16 +5006,17 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4834
5006
  trimmask = ants.iMath( bmask, "ME",2)
4835
5007
  edgemask = ants.iMath( bmask, "ME",1) - trimmask
4836
5008
  outdict['motion_corrected'] = corrmo['motion_corrected']
4837
- outdict['brain_mask'] = bmask
4838
5009
  outdict['nuisance'] = rsfNuisance
4839
5010
  outdict['tsnr'] = mytsnr
4840
5011
  outdict['ssnr'] = slice_snr( corrmo['motion_corrected'], csfAndWM, gmseg )
4841
5012
  outdict['dvars'] = dvars( corrmo['motion_corrected'], gmseg )
4842
- outdict['high_motion_count'] = (rsfNuisance['FD'] > FD_threshold ).sum()
4843
- outdict['high_motion_pct'] = (rsfNuisance['FD'] > FD_threshold ).sum() / rsfNuisance.shape[0]
5013
+ outdict['high_motion_count'] = high_motion_count
5014
+ outdict['high_motion_pct'] = high_motion_pct
4844
5015
  outdict['FD_max'] = rsfNuisance['FD'].max()
4845
5016
  outdict['FD_mean'] = rsfNuisance['FD'].mean()
4846
5017
  outdict['bold_evr'] = antspyt1w.patch_eigenvalue_ratio( und, 512, [16,16,16], evdepth = 0.9, mask = bmask )
5018
+ outdict['n_outliers'] = len(hlinds)
5019
+ outdict['nc'] = nc
4847
5020
  return outdict
4848
5021
 
4849
5022
 
@@ -5015,8 +5188,9 @@ def bold_perfusion_minimal(
5015
5188
  bmask = bmask * ants.iMath( tsnrmask, "FillHoles" )
5016
5189
  fmrimotcorr=corrmo['motion_corrected']
5017
5190
  und = fmri_template * bmask
5191
+ compcorquantile = 0.975
5018
5192
  mycompcor = ants.compcor( fmrimotcorr,
5019
- ncompcor=nc, quantile=0.975, mask = bmask,
5193
+ ncompcor=nc, quantile=compcorquantile, mask = bmask,
5020
5194
  filter_type='polynomial', degree=2 )
5021
5195
  tr = ants.get_spacing( fmrimotcorr )[3]
5022
5196
  simg = ants.smooth_image(fmrimotcorr, spa, sigma_in_physical_coordinates = True )
@@ -5347,8 +5521,9 @@ def bold_perfusion( fmri, t1head, t1, t1segmentation, t1dktcit,
5347
5521
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
5348
5522
  wmseg = ants.apply_transforms( und, wmseg,
5349
5523
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
5524
+ compcorquantile = 0.975
5350
5525
  mycompcor = ants.compcor( fmrimotcorr,
5351
- ncompcor=nc, quantile=0.975, mask = csfAndWM,
5526
+ ncompcor=nc, quantile=compcorquantile, mask = csfAndWM,
5352
5527
  filter_type='polynomial', degree=2 )
5353
5528
  tr = ants.get_spacing( fmrimotcorr )[3]
5354
5529
  simg = ants.smooth_image(fmrimotcorr, spa, sigma_in_physical_coordinates = True )
@@ -5477,7 +5652,7 @@ Where:
5477
5652
  {'cbf' : df_cbf},
5478
5653
  col_names = ['Mean'] )
5479
5654
  df_cbf = df_cbf.add_prefix('cbf_')
5480
- df_perf = pd.concat( [df_perf,df_cbf], axis=1 )
5655
+ df_perf = pd.concat( [df_perf,df_cbf], axis=1, ignore_index=False )
5481
5656
  if verbose:
5482
5657
  print("perfusion dataframe end")
5483
5658
 
@@ -5501,6 +5676,7 @@ Where:
5501
5676
  outdict['bold_evr'] = antspyt1w.patch_eigenvalue_ratio( und, 512, [16,16,16], evdepth = 0.9, mask = bmask )
5502
5677
  outdict['t1reg'] = t1reg
5503
5678
  outdict['outlier_volumes']=hlinds
5679
+ outdict['n_outliers']=len(hlinds)
5504
5680
  outdict['negative_voxels']=negative_voxels
5505
5681
  return outdict
5506
5682
 
@@ -5744,8 +5920,10 @@ def mm(
5744
5920
  # build a template then join the images
5745
5921
  if verbose:
5746
5922
  print("initial average for rsf")
5747
- rsfavg1=get_average_rsf(rsf_image1)
5748
- rsfavg2=get_average_rsf(rsf_image2)
5923
+ rsfavg1, hlinds = loop_timeseries_censoring( rsf_image1, 0.1 )
5924
+ rsfavg1=get_average_rsf(rsfavg1)
5925
+ rsfavg2, hlinds = loop_timeseries_censoring( rsf_image2, 0.1 )
5926
+ rsfavg2=get_average_rsf(rsfavg2)
5749
5927
  if verbose:
5750
5928
  print("template average for rsf")
5751
5929
  init_temp = ants.image_clone( rsfavg1 )
@@ -5765,7 +5943,8 @@ def mm(
5765
5943
  rsf_image = rsf_image2
5766
5944
  elif len( rsf_image ) == 1:
5767
5945
  rsf_image = rsf_image[0]
5768
- boldTemplate=get_average_rsf(rsf_image)
5946
+ boldTemplate, hlinds = loop_timeseries_censoring( rsf_image, 0.1 )
5947
+ boldTemplate = get_average_rsf(boldTemplate)
5769
5948
  if rsf_image.shape[3] > 10: # FIXME - better heuristic?
5770
5949
  output_dict['rsf'] = resting_state_fmri_networks(
5771
5950
  rsf_image,
@@ -6037,7 +6216,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6037
6216
  fat1derk,
6038
6217
  mdt1derk,
6039
6218
  cnxderk
6040
- ], axis=1 )
6219
+ ], axis=1, ignore_index=False )
6041
6220
  mm_wide = mm_wide.copy()
6042
6221
  if mm['NM'] is not None:
6043
6222
  mm_wide['NM_avg_signaltonoise'] = mm['NM']['NM_avg_signaltonoise']
@@ -6066,7 +6245,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6066
6245
  mm_wide['flair_evr'] = mm['flair']['wmh_evr']
6067
6246
  mm_wide['flair_SNR'] = mm['flair']['wmh_SNR']
6068
6247
  if mm['rsf'] is not None:
6069
- mynets = list([ 'meanBold', 'brain_mask', 'motion_corrected', 'alff', 'falff',
6248
+ mynets = list([ 'meanBold', 'brainmask', 'motion_corrected', 'alff', 'falff',
6070
6249
  'CinguloopercularTaskControl', 'DefaultMode', 'MemoryRetrieval',
6071
6250
  'VentralAttention', 'Visual', 'FrontoparietalTaskControl', 'Salience',
6072
6251
  'Subcortical', 'DorsalAttention', 'tsnr'] )
@@ -6075,7 +6254,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6075
6254
  myop = output_prefix + separator + mykey + '.nii.gz'
6076
6255
  image_write_with_thumbnail( rsfpro[mykey], myop, thumb=True )
6077
6256
  rsfpro['corr_wide'].set_index( mm_wide.index, inplace=True )
6078
- mm_wide = pd.concat( [ mm_wide, rsfpro['corr_wide'] ], axis=1 )
6257
+ mm_wide = pd.concat( [ mm_wide, rsfpro['corr_wide'] ], axis=1, ignore_index=False )
6079
6258
  # falff and alff
6080
6259
  search_key='alffPoint'
6081
6260
  alffkeys = [key for key, val in rsfpro.items() if search_key in key]
@@ -6085,14 +6264,17 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6085
6264
  mm_wide['rsf_dvars_mean'] = rsfpro['dvars'].mean()
6086
6265
  mm_wide['rsf_ssnr_mean'] = rsfpro['ssnr'].mean()
6087
6266
  mm_wide['rsf_high_motion_count'] = rsfpro['high_motion_count']
6088
- # mm_wide['rsf_high_motion_pct'] = rsfpro['rsf_high_motion_pct'] # BUG : rsf_high_motion_pct does not exist
6267
+ mm_wide['rsf_high_motion_pct'] = rsfpro['high_motion_pct']
6089
6268
  mm_wide['rsf_evr'] = rsfpro['bold_evr']
6269
+ mm_wide['rsf_n_outliers'] = rsfpro['n_outliers']
6090
6270
  mm_wide['rsf_FD_mean'] = rsfpro['FD_mean']
6091
6271
  mm_wide['rsf_FD_max'] = rsfpro['FD_max']
6092
6272
  mm_wide['rsf_alff_mean'] = rsfpro['alff_mean']
6093
6273
  mm_wide['rsf_alff_sd'] = rsfpro['alff_sd']
6094
6274
  mm_wide['rsf_falff_mean'] = rsfpro['falff_mean']
6095
6275
  mm_wide['rsf_falff_sd'] = rsfpro['falff_sd']
6276
+ mm_wide['rsf_nc'] = rsfpro['nc']
6277
+ mm_wide['rsf_n_outliers'] = rsfpro['n_outliers']
6096
6278
  ofn = output_prefix + separator + 'rsfcorr.csv'
6097
6279
  rsfpro['corr'].to_csv( ofn )
6098
6280
  # apply same principle to new correlation matrix, doesn't need to be incorporated with mm_wide
@@ -6126,11 +6308,12 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6126
6308
  mm_wide['ssnr_mean'] = perfpro['ssnr'].mean()
6127
6309
  mm_wide['high_motion_count'] = perfpro['high_motion_count']
6128
6310
  mm_wide['evr'] = perfpro['bold_evr']
6311
+ mm_wide['n_outliers'] = perfpro['n_outliers']
6129
6312
  mm_wide['FD_mean'] = perfpro['FD_mean']
6130
6313
  mm_wide['FD_max'] = perfpro['FD_max']
6131
6314
  if 'perf_dataframe' in perfpro.keys():
6132
6315
  pderk = perfpro['perf_dataframe'].iloc[: , 1:]
6133
- mm_wide = pd.concat( [ mm_wide, pderk ], axis=1 )
6316
+ mm_wide = pd.concat( [ mm_wide, pderk ], axis=1, ignore_index=False )
6134
6317
  else:
6135
6318
  print("FIXME - perfusion dataframe")
6136
6319
  for mykey in ['perfusion','cbf']:
@@ -7585,7 +7768,7 @@ def read_mm_csv( x, is_t1=False, colprefix=None, separator='-', verbose=False ):
7585
7768
  return None
7586
7769
  if colprefix is not None:
7587
7770
  xdf.columns=colprefix + xdf.columns
7588
- return pd.concat( [df,xdf], axis=1 )
7771
+ return pd.concat( [df,xdf], axis=1, ignore_index=False )
7589
7772
 
7590
7773
  def merge_wides_to_study_dataframe( sdf, processing_dir, separator='-', sid_is_int=True, id_is_int=True, date_is_int=True, report_missing=False,
7591
7774
  progress=False, verbose=False ):
@@ -7678,7 +7861,7 @@ progress=False, verbose=False ):
7678
7861
  # mm.index=csvrow.index
7679
7862
  uidname = mod_name + '_mmwide_filename'
7680
7863
  mm[ uidname ] = rootid
7681
- csvrow=pd.concat( [csvrow,mm], axis=1 )
7864
+ csvrow=pd.concat( [csvrow,mm], axis=1, ignore_index=False )
7682
7865
  else:
7683
7866
  if verbose and report_missing:
7684
7867
  print( nrgwidefn + " absent")
@@ -7688,7 +7871,7 @@ progress=False, verbose=False ):
7688
7871
  else:
7689
7872
  csvrow=csvrow.loc[:,~csvrow.columns.duplicated()]
7690
7873
  alldf = alldf.loc[:,~alldf.columns.duplicated()]
7691
- alldf = pd.concat( [alldf, csvrow], axis=0, ignore_index=True)
7874
+ alldf = pd.concat( [alldf, csvrow], axis=0, ignore_index=True )
7692
7875
  return alldf
7693
7876
 
7694
7877
  def assemble_modality_specific_dataframes( mm_wide_csvs, hierdfin, nrg_modality, separator='-', progress=None, verbose=False ):
@@ -7708,7 +7891,7 @@ def assemble_modality_specific_dataframes( mm_wide_csvs, hierdfin, nrg_modality,
7708
7891
  for y in fnsnm:
7709
7892
  temp=read_mm_csv( y, colprefix=moddersub+'_', is_t1=False, separator=separator, verbose=verbose )
7710
7893
  if temp is not None:
7711
- nmdf=pd.concat( [nmdf, temp], axis=0)
7894
+ nmdf=pd.concat( [nmdf, temp], axis=0, ignore_index=False )
7712
7895
  return nmdf
7713
7896
 
7714
7897
  def bind_wide_mm_csvs( mm_wide_csvs, merge=True, separator='-', verbose = 0 ) :
@@ -7733,7 +7916,7 @@ def bind_wide_mm_csvs( mm_wide_csvs, merge=True, separator='-', verbose = 0 ) :
7733
7916
  for y in mm_wide_csvs:
7734
7917
  temp=read_mm_csv( y, colprefix='T1Hier_', separator=separator, is_t1=True )
7735
7918
  if temp is not None:
7736
- hierdf=pd.concat( [hierdf, temp], axis=0)
7919
+ hierdf=pd.concat( [hierdf, temp], axis=0, ignore_index=False )
7737
7920
  if verbose > 0:
7738
7921
  mypro=50
7739
7922
  else:
@@ -7846,8 +8029,8 @@ def threaded_bind_wide_mm_csvs( mm_wide_csvs, n_workers ):
7846
8029
  results = []
7847
8030
  for future in futures.as_completed(to_do):
7848
8031
  res0, res1 = future.result()
7849
- alldf=pd.concat( [alldf, res0 ], axis=0 )
7850
- alldfavg=pd.concat( [alldfavg, res1 ], axis=0 )
8032
+ alldf=pd.concat( [alldf, res0 ], axis=0, ignore_index=False )
8033
+ alldfavg=pd.concat( [alldfavg, res1 ], axis=0, ignore_index=False )
7851
8034
  return alldf, alldfavg
7852
8035
 
7853
8036
 
@@ -7858,6 +8041,9 @@ def get_names_from_data_frame(x, demogIn, exclusions=None):
7858
8041
  antspymm.get_names_from_data_frame( ['a','e'], df )
7859
8042
  antspymm.get_names_from_data_frame( ['e'], df, exclusions='N' )
7860
8043
  """
8044
+ # Check if x is a string and convert it to a list
8045
+ if isinstance(x, str):
8046
+ x = [x]
7861
8047
  def get_unique( qq ):
7862
8048
  unique = []
7863
8049
  for number in qq:
@@ -7936,7 +8122,7 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
7936
8122
  jmm.loc[k, dt0[1:]] = nanList * len(v1)
7937
8123
  if verbose:
7938
8124
  print( joinDiagnosticsLoc )
7939
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8125
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0, ignore_index=False )
7940
8126
 
7941
8127
  if verbose:
7942
8128
  print("do DTI")
@@ -7990,7 +8176,7 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
7990
8176
  jmm.loc[k, dt0[1:]] = nanList * len( dt0[1:] )
7991
8177
  if verbose:
7992
8178
  print( joinDiagnosticsLoc )
7993
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8179
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0, ignore_index=False )
7994
8180
 
7995
8181
 
7996
8182
  # first task - sort by u_hier_id
@@ -8049,7 +8235,8 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
8049
8235
  jmmUniq.loc[u][fl_names[1:]] = temp.mean(axis=0)
8050
8236
  else:
8051
8237
  jmmUniq.loc[u][fl_names[1:]] = nanList * temp.shape[1]
8052
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8238
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc],
8239
+ axis=0, ignore_index=False )
8053
8240
 
8054
8241
  return jmmUniq, jmm, joinDiagnostics
8055
8242
 
@@ -8453,7 +8640,7 @@ def blind_image_assessment(
8453
8640
  viz_filename_use = re.sub( ".png", "_slice"+str(jjj).zfill(4)+".png", viz_filename )
8454
8641
  ants.plot_ortho( image, crop=False, filename=viz_filename_use, flat=True, xyz_lines=False, orient_labels=False, xyz_pad=0, title=ttl, titlefontsize=12, title_dy=-0.02,textfontcolor='red' )
8455
8642
  df = pd.DataFrame([[ mystem, noizlevel, snrref, cnrref, psnrref, ssimref, mymi, asym_err, myevr, msk_vol, spc[0], spc[1], spc[2],org[0], org[1], org[2], image.shape[0], image.shape[1], image.shape[2], jjj, modality, mriseries, mrimfg, mrimodel ]], columns=['filename', 'noise', 'snr', 'cnr', 'psnr', 'ssim', 'mi', 'reflection_err', 'EVR', 'msk_vol', 'spc0','spc1','spc2','org0','org1','org2','dimx','dimy','dimz','slice','modality', 'mriseries', 'mrimfg', 'mrimodel' ])
8456
- outdf = pd.concat( [outdf, df ], axis=0 )
8643
+ outdf = pd.concat( [outdf, df ], axis=0, ignore_index=False )
8457
8644
  if verbose:
8458
8645
  print( outdf )
8459
8646
  if viz_filename is not None:
@@ -8638,12 +8825,22 @@ def replace_elements_in_numpy_array(original_array, indices_to_replace, new_valu
8638
8825
  if original_array is None:
8639
8826
  return None
8640
8827
 
8828
+ max_index = original_array.size if original_array.ndim == 1 else original_array.shape[0]
8829
+
8830
+ # Filter out invalid indices and check for any out-of-bounds indices
8831
+ valid_indices = []
8832
+ for idx in indices_to_replace:
8833
+ if idx < max_index:
8834
+ valid_indices.append(idx)
8835
+ else:
8836
+ warnings.warn(f"Warning: Index {idx} is out of bounds and will be ignored.")
8837
+
8641
8838
  if original_array.ndim == 1:
8642
8839
  # Replace elements in a 1D array
8643
- original_array[indices_to_replace] = new_value
8840
+ original_array[valid_indices] = new_value
8644
8841
  elif original_array.ndim == 2:
8645
8842
  # Replace rows in a 2D array
8646
- original_array[indices_to_replace, :] = new_value
8843
+ original_array[valid_indices, :] = new_value
8647
8844
  else:
8648
8845
  raise ValueError("original_array must be either 1D or 2D.")
8649
8846
 
@@ -8699,13 +8896,14 @@ def remove_volumes_from_timeseries(time_series, volumes_to_remove):
8699
8896
  return ants.copy_image_info( time_series, filtered_time_series )
8700
8897
 
8701
8898
 
8702
- def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8899
+ def impute_timeseries(time_series, volumes_to_impute, method='linear', verbose=False):
8703
8900
  """
8704
8901
  Impute specified volumes from a time series with interpolated values.
8705
8902
 
8706
8903
  :param time_series: ANTsImage representing the time series (4D image).
8707
8904
  :param volumes_to_impute: List of volume indices to impute.
8708
8905
  :param method: Interpolation method ('linear' or other methods if implemented).
8906
+ :param verbose: boolean
8709
8907
  :return: ANTsImage with specified volumes imputed.
8710
8908
  """
8711
8909
  if not isinstance(time_series, ants.ANTsImage):
@@ -8716,25 +8914,36 @@ def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8716
8914
 
8717
8915
  # Convert time_series to numpy for manipulation
8718
8916
  time_series_np = time_series.numpy()
8917
+ total_volumes = time_series_np.shape[3]
8918
+
8919
+ # Create a complement list of volumes not to impute
8920
+ volumes_not_to_impute = [i for i in range(total_volumes) if i not in volumes_to_impute]
8921
+
8922
+ # Define the lower and upper bounds
8923
+ min_valid_index = min(volumes_not_to_impute)
8924
+ max_valid_index = max(volumes_not_to_impute)
8719
8925
 
8720
8926
  for vol_idx in volumes_to_impute:
8721
8927
  # Ensure the volume index is within the valid range
8722
- if vol_idx < 0 or vol_idx >= time_series_np.shape[3]:
8928
+ if vol_idx < 0 or vol_idx >= total_volumes:
8723
8929
  raise ValueError(f"Volume index {vol_idx} is out of bounds.")
8724
8930
 
8725
- # Find neighboring volumes for interpolation
8726
- lower_idx = max(0, vol_idx - 1)
8727
- upper_idx = min(time_series_np.shape[3] - 1, vol_idx + 1)
8931
+ # Find the nearest valid lower index within the bounds
8932
+ lower_candidates = [v for v in volumes_not_to_impute if v <= vol_idx]
8933
+ lower_idx = max(lower_candidates) if lower_candidates else min_valid_index
8934
+
8935
+ # Find the nearest valid upper index within the bounds
8936
+ upper_candidates = [v for v in volumes_not_to_impute if v >= vol_idx]
8937
+ upper_idx = min(upper_candidates) if upper_candidates else max_valid_index
8938
+
8939
+ if verbose:
8940
+ print(f"Imputing volume {vol_idx} using indices {lower_idx} and {upper_idx}")
8728
8941
 
8729
8942
  if method == 'linear':
8730
8943
  # Linear interpolation between the two nearest volumes
8731
- if lower_idx == vol_idx or upper_idx == vol_idx:
8732
- # Edge case: duplicate the closest volume
8733
- interpolated_volume = time_series_np[..., lower_idx]
8734
- else:
8735
- lower_volume = time_series_np[..., lower_idx]
8736
- upper_volume = time_series_np[..., upper_idx]
8737
- interpolated_volume = (lower_volume + upper_volume) / 2
8944
+ lower_volume = time_series_np[..., lower_idx]
8945
+ upper_volume = time_series_np[..., upper_idx]
8946
+ interpolated_volume = (lower_volume + upper_volume) / 2
8738
8947
  else:
8739
8948
  # Placeholder for other interpolation methods
8740
8949
  raise NotImplementedError("Currently, only linear interpolation is implemented.")
@@ -8748,7 +8957,6 @@ def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8748
8957
 
8749
8958
  return imputed_time_series
8750
8959
 
8751
-
8752
8960
  def impute_dwi( dwi, threshold = 0.20, verbose=False ):
8753
8961
  """
8754
8962
  Identify bad volumes in a dwi and impute them fully automatically.
@@ -8777,6 +8985,36 @@ def impute_dwi( dwi, threshold = 0.20, verbose=False ):
8777
8985
  return dwi
8778
8986
  return impute_timeseries( dwi, complement )
8779
8987
 
8988
+ def scrub_dwi( dwi, bval, bvec, threshold = 0.20, verbose=False ):
8989
+ """
8990
+ Identify bad volumes in a dwi and impute them fully automatically.
8991
+
8992
+ :param dwi: ANTsImage representing the time series (4D image).
8993
+ :param bval: bval array
8994
+ :param bvec: bvec array
8995
+ :param threshold: threshold (0,1) for outlierness (lower means impute more data)
8996
+ :param verbose: boolean
8997
+ :return: ANTsImage automatically imputed.
8998
+ """
8999
+ list1 = segment_timeseries_by_meanvalue( dwi )['highermeans']
9000
+ looped, list2 = loop_timeseries_censoring( dwi, threshold )
9001
+ if verbose:
9002
+ print( list1 )
9003
+ print( list2 )
9004
+ # Convert lists to sets
9005
+ set1 = set(list(list1))
9006
+ set2 = set(list(list2))
9007
+ # Find the intersection
9008
+ intersection = set1 & set2
9009
+ # Find the complement of the intersection
9010
+ complement = list( (set1 | set2) - intersection )
9011
+ if verbose:
9012
+ print( "scrubbing:")
9013
+ print( complement )
9014
+ if len( complement ) == 0:
9015
+ return dwi, bval, bvec
9016
+ return remove_volumes_from_timeseries( dwi, complement ), remove_elements_from_numpy_array( bval, complement ), remove_elements_from_numpy_array( bvec, complement )
9017
+
8780
9018
 
8781
9019
  def flatten_time_series(time_series):
8782
9020
  """
@@ -9207,6 +9445,42 @@ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, brain_i
9207
9445
  return addem
9208
9446
 
9209
9447
 
9448
+ def filter_df(indf, myprefix):
9449
+ """
9450
+ Process and filter a pandas DataFrame, removing certain columns,
9451
+ filtering based on data types, computing the mean of numeric columns,
9452
+ and adding a prefix to column names.
9453
+
9454
+ Parameters:
9455
+ indf (pandas.DataFrame): The input DataFrame to be processed.
9456
+ myprefix (str): A string prefix to be added to the column names
9457
+ of the processed DataFrame.
9458
+
9459
+ Steps:
9460
+ 1. Removes columns with names containing 'Unnamed'.
9461
+ 2. If the DataFrame has no rows, it returns the empty DataFrame.
9462
+ 3. Filters out columns based on the type of the first element,
9463
+ keeping those that are of type `object`, `int`, or `float`.
9464
+ 4. Removes columns that are of `object` dtype.
9465
+ 5. Calculates the mean of the remaining columns, skipping NaN values.
9466
+ 6. Adds the specified `myprefix` to the column names.
9467
+
9468
+ Returns:
9469
+ pandas.DataFrame: A transformed DataFrame with a single row containing
9470
+ the mean values of the filtered columns, and with
9471
+ column names prefixed as specified.
9472
+ """
9473
+ indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
9474
+ if indf.shape[0] == 0:
9475
+ return indf
9476
+ nums = [isinstance(indf[col].iloc[0], (object, int, float)) for col in indf.columns]
9477
+ indf = indf.loc[:, nums]
9478
+ indf = indf.loc[:, indf.dtypes != 'object']
9479
+ indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
9480
+ indf = indf.add_prefix(myprefix)
9481
+ return indf
9482
+
9483
+
9210
9484
  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 ):
9211
9485
  """
9212
9486
  Aggregate ANTsPyMM results from the specified CSV file and save the aggregated results to a new CSV file.
@@ -9235,15 +9509,6 @@ def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='dat
9235
9509
  import numpy as np
9236
9510
  from glob import glob
9237
9511
 
9238
- def filter_df( indf, myprefix ):
9239
- nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
9240
- indf = indf.loc[:, nums]
9241
- indf=indf.loc[:, indf.dtypes != 'object' ]
9242
- indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
9243
- indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
9244
- indf = indf.add_prefix( myprefix )
9245
- return( indf )
9246
-
9247
9512
  def myread_csv(x, cnms):
9248
9513
  """
9249
9514
  Reads a CSV file and returns a DataFrame excluding specified columns.
@@ -9339,13 +9604,13 @@ def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='dat
9339
9604
  t1df = filter_df( t1df, mymod+'_')
9340
9605
  dflist = dflist + [t1df]
9341
9606
 
9342
- hdf = pd.concat( dflist, axis=1)
9607
+ hdf = pd.concat( dflist, axis=1, ignore_index=False )
9343
9608
  if verbose:
9344
9609
  print( df.loc[locind,'filename'] )
9345
9610
  if myct == 1:
9346
9611
  subdf = df.iloc[[x]]
9347
9612
  hdf.index = subdf.index.copy()
9348
- df = pd.concat( [df,hdf], axis=1)
9613
+ df = pd.concat( [df,hdf], axis=1, ignore_index=False )
9349
9614
  else:
9350
9615
  commcols = list(set(hdf.columns).intersection(df.columns))
9351
9616
  df.loc[locind, commcols] = hdf.loc[0, commcols]
@@ -9400,14 +9665,18 @@ def aggregate_antspymm_results_sdf(
9400
9665
  import numpy as np
9401
9666
  from glob import glob
9402
9667
 
9403
- def filter_df( indf, myprefix ):
9404
- nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
9405
- indf = indf.loc[:, nums]
9406
- indf=indf.loc[:, indf.dtypes != 'object' ]
9407
- indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
9408
- indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
9409
- indf = indf.add_prefix( myprefix )
9410
- return( indf )
9668
+ def progress_reporter(current_step, total_steps, width=50):
9669
+ # Calculate the proportion of progress
9670
+ progress = current_step / total_steps
9671
+ # Calculate the number of 'filled' characters in the progress bar
9672
+ filled_length = int(width * progress)
9673
+ # Create the progress bar string
9674
+ bar = '█' * filled_length + '-' * (width - filled_length)
9675
+ # Print the progress bar with percentage
9676
+ print(f'\rProgress: |{bar}| {int(100 * progress)}%', end='\r')
9677
+ # Print a new line when the progress is complete
9678
+ if current_step == total_steps:
9679
+ print()
9411
9680
 
9412
9681
  def myread_csv(x, cnms):
9413
9682
  """
@@ -9451,8 +9720,8 @@ def aggregate_antspymm_results_sdf(
9451
9720
  myfn = os.path.basename( df['filename'].iloc[x] )
9452
9721
  temp = myfn.split( splitsep )
9453
9722
  # Generalized search paths
9454
- sid0 = temp[0]
9455
- sid = str(df[subject_col].iloc[x])
9723
+ sid0 = str( temp[0] )
9724
+ sid = str( df[subject_col].iloc[x] )
9456
9725
  if sid0 != sid:
9457
9726
  warnings.warn("OUTER: the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
9458
9727
  warnings.warn( "filename is : " + myfn )
@@ -9480,18 +9749,20 @@ def aggregate_antspymm_results_sdf(
9480
9749
  dfout = pd.DataFrame()
9481
9750
  myct = 0
9482
9751
  for x in range( df.shape[0]):
9483
- print("\n\n-------------------------------------------------")
9484
9752
  if verbose:
9753
+ print("\n\n-------------------------------------------------")
9485
9754
  print(f"{x}...")
9755
+ else:
9756
+ progress_reporter(x, df.shape[0], width=500)
9486
9757
  locind = df.index[x]
9487
9758
  myfn = os.path.basename( df['filename'].iloc[x] )
9488
- sid = df[subject_col].iloc[x]
9759
+ sid = str( df[subject_col].iloc[x] )
9489
9760
  tempB = myfn.split( splitsep )
9490
- sid0 = tempB[1]
9491
- if sid0 != sid:
9492
- warnings.warn("INNER: the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid0 )
9493
- warnings.warn( "filename is : " + myfn )
9494
- warnings.warn( "sid is : " + sid )
9761
+ sid0 = str(tempB[1])
9762
+ if sid0 != sid and verbose:
9763
+ warnings.warn("INNER: the id derived from the filename " + str(sid) + " does not match the id stored in the data frame " + str(sid0) )
9764
+ warnings.warn( "filename is : " + str(myfn) )
9765
+ warnings.warn( "sid is : " + str(sid) )
9495
9766
  warnings.warn( "x is : " + str(x) )
9496
9767
  warnings.warn( "index is : " + str(locind) )
9497
9768
  myproj = str(df[project_col].iloc[x])
@@ -9526,7 +9797,8 @@ def aggregate_antspymm_results_sdf(
9526
9797
  dflist = [hdf]
9527
9798
 
9528
9799
  for mymod in vmoddict.keys():
9529
- print("\n\n************************* " + mymod + " *************************")
9800
+ if verbose:
9801
+ print("\n\n************************* " + mymod + " *************************")
9530
9802
  modalityclass = vmoddict[ mymod ]
9531
9803
  if wild_card_modality_id:
9532
9804
  mymodid = '*'
@@ -9539,7 +9811,8 @@ def aggregate_antspymm_results_sdf(
9539
9811
  temp = mymodid.split( idsep )
9540
9812
  mymodid = temp[ len( temp )-1 ]
9541
9813
  else:
9542
- print("missing")
9814
+ if verbose:
9815
+ print("missing")
9543
9816
  continue
9544
9817
  if verbose:
9545
9818
  print( "modality id is " + mymodid + " for modality " + modalityclass + ' modality specific subj ' + sid + ' modality specific id is ' + myid + " its date " + mydate )
@@ -9552,7 +9825,10 @@ def aggregate_antspymm_results_sdf(
9552
9825
  print( modsearch )
9553
9826
  t1wfn = sorted( glob( modsearch ) )
9554
9827
  if len( t1wfn ) > 1:
9555
- raise ValueError("there are " + str( len( t1wfn ) ) + " number of wide fns with search path " + modsearch )
9828
+ nlarge = len(t1wfn)
9829
+ t1wfn = [ t1wfn[ len(t1wfn)-1 ] ]
9830
+ warnings.warn("there are " + str( nlarge ) + " number of wide fns with search path " + modsearch + " we take the last of these " + t1wfn[0] )
9831
+ # raise ValueError("there are " + str( len( t1wfn ) ) + " number of wide fns with search path " + modsearch )
9556
9832
  if len( t1wfn ) == 1:
9557
9833
  if verbose:
9558
9834
  print(t1wfn)
@@ -9563,13 +9839,13 @@ def aggregate_antspymm_results_sdf(
9563
9839
  if verbose:
9564
9840
  print( " cannot find " + modsearch )
9565
9841
 
9566
- hdf = pd.concat( dflist, axis=1)
9842
+ hdf = pd.concat( dflist, axis=1, ignore_index=False)
9567
9843
  if verbose:
9568
9844
  print( "count: " + str( myct ) )
9569
9845
  subdf = df.iloc[[x]]
9570
9846
  hdf.index = subdf.index.copy()
9571
- subdf = pd.concat( [subdf,hdf], axis=1)
9572
- dfout = pd.concat( [dfout,subdf], axis=0)
9847
+ subdf = pd.concat( [subdf,hdf], axis=1, ignore_index=False)
9848
+ dfout = pd.concat( [dfout,subdf], axis=0, ignore_index=False )
9573
9849
  badnames = get_names_from_data_frame( ['Unnamed'], dfout )
9574
9850
  dfout=dfout.drop(badnames, axis=1)
9575
9851
  return( dfout )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: antspymm
3
- Version: 1.1.8
3
+ Version: 1.1.9
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
@@ -0,0 +1,7 @@
1
+ antspymm/__init__.py,sha256=xQWo4DJQ0uK0_7G0kjgrj7SZoar5oB1FCV6jeTpTDcc,3805
2
+ antspymm/mm.py,sha256=Z1tcO04yl3CHmCbQpDdZocllIZbOw6wRGM-sKEcXcIQ,405265
3
+ antspymm-1.1.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
+ antspymm-1.1.9.dist-info/METADATA,sha256=FgaDoUndsazM2rNGB5ZG-s3x9pt2e5TFnMDWumUDoT8,14162
5
+ antspymm-1.1.9.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ antspymm-1.1.9.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
+ antspymm-1.1.9.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- antspymm/__init__.py,sha256=qKqcY0wEcRsFFNAlz6jP5g6EVWtbVHh1_YbpSb5nQaU,3704
2
- antspymm/mm.py,sha256=IqeWKjrtRY4iS3U07JZlmfc6nKDsOzNLMPYy3Mj4Qes,393899
3
- antspymm-1.1.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-1.1.8.dist-info/METADATA,sha256=qqYU8JQ8R_84LqLwo5aeViciGkoY8EbiPE7_s8AUlJA,14162
5
- antspymm-1.1.8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- antspymm-1.1.8.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-1.1.8.dist-info/RECORD,,