antspymm 1.1.7__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
@@ -103,9 +103,14 @@ from .mm import docsamson
103
103
  from .mm import enantiomorphic_filling_without_mask
104
104
  from .mm import loop_timeseries_censoring
105
105
  from .mm import score_fmri_censoring
106
+ from .mm import replace_elements_in_numpy_array
106
107
  from .mm import remove_elements_from_numpy_array
107
108
  from .mm import remove_volumes_from_timeseries
108
109
  from .mm import impute_timeseries
109
110
  from .mm import impute_dwi
111
+ from .mm import scrub_dwi
110
112
  from .mm import timeseries_n3
111
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,10 +4603,53 @@ 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.03,0.08], FD_threshold=0.5, spa = 1.5, spt = 0.5, nc = 6, type_of_transform='Rigid',
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,
4601
4652
  verbose=False ):
4602
-
4603
4653
  """
4604
4654
  Compute resting state network correlation maps based on the J Power labels.
4605
4655
  This will output a map for each of the major network systems.
@@ -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,13 +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")
4767
+
4768
+ high_motion_count=(corrmo['FD'] > FD_threshold ).sum()
4769
+ high_motion_pct=high_motion_count / fmri.shape[3]
4659
4770
 
4771
+ # filter mask based on TSNR
4660
4772
  mytsnr = tsnr( corrmo['motion_corrected'], bmask )
4661
4773
  mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
4662
4774
  tsnrmask = ants.threshold_image( mytsnr, 0, mytsnrThresh ).morphology("close",2)
4663
4775
  bmask = bmask * tsnrmask
4776
+
4777
+ # anatomical mapping
4664
4778
  und = fmri_template * bmask
4665
4779
  t1reg = ants.registration( und, t1, "SyNBold" )
4666
4780
  if verbose:
@@ -4677,14 +4791,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4677
4791
  ants.threshold_image( t1segmentation, 3, 3 ) ).morphology("erode",1)
4678
4792
  csfAndWM = ants.apply_transforms( und, csfAndWM,
4679
4793
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
4680
-
4681
- # get falff and alff
4682
- mycompcor = ants.compcor( corrmo['motion_corrected'],
4683
- ncompcor=nc, quantile=0.90, mask = csfAndWM,
4684
- filter_type='polynomial', degree=2 )
4685
-
4686
4794
  nt = corrmo['motion_corrected'].shape[3]
4687
-
4688
4795
  myvoxes = range(powers_areal_mni_itk.shape[0])
4689
4796
  anat = powers_areal_mni_itk['Anatomy']
4690
4797
  syst = powers_areal_mni_itk['SystemName']
@@ -4698,26 +4805,98 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4698
4805
  locations = pts2bold.iloc[:,:3].values
4699
4806
  ptImg = ants.make_points_image( locations, bmask, radius = 2 )
4700
4807
 
4808
+ # optional smoothing
4701
4809
  tr = ants.get_spacing( corrmo['motion_corrected'] )[3]
4702
- highMotionTimes = np.where( corrmo['FD'] >= 1.0 )
4703
- goodtimes = np.where( corrmo['FD'] < 0.5 )
4704
- smth = ( spa, spa, spa, spt ) # this is for sigmaInPhysicalCoordinates = F
4705
- 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 )
4706
4812
 
4813
+ ants.image_write(corrmo['motion_corrected'],'/tmp/tempZZZ.nii.gz')
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 )
4707
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
4708
4852
  nuisance = np.c_[ nuisance, mycompcor['basis'] ]
4709
4853
  nuisance = np.c_[ nuisance, corrmo['FD'] ]
4854
+ nuisance = np.c_[ nuisance, globalsignal ]
4710
4855
 
4711
- gmmat = ants.timeseries_to_matrix( simg, gmseg )
4712
- gmmat = ants.bandpass_filter_matrix( gmmat, tr = tr, lowf=f[0], highf=f[1] ) # some would argue against this
4713
- gmsignal = gmmat.mean( axis = 1 )
4714
- nuisance = np.c_[ nuisance, gmsignal ]
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")
4887
+
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 )
4715
4893
  gmmat = ants.regress_components( gmmat, nuisance )
4716
- # turn data following nuisance and gsr back to image format
4717
- gsrbold = ants.matrix_to_timeseries(simg, gmmat, gmseg)
4894
+ simg = ants.matrix_to_timeseries(simg, gmmat, bmask)
4718
4895
 
4719
- myfalff=alff_image( simg, bmask, flo=f[0], fhi=f[1], nuisance=nuisance )
4896
+ # falff/alff stuff
4897
+ myfalff=alff_image( simgimp, bmask )
4720
4898
 
4899
+ # structure the output data
4721
4900
  outdict = {}
4722
4901
  outdict['meanBold'] = und
4723
4902
  outdict['pts2bold'] = pts2bold
@@ -4736,7 +4915,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4736
4915
  roiLabel = "ROI" + str(pts2bold.loc[i,'ROI']) + '_' + netLabel
4737
4916
  roiNames.append( roiLabel )
4738
4917
  ptImage = ants.make_points_image(pts2bold.iloc[[i],:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4739
- meanROI[:,i] = ants.timeseries_to_matrix( gsrbold, ptImage).mean(axis=1)
4918
+ meanROI[:,i] = ants.timeseries_to_matrix( simg, ptImage).mean(axis=1)
4740
4919
 
4741
4920
  # get full correlation matrix
4742
4921
  corMat = np.corrcoef(meanROI, rowvar=False)
@@ -4763,14 +4942,12 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4763
4942
  dfnImg = ants.make_points_image(pts2bold.iloc[ww,:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4764
4943
  if dfnImg.max() >= 1:
4765
4944
  dfnmat = ants.timeseries_to_matrix( simg, ants.threshold_image( dfnImg, 1, dfnImg.max() ) )
4766
- dfnmat = ants.bandpass_filter_matrix( dfnmat, tr = tr, lowf=f[0], highf=f[1] )
4767
- dfnmat = ants.regress_components( dfnmat, nuisance )
4768
4945
  dfnsignal = dfnmat.mean( axis = 1 )
4769
4946
  gmmatDFNCorr = np.zeros( gmmat.shape[1] )
4770
4947
  for k in range( gmmat.shape[1] ):
4771
4948
  gmmatDFNCorr[ k ] = pearsonr( dfnsignal, gmmat[:,k] )[0]
4772
- corrImg = ants.make_image( gmseg, gmmatDFNCorr )
4773
- outdict[ netname ] = corrImg
4949
+ corrImg = ants.make_image( bmask, gmmatDFNCorr )
4950
+ outdict[ netname ] = corrImg * gmseg
4774
4951
  else:
4775
4952
  outdict[ netname ] = None
4776
4953
  ct = ct + 1
@@ -4784,7 +4961,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4784
4961
  netnamei = re.sub( " ", "", networks[numofnets[i]] )
4785
4962
  netnamei = re.sub( "-", "", netnamei )
4786
4963
  newnames.append( netnamei )
4787
- binmask = ants.threshold_image( outdict[ netnamei ], 0.2, 1.0 )
4788
4964
  ww = np.where( powers_areal_mni_itk['SystemName'] == networks[numofnets[i]] )[0]
4789
4965
  dfnImg = ants.make_points_image(pts2bold.iloc[ww,:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4790
4966
  for j in range( len( numofnets ) ):
@@ -4803,6 +4979,7 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4803
4979
  outdict['corr'] = A
4804
4980
  outdict['corr_wide'] = A_wide
4805
4981
  outdict['brainmask'] = bmask
4982
+ outdict['gmmask'] = gmseg
4806
4983
  outdict['alff'] = myfalff['alff']
4807
4984
  outdict['falff'] = myfalff['falff']
4808
4985
  # add global mean and standard deviation for post-hoc z-scoring
@@ -4829,16 +5006,17 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4829
5006
  trimmask = ants.iMath( bmask, "ME",2)
4830
5007
  edgemask = ants.iMath( bmask, "ME",1) - trimmask
4831
5008
  outdict['motion_corrected'] = corrmo['motion_corrected']
4832
- outdict['brain_mask'] = bmask
4833
5009
  outdict['nuisance'] = rsfNuisance
4834
5010
  outdict['tsnr'] = mytsnr
4835
5011
  outdict['ssnr'] = slice_snr( corrmo['motion_corrected'], csfAndWM, gmseg )
4836
5012
  outdict['dvars'] = dvars( corrmo['motion_corrected'], gmseg )
4837
- outdict['high_motion_count'] = (rsfNuisance['FD'] > FD_threshold ).sum()
4838
- 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
4839
5015
  outdict['FD_max'] = rsfNuisance['FD'].max()
4840
5016
  outdict['FD_mean'] = rsfNuisance['FD'].mean()
4841
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
4842
5020
  return outdict
4843
5021
 
4844
5022
 
@@ -5010,8 +5188,9 @@ def bold_perfusion_minimal(
5010
5188
  bmask = bmask * ants.iMath( tsnrmask, "FillHoles" )
5011
5189
  fmrimotcorr=corrmo['motion_corrected']
5012
5190
  und = fmri_template * bmask
5191
+ compcorquantile = 0.975
5013
5192
  mycompcor = ants.compcor( fmrimotcorr,
5014
- ncompcor=nc, quantile=0.975, mask = bmask,
5193
+ ncompcor=nc, quantile=compcorquantile, mask = bmask,
5015
5194
  filter_type='polynomial', degree=2 )
5016
5195
  tr = ants.get_spacing( fmrimotcorr )[3]
5017
5196
  simg = ants.smooth_image(fmrimotcorr, spa, sigma_in_physical_coordinates = True )
@@ -5342,8 +5521,9 @@ def bold_perfusion( fmri, t1head, t1, t1segmentation, t1dktcit,
5342
5521
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
5343
5522
  wmseg = ants.apply_transforms( und, wmseg,
5344
5523
  t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
5524
+ compcorquantile = 0.975
5345
5525
  mycompcor = ants.compcor( fmrimotcorr,
5346
- ncompcor=nc, quantile=0.975, mask = csfAndWM,
5526
+ ncompcor=nc, quantile=compcorquantile, mask = csfAndWM,
5347
5527
  filter_type='polynomial', degree=2 )
5348
5528
  tr = ants.get_spacing( fmrimotcorr )[3]
5349
5529
  simg = ants.smooth_image(fmrimotcorr, spa, sigma_in_physical_coordinates = True )
@@ -5472,7 +5652,7 @@ Where:
5472
5652
  {'cbf' : df_cbf},
5473
5653
  col_names = ['Mean'] )
5474
5654
  df_cbf = df_cbf.add_prefix('cbf_')
5475
- df_perf = pd.concat( [df_perf,df_cbf], axis=1 )
5655
+ df_perf = pd.concat( [df_perf,df_cbf], axis=1, ignore_index=False )
5476
5656
  if verbose:
5477
5657
  print("perfusion dataframe end")
5478
5658
 
@@ -5496,6 +5676,7 @@ Where:
5496
5676
  outdict['bold_evr'] = antspyt1w.patch_eigenvalue_ratio( und, 512, [16,16,16], evdepth = 0.9, mask = bmask )
5497
5677
  outdict['t1reg'] = t1reg
5498
5678
  outdict['outlier_volumes']=hlinds
5679
+ outdict['n_outliers']=len(hlinds)
5499
5680
  outdict['negative_voxels']=negative_voxels
5500
5681
  return outdict
5501
5682
 
@@ -5739,8 +5920,10 @@ def mm(
5739
5920
  # build a template then join the images
5740
5921
  if verbose:
5741
5922
  print("initial average for rsf")
5742
- rsfavg1=get_average_rsf(rsf_image1)
5743
- 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)
5744
5927
  if verbose:
5745
5928
  print("template average for rsf")
5746
5929
  init_temp = ants.image_clone( rsfavg1 )
@@ -5760,17 +5943,14 @@ def mm(
5760
5943
  rsf_image = rsf_image2
5761
5944
  elif len( rsf_image ) == 1:
5762
5945
  rsf_image = rsf_image[0]
5763
- boldTemplate=get_average_rsf(rsf_image)
5946
+ boldTemplate, hlinds = loop_timeseries_censoring( rsf_image, 0.1 )
5947
+ boldTemplate = get_average_rsf(boldTemplate)
5764
5948
  if rsf_image.shape[3] > 10: # FIXME - better heuristic?
5765
5949
  output_dict['rsf'] = resting_state_fmri_networks(
5766
5950
  rsf_image,
5767
5951
  boldTemplate,
5768
5952
  hier['brain_n4_dnz'],
5769
- t1atropos,
5770
- f=[0.03,0.08],
5771
- spa = 1.0,
5772
- spt = 0.5,
5773
- nc = 6, verbose=verbose )
5953
+ t1atropos, verbose=verbose )
5774
5954
  if nm_image_list is not None:
5775
5955
  if verbose:
5776
5956
  print('nm')
@@ -6036,7 +6216,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6036
6216
  fat1derk,
6037
6217
  mdt1derk,
6038
6218
  cnxderk
6039
- ], axis=1 )
6219
+ ], axis=1, ignore_index=False )
6040
6220
  mm_wide = mm_wide.copy()
6041
6221
  if mm['NM'] is not None:
6042
6222
  mm_wide['NM_avg_signaltonoise'] = mm['NM']['NM_avg_signaltonoise']
@@ -6065,7 +6245,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6065
6245
  mm_wide['flair_evr'] = mm['flair']['wmh_evr']
6066
6246
  mm_wide['flair_SNR'] = mm['flair']['wmh_SNR']
6067
6247
  if mm['rsf'] is not None:
6068
- mynets = list([ 'meanBold', 'brain_mask', 'motion_corrected', 'alff', 'falff',
6248
+ mynets = list([ 'meanBold', 'brainmask', 'motion_corrected', 'alff', 'falff',
6069
6249
  'CinguloopercularTaskControl', 'DefaultMode', 'MemoryRetrieval',
6070
6250
  'VentralAttention', 'Visual', 'FrontoparietalTaskControl', 'Salience',
6071
6251
  'Subcortical', 'DorsalAttention', 'tsnr'] )
@@ -6074,7 +6254,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6074
6254
  myop = output_prefix + separator + mykey + '.nii.gz'
6075
6255
  image_write_with_thumbnail( rsfpro[mykey], myop, thumb=True )
6076
6256
  rsfpro['corr_wide'].set_index( mm_wide.index, inplace=True )
6077
- 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 )
6078
6258
  # falff and alff
6079
6259
  search_key='alffPoint'
6080
6260
  alffkeys = [key for key, val in rsfpro.items() if search_key in key]
@@ -6084,14 +6264,17 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6084
6264
  mm_wide['rsf_dvars_mean'] = rsfpro['dvars'].mean()
6085
6265
  mm_wide['rsf_ssnr_mean'] = rsfpro['ssnr'].mean()
6086
6266
  mm_wide['rsf_high_motion_count'] = rsfpro['high_motion_count']
6087
- # 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']
6088
6268
  mm_wide['rsf_evr'] = rsfpro['bold_evr']
6269
+ mm_wide['rsf_n_outliers'] = rsfpro['n_outliers']
6089
6270
  mm_wide['rsf_FD_mean'] = rsfpro['FD_mean']
6090
6271
  mm_wide['rsf_FD_max'] = rsfpro['FD_max']
6091
6272
  mm_wide['rsf_alff_mean'] = rsfpro['alff_mean']
6092
6273
  mm_wide['rsf_alff_sd'] = rsfpro['alff_sd']
6093
6274
  mm_wide['rsf_falff_mean'] = rsfpro['falff_mean']
6094
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']
6095
6278
  ofn = output_prefix + separator + 'rsfcorr.csv'
6096
6279
  rsfpro['corr'].to_csv( ofn )
6097
6280
  # apply same principle to new correlation matrix, doesn't need to be incorporated with mm_wide
@@ -6125,11 +6308,12 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
6125
6308
  mm_wide['ssnr_mean'] = perfpro['ssnr'].mean()
6126
6309
  mm_wide['high_motion_count'] = perfpro['high_motion_count']
6127
6310
  mm_wide['evr'] = perfpro['bold_evr']
6311
+ mm_wide['n_outliers'] = perfpro['n_outliers']
6128
6312
  mm_wide['FD_mean'] = perfpro['FD_mean']
6129
6313
  mm_wide['FD_max'] = perfpro['FD_max']
6130
6314
  if 'perf_dataframe' in perfpro.keys():
6131
6315
  pderk = perfpro['perf_dataframe'].iloc[: , 1:]
6132
- mm_wide = pd.concat( [ mm_wide, pderk ], axis=1 )
6316
+ mm_wide = pd.concat( [ mm_wide, pderk ], axis=1, ignore_index=False )
6133
6317
  else:
6134
6318
  print("FIXME - perfusion dataframe")
6135
6319
  for mykey in ['perfusion','cbf']:
@@ -7584,7 +7768,7 @@ def read_mm_csv( x, is_t1=False, colprefix=None, separator='-', verbose=False ):
7584
7768
  return None
7585
7769
  if colprefix is not None:
7586
7770
  xdf.columns=colprefix + xdf.columns
7587
- return pd.concat( [df,xdf], axis=1 )
7771
+ return pd.concat( [df,xdf], axis=1, ignore_index=False )
7588
7772
 
7589
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,
7590
7774
  progress=False, verbose=False ):
@@ -7610,7 +7794,7 @@ progress=False, verbose=False ):
7610
7794
  verbose : boolean
7611
7795
  """
7612
7796
  from os.path import exists
7613
- musthavecols = ['projectID', 'subjectID','date','imageID','filename']
7797
+ musthavecols = ['projectID', 'subjectID','date','imageID']
7614
7798
  for k in range(len(musthavecols)):
7615
7799
  if not musthavecols[k] in sdf.keys():
7616
7800
  raise ValueError('sdf is missing column ' +musthavecols[k] + ' in merge_wides_to_study_dataframe' )
@@ -7677,7 +7861,7 @@ progress=False, verbose=False ):
7677
7861
  # mm.index=csvrow.index
7678
7862
  uidname = mod_name + '_mmwide_filename'
7679
7863
  mm[ uidname ] = rootid
7680
- csvrow=pd.concat( [csvrow,mm], axis=1 )
7864
+ csvrow=pd.concat( [csvrow,mm], axis=1, ignore_index=False )
7681
7865
  else:
7682
7866
  if verbose and report_missing:
7683
7867
  print( nrgwidefn + " absent")
@@ -7687,7 +7871,7 @@ progress=False, verbose=False ):
7687
7871
  else:
7688
7872
  csvrow=csvrow.loc[:,~csvrow.columns.duplicated()]
7689
7873
  alldf = alldf.loc[:,~alldf.columns.duplicated()]
7690
- alldf = pd.concat( [alldf, csvrow], axis=0, ignore_index=True)
7874
+ alldf = pd.concat( [alldf, csvrow], axis=0, ignore_index=True )
7691
7875
  return alldf
7692
7876
 
7693
7877
  def assemble_modality_specific_dataframes( mm_wide_csvs, hierdfin, nrg_modality, separator='-', progress=None, verbose=False ):
@@ -7707,7 +7891,7 @@ def assemble_modality_specific_dataframes( mm_wide_csvs, hierdfin, nrg_modality,
7707
7891
  for y in fnsnm:
7708
7892
  temp=read_mm_csv( y, colprefix=moddersub+'_', is_t1=False, separator=separator, verbose=verbose )
7709
7893
  if temp is not None:
7710
- nmdf=pd.concat( [nmdf, temp], axis=0)
7894
+ nmdf=pd.concat( [nmdf, temp], axis=0, ignore_index=False )
7711
7895
  return nmdf
7712
7896
 
7713
7897
  def bind_wide_mm_csvs( mm_wide_csvs, merge=True, separator='-', verbose = 0 ) :
@@ -7732,7 +7916,7 @@ def bind_wide_mm_csvs( mm_wide_csvs, merge=True, separator='-', verbose = 0 ) :
7732
7916
  for y in mm_wide_csvs:
7733
7917
  temp=read_mm_csv( y, colprefix='T1Hier_', separator=separator, is_t1=True )
7734
7918
  if temp is not None:
7735
- hierdf=pd.concat( [hierdf, temp], axis=0)
7919
+ hierdf=pd.concat( [hierdf, temp], axis=0, ignore_index=False )
7736
7920
  if verbose > 0:
7737
7921
  mypro=50
7738
7922
  else:
@@ -7845,8 +8029,8 @@ def threaded_bind_wide_mm_csvs( mm_wide_csvs, n_workers ):
7845
8029
  results = []
7846
8030
  for future in futures.as_completed(to_do):
7847
8031
  res0, res1 = future.result()
7848
- alldf=pd.concat( [alldf, res0 ], axis=0 )
7849
- 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 )
7850
8034
  return alldf, alldfavg
7851
8035
 
7852
8036
 
@@ -7857,6 +8041,9 @@ def get_names_from_data_frame(x, demogIn, exclusions=None):
7857
8041
  antspymm.get_names_from_data_frame( ['a','e'], df )
7858
8042
  antspymm.get_names_from_data_frame( ['e'], df, exclusions='N' )
7859
8043
  """
8044
+ # Check if x is a string and convert it to a list
8045
+ if isinstance(x, str):
8046
+ x = [x]
7860
8047
  def get_unique( qq ):
7861
8048
  unique = []
7862
8049
  for number in qq:
@@ -7935,7 +8122,7 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
7935
8122
  jmm.loc[k, dt0[1:]] = nanList * len(v1)
7936
8123
  if verbose:
7937
8124
  print( joinDiagnosticsLoc )
7938
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8125
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0, ignore_index=False )
7939
8126
 
7940
8127
  if verbose:
7941
8128
  print("do DTI")
@@ -7989,7 +8176,7 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
7989
8176
  jmm.loc[k, dt0[1:]] = nanList * len( dt0[1:] )
7990
8177
  if verbose:
7991
8178
  print( joinDiagnosticsLoc )
7992
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8179
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0, ignore_index=False )
7993
8180
 
7994
8181
 
7995
8182
  # first task - sort by u_hier_id
@@ -8048,7 +8235,8 @@ def average_mm_df( jmm_in, diagnostic_n=25, corr_thresh=0.9, verbose=False ):
8048
8235
  jmmUniq.loc[u][fl_names[1:]] = temp.mean(axis=0)
8049
8236
  else:
8050
8237
  jmmUniq.loc[u][fl_names[1:]] = nanList * temp.shape[1]
8051
- joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc], axis=0)
8238
+ joinDiagnostics = pd.concat( [joinDiagnostics, joinDiagnosticsLoc],
8239
+ axis=0, ignore_index=False )
8052
8240
 
8053
8241
  return jmmUniq, jmm, joinDiagnostics
8054
8242
 
@@ -8452,7 +8640,7 @@ def blind_image_assessment(
8452
8640
  viz_filename_use = re.sub( ".png", "_slice"+str(jjj).zfill(4)+".png", viz_filename )
8453
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' )
8454
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' ])
8455
- outdf = pd.concat( [outdf, df ], axis=0 )
8643
+ outdf = pd.concat( [outdf, df ], axis=0, ignore_index=False )
8456
8644
  if verbose:
8457
8645
  print( outdf )
8458
8646
  if viz_filename is not None:
@@ -8620,6 +8808,46 @@ def wmh( flair, t1, t1seg,
8620
8808
  'convexhull_mask': distmask }
8621
8809
 
8622
8810
 
8811
+ def replace_elements_in_numpy_array(original_array, indices_to_replace, new_value):
8812
+ """
8813
+ Replace specified elements or rows in a numpy array with a new value.
8814
+
8815
+ Parameters:
8816
+ original_array (numpy.ndarray): A numpy array in which elements or rows are to be replaced.
8817
+ indices_to_replace (list or numpy.ndarray): Indices of elements or rows to be replaced.
8818
+ new_value: The new value to replace the specified elements or rows.
8819
+
8820
+ Returns:
8821
+ numpy.ndarray: A new numpy array with the specified elements or rows replaced. If the input array is None,
8822
+ the function returns None.
8823
+ """
8824
+
8825
+ if original_array is None:
8826
+ return None
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
+
8838
+ if original_array.ndim == 1:
8839
+ # Replace elements in a 1D array
8840
+ original_array[valid_indices] = new_value
8841
+ elif original_array.ndim == 2:
8842
+ # Replace rows in a 2D array
8843
+ original_array[valid_indices, :] = new_value
8844
+ else:
8845
+ raise ValueError("original_array must be either 1D or 2D.")
8846
+
8847
+ return original_array
8848
+
8849
+
8850
+
8623
8851
  def remove_elements_from_numpy_array(original_array, indices_to_remove):
8624
8852
  """
8625
8853
  Remove specified elements or rows from a numpy array.
@@ -8668,13 +8896,14 @@ def remove_volumes_from_timeseries(time_series, volumes_to_remove):
8668
8896
  return ants.copy_image_info( time_series, filtered_time_series )
8669
8897
 
8670
8898
 
8671
- def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8899
+ def impute_timeseries(time_series, volumes_to_impute, method='linear', verbose=False):
8672
8900
  """
8673
8901
  Impute specified volumes from a time series with interpolated values.
8674
8902
 
8675
8903
  :param time_series: ANTsImage representing the time series (4D image).
8676
8904
  :param volumes_to_impute: List of volume indices to impute.
8677
8905
  :param method: Interpolation method ('linear' or other methods if implemented).
8906
+ :param verbose: boolean
8678
8907
  :return: ANTsImage with specified volumes imputed.
8679
8908
  """
8680
8909
  if not isinstance(time_series, ants.ANTsImage):
@@ -8685,25 +8914,36 @@ def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8685
8914
 
8686
8915
  # Convert time_series to numpy for manipulation
8687
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)
8688
8925
 
8689
8926
  for vol_idx in volumes_to_impute:
8690
8927
  # Ensure the volume index is within the valid range
8691
- if vol_idx < 0 or vol_idx >= time_series_np.shape[3]:
8928
+ if vol_idx < 0 or vol_idx >= total_volumes:
8692
8929
  raise ValueError(f"Volume index {vol_idx} is out of bounds.")
8693
8930
 
8694
- # Find neighboring volumes for interpolation
8695
- lower_idx = max(0, vol_idx - 1)
8696
- 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}")
8697
8941
 
8698
8942
  if method == 'linear':
8699
8943
  # Linear interpolation between the two nearest volumes
8700
- if lower_idx == vol_idx or upper_idx == vol_idx:
8701
- # Edge case: duplicate the closest volume
8702
- interpolated_volume = time_series_np[..., lower_idx]
8703
- else:
8704
- lower_volume = time_series_np[..., lower_idx]
8705
- upper_volume = time_series_np[..., upper_idx]
8706
- 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
8707
8947
  else:
8708
8948
  # Placeholder for other interpolation methods
8709
8949
  raise NotImplementedError("Currently, only linear interpolation is implemented.")
@@ -8717,7 +8957,6 @@ def impute_timeseries(time_series, volumes_to_impute, method='linear'):
8717
8957
 
8718
8958
  return imputed_time_series
8719
8959
 
8720
-
8721
8960
  def impute_dwi( dwi, threshold = 0.20, verbose=False ):
8722
8961
  """
8723
8962
  Identify bad volumes in a dwi and impute them fully automatically.
@@ -8746,6 +8985,36 @@ def impute_dwi( dwi, threshold = 0.20, verbose=False ):
8746
8985
  return dwi
8747
8986
  return impute_timeseries( dwi, complement )
8748
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
+
8749
9018
 
8750
9019
  def flatten_time_series(time_series):
8751
9020
  """
@@ -9176,6 +9445,42 @@ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, brain_i
9176
9445
  return addem
9177
9446
 
9178
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
+
9179
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 ):
9180
9485
  """
9181
9486
  Aggregate ANTsPyMM results from the specified CSV file and save the aggregated results to a new CSV file.
@@ -9204,15 +9509,6 @@ def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='dat
9204
9509
  import numpy as np
9205
9510
  from glob import glob
9206
9511
 
9207
- def filter_df( indf, myprefix ):
9208
- nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
9209
- indf = indf.loc[:, nums]
9210
- indf=indf.loc[:, indf.dtypes != 'object' ]
9211
- indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
9212
- indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
9213
- indf = indf.add_prefix( myprefix )
9214
- return( indf )
9215
-
9216
9512
  def myread_csv(x, cnms):
9217
9513
  """
9218
9514
  Reads a CSV file and returns a DataFrame excluding specified columns.
@@ -9308,13 +9604,13 @@ def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='dat
9308
9604
  t1df = filter_df( t1df, mymod+'_')
9309
9605
  dflist = dflist + [t1df]
9310
9606
 
9311
- hdf = pd.concat( dflist, axis=1)
9607
+ hdf = pd.concat( dflist, axis=1, ignore_index=False )
9312
9608
  if verbose:
9313
9609
  print( df.loc[locind,'filename'] )
9314
9610
  if myct == 1:
9315
9611
  subdf = df.iloc[[x]]
9316
9612
  hdf.index = subdf.index.copy()
9317
- df = pd.concat( [df,hdf], axis=1)
9613
+ df = pd.concat( [df,hdf], axis=1, ignore_index=False )
9318
9614
  else:
9319
9615
  commcols = list(set(hdf.columns).intersection(df.columns))
9320
9616
  df.loc[locind, commcols] = hdf.loc[0, commcols]
@@ -9369,14 +9665,18 @@ def aggregate_antspymm_results_sdf(
9369
9665
  import numpy as np
9370
9666
  from glob import glob
9371
9667
 
9372
- def filter_df( indf, myprefix ):
9373
- nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
9374
- indf = indf.loc[:, nums]
9375
- indf=indf.loc[:, indf.dtypes != 'object' ]
9376
- indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
9377
- indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
9378
- indf = indf.add_prefix( myprefix )
9379
- 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()
9380
9680
 
9381
9681
  def myread_csv(x, cnms):
9382
9682
  """
@@ -9420,8 +9720,8 @@ def aggregate_antspymm_results_sdf(
9420
9720
  myfn = os.path.basename( df['filename'].iloc[x] )
9421
9721
  temp = myfn.split( splitsep )
9422
9722
  # Generalized search paths
9423
- sid0 = temp[0]
9424
- sid = str(df[subject_col].iloc[x])
9723
+ sid0 = str( temp[0] )
9724
+ sid = str( df[subject_col].iloc[x] )
9425
9725
  if sid0 != sid:
9426
9726
  warnings.warn("OUTER: the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
9427
9727
  warnings.warn( "filename is : " + myfn )
@@ -9449,18 +9749,20 @@ def aggregate_antspymm_results_sdf(
9449
9749
  dfout = pd.DataFrame()
9450
9750
  myct = 0
9451
9751
  for x in range( df.shape[0]):
9452
- print("\n\n-------------------------------------------------")
9453
9752
  if verbose:
9753
+ print("\n\n-------------------------------------------------")
9454
9754
  print(f"{x}...")
9755
+ else:
9756
+ progress_reporter(x, df.shape[0], width=500)
9455
9757
  locind = df.index[x]
9456
9758
  myfn = os.path.basename( df['filename'].iloc[x] )
9457
- sid = df[subject_col].iloc[x]
9759
+ sid = str( df[subject_col].iloc[x] )
9458
9760
  tempB = myfn.split( splitsep )
9459
- sid0 = tempB[1]
9460
- if sid0 != sid:
9461
- warnings.warn("INNER: the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid0 )
9462
- warnings.warn( "filename is : " + myfn )
9463
- 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) )
9464
9766
  warnings.warn( "x is : " + str(x) )
9465
9767
  warnings.warn( "index is : " + str(locind) )
9466
9768
  myproj = str(df[project_col].iloc[x])
@@ -9495,7 +9797,8 @@ def aggregate_antspymm_results_sdf(
9495
9797
  dflist = [hdf]
9496
9798
 
9497
9799
  for mymod in vmoddict.keys():
9498
- print("\n\n************************* " + mymod + " *************************")
9800
+ if verbose:
9801
+ print("\n\n************************* " + mymod + " *************************")
9499
9802
  modalityclass = vmoddict[ mymod ]
9500
9803
  if wild_card_modality_id:
9501
9804
  mymodid = '*'
@@ -9508,7 +9811,8 @@ def aggregate_antspymm_results_sdf(
9508
9811
  temp = mymodid.split( idsep )
9509
9812
  mymodid = temp[ len( temp )-1 ]
9510
9813
  else:
9511
- print("missing")
9814
+ if verbose:
9815
+ print("missing")
9512
9816
  continue
9513
9817
  if verbose:
9514
9818
  print( "modality id is " + mymodid + " for modality " + modalityclass + ' modality specific subj ' + sid + ' modality specific id is ' + myid + " its date " + mydate )
@@ -9521,7 +9825,10 @@ def aggregate_antspymm_results_sdf(
9521
9825
  print( modsearch )
9522
9826
  t1wfn = sorted( glob( modsearch ) )
9523
9827
  if len( t1wfn ) > 1:
9524
- 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 )
9525
9832
  if len( t1wfn ) == 1:
9526
9833
  if verbose:
9527
9834
  print(t1wfn)
@@ -9532,13 +9839,13 @@ def aggregate_antspymm_results_sdf(
9532
9839
  if verbose:
9533
9840
  print( " cannot find " + modsearch )
9534
9841
 
9535
- hdf = pd.concat( dflist, axis=1)
9842
+ hdf = pd.concat( dflist, axis=1, ignore_index=False)
9536
9843
  if verbose:
9537
9844
  print( "count: " + str( myct ) )
9538
9845
  subdf = df.iloc[[x]]
9539
9846
  hdf.index = subdf.index.copy()
9540
- subdf = pd.concat( [subdf,hdf], axis=1)
9541
- 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 )
9542
9849
  badnames = get_names_from_data_frame( ['Unnamed'], dfout )
9543
9850
  dfout=dfout.drop(badnames, axis=1)
9544
9851
  return( dfout )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: antspymm
3
- Version: 1.1.7
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
@@ -337,8 +337,8 @@ for each subject/timepoint, one would run:
337
337
  studyfn="matched_mm_data2.csv"
338
338
  df=pd.read_csv( studyfn )
339
339
  index = 20 # 20th subject/timepoint
340
- csvfns = df['fn']
341
- csvrow = df[ df['fn'] == csvfns[index] ]
340
+ csvfns = df['filename']
341
+ csvrow = df[ df['filename'] == csvfns[index] ]
342
342
  csvrow['projectID']='MyStudy'
343
343
 
344
344
  ############################################################################################
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,7 +0,0 @@
1
- antspymm/__init__.py,sha256=DaLNk25DbKwtBnX_IKBnSK99tHCeU17M-OYUTfxR3pw,3656
2
- antspymm/mm.py,sha256=hQIzKhltVHICtS9z9iYjE0rMjQO-zxXm0pHihLjkNV8,392560
3
- antspymm-1.1.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-1.1.7.dist-info/METADATA,sha256=uK0Aigxxr7MWmsgs58OCa2CH9ykZvrY0RosKdIE1u8c,14150
5
- antspymm-1.1.7.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
6
- antspymm-1.1.7.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-1.1.7.dist-info/RECORD,,