antspymm 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
antspymm/__init__.py CHANGED
@@ -56,6 +56,8 @@ from .mm import average_mm_df
56
56
  from .mm import get_names_from_data_frame
57
57
  from .mm import read_mm_csv
58
58
  from .mm import assemble_modality_specific_dataframes
59
+ from .mm import aggregate_antspymm_results
60
+ from .mm import aggregate_antspymm_results_sdf
59
61
  from .mm import mc_denoise
60
62
  from .mm import mc_reg
61
63
  from .mm import dti_reg
@@ -90,3 +92,7 @@ from .mm import collect_blind_qc_by_modality
90
92
  from .mm import get_valid_modalities
91
93
  from .mm import study_dataframe_from_matched_dataframe
92
94
  from .mm import merge_wides_to_study_dataframe
95
+ from .mm import map_scalar_to_labels
96
+ from .mm import template_figure_with_overlay
97
+ from .mm import brainmap_figure
98
+ from .mm import bold_perfusion
antspymm/mm.py CHANGED
@@ -79,6 +79,8 @@ __all__ = ['version',
79
79
  'novelty_detection_loop',
80
80
  'novelty_detection_quantile',
81
81
  'generate_mm_dataframe',
82
+ 'aggregate_antspymm_results',
83
+ 'aggregate_antspymm_results_sdf',
82
84
  'study_dataframe_from_matched_dataframe',
83
85
  'merge_wides_to_study_dataframe',
84
86
  'wmh']
@@ -159,11 +161,11 @@ def get_valid_modalities( long=False, asString=False, qc=False ):
159
161
  asString - concat list to string
160
162
  """
161
163
  if long:
162
- mymod = ["T1w", "NM2DMT", "rsfMRI", "rsfMRI_LR", "rsfMRI_RL", "DTI", "DTI_LR","DTI_RL","T2Flair", "dwi", "func" ]
164
+ mymod = ["T1w", "NM2DMT", "rsfMRI", "rsfMRI_LR", "rsfMRI_RL", "DTI", "DTI_LR","DTI_RL","T2Flair", "dwi", "func", "perf" ]
163
165
  elif qc:
164
- mymod = [ 'T1w', 'T2Flair', 'NM2DMT','DTIdwi','DTIb0', 'rsfMRI']
166
+ mymod = [ 'T1w', 'T2Flair', 'NM2DMT','DTIdwi','DTIb0', 'rsfMRI', "perf" ]
165
167
  else:
166
- mymod = ["T1w", "NM2DMT", "DTI","T2Flair", "rsfMRI" ]
168
+ mymod = ["T1w", "NM2DMT", "DTI","T2Flair", "rsfMRI", "perf" ]
167
169
  if not asString:
168
170
  return mymod
169
171
  else:
@@ -184,7 +186,8 @@ def generate_mm_dataframe(
184
186
  flair_filename=[],
185
187
  rsf_filenames=[],
186
188
  dti_filenames=[],
187
- nm_filenames=[]
189
+ nm_filenames=[],
190
+ perf_filename=[]
188
191
  ):
189
192
  from os.path import exists
190
193
  valid_modalities = get_valid_modalities()
@@ -219,6 +222,17 @@ def generate_mm_dataframe(
219
222
  flair_filename=flair_filename[0]
220
223
  if flair_filename is not None and not "lair" in flair_filename:
221
224
  raise ValueError("flair is not flair filename " + flair_filename)
225
+ ## perfusion
226
+ if perf_filename is not None:
227
+ if isinstance(perf_filename,list):
228
+ if (len(perf_filename) == 0):
229
+ perf_filename=None
230
+ else:
231
+ print("Take first entry from perf_filename list")
232
+ perf_filename=perf_filename[0]
233
+ if perf_filename is not None and not "perf" in perf_filename:
234
+ raise ValueError("perf_filename is not perf filename " + perf_filename)
235
+
222
236
  for k in nm_filenames:
223
237
  if k is not None:
224
238
  if not "NM" in k:
@@ -231,7 +245,10 @@ def generate_mm_dataframe(
231
245
  if k is not None:
232
246
  if not "fMRI" in k and not "func" in k:
233
247
  raise ValueError("rsfMRI/func is not rsfmri filename " + k)
234
- allfns = [t1_filename] + [flair_filename] + nm_filenames + dti_filenames + rsf_filenames
248
+ if perf_filename is not None:
249
+ if not "perf" in perf_filename:
250
+ raise ValueError("perf_filename is not a valid perfusion (perf) filename " + k)
251
+ allfns = [t1_filename] + [flair_filename] + nm_filenames + dti_filenames + rsf_filenames + [perf_filename]
235
252
  for k in allfns:
236
253
  if k is not None:
237
254
  if not isinstance(k, str):
@@ -247,7 +264,8 @@ def generate_mm_dataframe(
247
264
  source_image_directory,
248
265
  output_image_directory,
249
266
  t1_filename,
250
- flair_filename]
267
+ flair_filename,
268
+ perf_filename]
251
269
  mydata0 = coredata + rsf_filenames + dti_filenames
252
270
  mydata = mydata0 + nm_filenames
253
271
  corecols = [
@@ -259,7 +277,8 @@ def generate_mm_dataframe(
259
277
  'sourcedir',
260
278
  'outputdir',
261
279
  'filename',
262
- 'flairid']
280
+ 'flairid',
281
+ 'perfid']
263
282
  mycols0 = corecols + [
264
283
  'rsfid1', 'rsfid2',
265
284
  'dtid1', 'dtid2']
@@ -268,10 +287,6 @@ def generate_mm_dataframe(
268
287
  'nmid6', 'nmid7','nmid8', 'nmid9', 'nmid10', 'nmid11'
269
288
  ]
270
289
  mycols = mycols0 + nmext
271
- print(len(mydata0))
272
- print(len(nm_filenames))
273
- print(len(mycols0))
274
- print(len(nmext))
275
290
  studycsv = pd.DataFrame([ mydata ],
276
291
  columns=mycols)
277
292
  return studycsv
@@ -343,6 +358,10 @@ def nrg_2_bids( nrg_filename ):
343
358
  bids_modality_folder = 'func'
344
359
  bids_modality_filename = 'func'
345
360
 
361
+ if nrg_modality == 'perf' :
362
+ bids_modality_folder = 'perf'
363
+ bids_modality_filename = 'perf'
364
+
346
365
  bids_suffix = nrg_suffix[1:]
347
366
  bids_filename = f'{bids_subject}_{bids_session}_{bids_modality_filename}.{bids_suffix}'
348
367
 
@@ -385,6 +404,9 @@ def bids_2_nrg( bids_filename, project_name, date, nrg_modality=None ):
385
404
  if bids_modality == 'func' and nrg_modality is None :
386
405
  nrg_modality = 'rsfMRI'
387
406
 
407
+ if bids_modality == 'perf' and nrg_modality is None :
408
+ nrg_modality = 'perf'
409
+
388
410
  nrg_suffix = bids_suffix[1:]
389
411
  nrg_filename = f'{project_name}-{nrg_subject_id}-{date}-{nrg_modality}-{nrg_image_id}.{nrg_suffix}'
390
412
 
@@ -1833,7 +1855,128 @@ def mc_reg(
1833
1855
  "FD": FD,
1834
1856
  }
1835
1857
 
1836
- def get_data( name=None, force_download=False, version=14, target_extension='.csv' ):
1858
+ def map_scalar_to_labels(dataframe, label_image_template):
1859
+ """
1860
+ Map scalar values from a DataFrame to associated integer image labels.
1861
+
1862
+ Parameters:
1863
+ - dataframe (pd.DataFrame): A Pandas DataFrame containing a label column and scalar_value column.
1864
+ - label_image_template (ants.ANTsImage): ANTs image with (at least some of) the same values as labels.
1865
+
1866
+ Returns:
1867
+ - ants.ANTsImage: A label image with scalar values mapped to associated integer labels.
1868
+ """
1869
+
1870
+ # Create an empty label image with the same geometry as the template
1871
+ mapped_label_image = label_image_template.clone() * 0.0
1872
+
1873
+ # Loop through DataFrame and map scalar values to labels
1874
+ for index, row in dataframe.iterrows():
1875
+ label = int(row['label']) # Assuming the DataFrame has a 'label' column
1876
+ scalar_value = row['scalar_value'] # Replace with your column name
1877
+ mapped_label_image[label_image_template == label] = scalar_value
1878
+
1879
+ return mapped_label_image
1880
+
1881
+
1882
+ def template_figure_with_overlay(scalar_label_df, prefix, outputfilename=None, template='cit168', xyz=None, mask_dilation=25, padding=12, verbose=True):
1883
+ """
1884
+ Process and visualize images with mapped scalar values.
1885
+
1886
+ Parameters:
1887
+ - scalar_label_df (pd.DataFrame): A Pandas DataFrame containing scalar values and labels.
1888
+ - prefix (str): The prefix for input image files.
1889
+ - template (str, optional): Template for selecting image data (default is 'cit168').
1890
+ - xyz (str, optional): The integer index of the slices to display.
1891
+ - mask_dilation (int, optional): Dilation factor for creating a mask (default is 25).
1892
+ - padding (int, optional): Padding value for the mapped images (default is 12).
1893
+ - verbose (bool, optional): Enable verbose mode for printing (default is True).
1894
+
1895
+ Example Usage:
1896
+ >>> scalar_label_df = pd.DataFrame({'label': [1, 2, 3], 'scalar_value': [0.5, 0.8, 1.2]})
1897
+ >>> prefix = '../PPMI_template0_'
1898
+ >>> process_and_visualize_images(scalar_label_df, prefix, template='cit168', xyz=None, mask_dilation=25, padding=12, verbose=True)
1899
+ """
1900
+
1901
+ # Template image paths
1902
+ template_paths = {
1903
+ 'cit168': 'cit168lab.nii.gz',
1904
+ 'bf': 'bf.nii.gz',
1905
+ 'cerebellum': 'cerebellum.nii.gz',
1906
+ 'mtl': 'mtl.nii.gz',
1907
+ 'ctx': 'dkt_cortex.nii.gz',
1908
+ 'jhuwm': 'JHU_wm.nii.gz'
1909
+ }
1910
+
1911
+ if template not in template_paths:
1912
+ print( "Valid options:")
1913
+ print( template_paths )
1914
+ raise ValueError(f"Template option '{template}' does not exist.")
1915
+
1916
+ template_image_path = template_paths[template]
1917
+ template_image = ants.image_read(f'{prefix}{template_image_path}')
1918
+
1919
+ # Load image data
1920
+ edgeimg = ants.image_read(f'{prefix}edge.nii.gz')
1921
+ dktimg = ants.image_read(f'{prefix}dkt_parcellation.nii.gz')
1922
+ segimg = ants.image_read(f'{prefix}tissue_segmentation.nii.gz')
1923
+ ttl = ''
1924
+
1925
+ # Load and process the template image
1926
+ ventricles = ants.threshold_image(dktimg, 4, 4) + ants.threshold_image(dktimg, 43, 43)
1927
+ seggm = ants.mask_image(segimg, segimg, [2, 4], binarize=False)
1928
+ edgeimg = edgeimg.clone()
1929
+ edgeimg[edgeimg == 0] = ventricles[edgeimg == 0]
1930
+ segwm = ants.threshold_image(segimg, 3, 4).morphology("open", 1)
1931
+
1932
+ # Define cropping mask
1933
+ cmask = ants.threshold_image(template_image, 1, 1.e9).iMath("MD", mask_dilation)
1934
+
1935
+ mapped_image = map_scalar_to_labels(scalar_label_df, template_image)
1936
+ tcrop = ants.crop_image(template_image, cmask)
1937
+ toviz = ants.crop_image(mapped_image, cmask)
1938
+ seggm = ants.crop_image(edgeimg, cmask)
1939
+
1940
+ # Map scalar values to labels and visualize
1941
+ toviz = ants.pad_image(toviz, pad_width=(padding, padding, padding))
1942
+ seggm = ants.pad_image(seggm, pad_width=(padding, padding, padding))
1943
+ tcrop = ants.pad_image(tcrop, pad_width=(padding, padding, padding))
1944
+
1945
+ if xyz is None:
1946
+ if template == 'cit168':
1947
+ xyz=[140, 89, 94]
1948
+ elif template == 'bf':
1949
+ xyz=[114,92,76]
1950
+ elif template == 'cerebellum':
1951
+ xyz=[169, 128, 137]
1952
+ elif template == 'mtl':
1953
+ xyz=[154, 112, 113]
1954
+ elif template == 'ctx':
1955
+ xyz=[233, 190, 174]
1956
+ elif template == 'jhuwm':
1957
+ xyz=[146, 133, 182]
1958
+
1959
+ if verbose:
1960
+ print("plot xyz for " + template )
1961
+ print( xyz )
1962
+
1963
+ if outputfilename is None:
1964
+ temp = ants.plot_ortho( seggm, overlay=toviz, crop=False,
1965
+ xyz=xyz, cbar_length=0.2, cbar_vertical=False,
1966
+ flat=True, xyz_lines=False, resample=False, orient_labels=False,
1967
+ title=ttl, titlefontsize=12, title_dy=-0.02, textfontcolor='red',
1968
+ cbar=True, allow_xyz_change=False)
1969
+ else:
1970
+ temp = ants.plot_ortho( seggm, overlay=toviz, crop=False,
1971
+ xyz=xyz, cbar_length=0.2, cbar_vertical=False,
1972
+ flat=True, xyz_lines=False, resample=False, orient_labels=False,
1973
+ title=ttl, titlefontsize=12, title_dy=-0.02, textfontcolor='red',
1974
+ cbar=True, allow_xyz_change=False, filename=outputfilename )
1975
+ seggm = temp['image']
1976
+ toviz = temp['overlay']
1977
+ return { "underlay": seggm, 'overlay': toviz, 'seg': tcrop }
1978
+
1979
+ def get_data( name=None, force_download=False, version=19, target_extension='.csv' ):
1837
1980
  """
1838
1981
  Get ANTsPyMM data filename
1839
1982
 
@@ -2187,7 +2330,9 @@ def get_average_rsf( x, min_t=10, max_t=35 ):
2187
2330
  bavg = bavg + ants.registration(oavg,b0,'Rigid',outprefix=ofn)['warpedmovout']
2188
2331
  import shutil
2189
2332
  shutil.rmtree(output_directory, ignore_errors=True )
2190
- return ants.n4_bias_field_correction(bavg)
2333
+ bavg = ants.iMath( bavg, 'Normalize' )
2334
+ return bavg
2335
+ # return ants.n4_bias_field_correction(bavg, mask=ants.get_mask( bavg ) )
2191
2336
 
2192
2337
 
2193
2338
  def get_average_dwi_b0( x, fixed_b0=None, fixed_dwi=None, fast=False ):
@@ -4214,7 +4359,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4214
4359
  reg_iterations=[40,20,5] )
4215
4360
  if verbose:
4216
4361
  print("End rsfmri motion correction")
4217
- # ants.image_write( corrmo['motion_corrected'], '/tmp/temp.nii.gz' )
4218
4362
 
4219
4363
  mytsnr = tsnr( corrmo['motion_corrected'], bmask )
4220
4364
  mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
@@ -4224,8 +4368,6 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4224
4368
  t1reg = ants.registration( und, t1, "SyNBold" )
4225
4369
  if verbose:
4226
4370
  print("t1 2 bold done")
4227
- # ants.image_write( und, '/tmp/template_bold_masked.nii.gz' )
4228
- # ants.image_write( t1reg['warpedmovout'], '/tmp/t1tobold.nii.gz' )
4229
4371
  boldseg = ants.apply_transforms( und, t1segmentation,
4230
4372
  t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
4231
4373
  gmseg = ants.threshold_image( t1segmentation, 2, 2 )
@@ -4403,6 +4545,216 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4403
4545
  return outdict
4404
4546
 
4405
4547
 
4548
+ def bold_perfusion( fmri, fmri_template, t1head, t1, t1segmentation, t1dktcit, f=[0.0,math.inf], FD_threshold=0.5, spa = 1.5, nc = 6, type_of_transform='Rigid', tc='alternating', deepmask=False, add_FD_to_nuisance=False, verbose=False ):
4549
+ """
4550
+ Estimate perfusion from a BOLD time series image. Will attempt to figure out the T-C labels from the data.
4551
+
4552
+ Arguments
4553
+ ---------
4554
+ fmri : BOLD fmri antsImage
4555
+
4556
+ fmri_template : reference space for BOLD
4557
+
4558
+ t1head : ANTsImage
4559
+ input 3-D T1 brain image (not brain extracted)
4560
+
4561
+ t1 : ANTsImage
4562
+ input 3-D T1 brain image (brain extracted)
4563
+
4564
+ t1segmentation : ANTsImage
4565
+ t1 segmentation - a six tissue segmentation image in T1 space
4566
+
4567
+ t1dktcit : ANTsImage
4568
+ t1 dkt cortex plus cit parcellation
4569
+
4570
+ f : band pass limits for frequency filtering
4571
+
4572
+ spa : gaussian smoothing for spatial and temporal component e.g. (1,1,1,0) in physical space coordinates
4573
+
4574
+ nc : number of components for compcor filtering
4575
+
4576
+ type_of_transform : SyN or Rigid
4577
+
4578
+ tc: string either alternating or split (default is alternating ie CTCTCT; split is CCCCTTTT)
4579
+
4580
+ deepmask: boolean
4581
+
4582
+ add_FD_to_nuisance: boolean
4583
+
4584
+ verbose : boolean
4585
+
4586
+ Returns
4587
+ ---------
4588
+ a dictionary containing the derived network maps
4589
+
4590
+ """
4591
+ import numpy as np
4592
+ import pandas as pd
4593
+ import re
4594
+ import math
4595
+ from sklearn.linear_model import LinearRegression
4596
+
4597
+ ex_path = os.path.expanduser( "~/.antspyt1w/" )
4598
+ cnxcsvfn = ex_path + "dkt_cortex_cit_deep_brain.csv"
4599
+
4600
+ def replicate_list(user_list, target_size):
4601
+ # Calculate the number of times the list should be replicated
4602
+ replication_factor = target_size // len(user_list)
4603
+ # Replicate the list and handle any remaining elements
4604
+ replicated_list = user_list * replication_factor
4605
+ remaining_elements = target_size % len(user_list)
4606
+ replicated_list += user_list[:remaining_elements]
4607
+ return replicated_list
4608
+
4609
+ def one_hot_encode(char_list):
4610
+ unique_chars = list(set(char_list))
4611
+ encoding_dict = {char: [1 if char == c else 0 for c in unique_chars] for char in unique_chars}
4612
+ encoded_matrix = np.array([encoding_dict[char] for char in char_list])
4613
+ return encoded_matrix
4614
+
4615
+ A = np.zeros((1,1))
4616
+ fmri = ants.iMath( fmri, 'Normalize' )
4617
+ if deepmask:
4618
+ bmask = antspynet.brain_extraction( fmri_template, 'bold' ).threshold_image(0.5,1).morphology("close",2).iMath("FillHoles")
4619
+ else:
4620
+ rig = ants.registration( fmri_template, t1head, 'BOLDRigid' )
4621
+ bmask = ants.apply_transforms( fmri_template, ants.threshold_image(t1segmentation,1,6), rig['fwdtransforms'][0], interpolator='genericLabel' )
4622
+ if verbose:
4623
+ print("Begin perfusion motion correction")
4624
+ mytrim=4 # trim will guarantee an even length
4625
+ if fmri.shape[3] % 2 == 1:
4626
+ mytrim = 5
4627
+ corrmo = timeseries_reg(
4628
+ fmri, fmri_template,
4629
+ type_of_transform=type_of_transform,
4630
+ total_sigma=0.0,
4631
+ fdOffset=2.0,
4632
+ trim = mytrim,
4633
+ output_directory=None,
4634
+ verbose=verbose,
4635
+ syn_metric='cc',
4636
+ syn_sampling=2,
4637
+ reg_iterations=[40,20,5] )
4638
+ if verbose:
4639
+ print("End rsfmri motion correction")
4640
+
4641
+ ntp = corrmo['motion_corrected'].shape[3]
4642
+ if tc == 'alternating':
4643
+ tclist = replicate_list( ['C','T'], ntp )
4644
+ else:
4645
+ tclist = replicate_list( ['C'], int(ntp/2) ) + replicate_list( ['T'], int(ntp/2) )
4646
+
4647
+ tclist = one_hot_encode( tclist )
4648
+ mytsnr = tsnr( corrmo['motion_corrected'], bmask )
4649
+ mytsnrThresh = np.quantile( mytsnr.numpy(), 0.995 )
4650
+ tsnrmask = ants.threshold_image( mytsnr, 0, mytsnrThresh ).morphology("close",2)
4651
+ bmask = bmask * ants.iMath( tsnrmask, "FillHoles" )
4652
+ und = fmri_template * bmask
4653
+ t1reg = ants.registration( und, t1, "SyNBold" )
4654
+ if verbose:
4655
+ print("t1 2 bold done")
4656
+ boldseg = ants.apply_transforms( und, t1segmentation,
4657
+ t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
4658
+ dktseg = ants.apply_transforms( und, t1dktcit,
4659
+ t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
4660
+ gmseg = ants.threshold_image( t1segmentation, 2, 2 )
4661
+ gmseg = gmseg + ants.threshold_image( t1segmentation, 4, 4 )
4662
+ gmseg = ants.threshold_image( gmseg, 1, 4 )
4663
+ gmseg = ants.iMath( gmseg, 'MD', 1 )
4664
+ gmseg = ants.apply_transforms( und, gmseg,
4665
+ t1reg['fwdtransforms'], interpolator = 'genericLabel' ) * bmask
4666
+ csfAndWM = ( ants.threshold_image( t1segmentation, 1, 1 ) +
4667
+ ants.threshold_image( t1segmentation, 3, 3 ) ).morphology("erode",1)
4668
+ csfAndWM = ants.apply_transforms( und, csfAndWM,
4669
+ t1reg['fwdtransforms'], interpolator = 'nearestNeighbor' ) * bmask
4670
+
4671
+ mycompcor = ants.compcor( corrmo['motion_corrected'],
4672
+ ncompcor=nc, quantile=0.90, mask = csfAndWM,
4673
+ filter_type='polynomial', degree=2 )
4674
+
4675
+ nt = corrmo['motion_corrected'].shape[3]
4676
+ tr = ants.get_spacing( corrmo['motion_corrected'] )[3]
4677
+ highMotionTimes = np.where( corrmo['FD'] >= 1.0 )
4678
+ goodtimes = np.where( corrmo['FD'] < 0.5 )
4679
+ simg = ants.smooth_image(corrmo['motion_corrected'],
4680
+ spa, sigma_in_physical_coordinates = True )
4681
+
4682
+ nuisance = mycompcor[ 'components' ]
4683
+ nuisance = np.c_[ nuisance, mycompcor['basis'] ]
4684
+ if add_FD_to_nuisance:
4685
+ nuisance = np.c_[ nuisance, corrmo['FD'] ]
4686
+
4687
+ if verbose:
4688
+ print("make sure nuisance is independent of TC")
4689
+ nuisance = ants.regress_components( nuisance, tclist )
4690
+
4691
+ regression_mask = bmask.clone()
4692
+ gmmat = ants.timeseries_to_matrix( simg, regression_mask )
4693
+ if f[0] > 0 and f[1] < 1: # some would argue against this
4694
+ gmmat = ants.bandpass_filter_matrix( gmmat, tr = tr, lowf=f[0], highf=f[1] )
4695
+ # gmmat = ants.regress_components( gmmat, nuisance )
4696
+ # Perform linear regression to estimate perfusion
4697
+ regression_model = LinearRegression()
4698
+ regvars = np.hstack( (nuisance, tclist ))
4699
+ coefind = regvars.shape[1]-1
4700
+ regvars = regvars[:,range(coefind)]
4701
+ regression_model.fit( regvars, gmmat )
4702
+ coefind = regression_model.coef_.shape[1]-1
4703
+ perfimg = ants.make_image( regression_mask, regression_model.coef_[:,coefind] )
4704
+ meangmval = ( perfimg[ gmseg == 1 ] ).mean()
4705
+ if meangmval < 0:
4706
+ perfimg = perfimg * (-1.0)
4707
+ meangmval = ( perfimg[ gmseg == 1 ] ).mean()
4708
+ if verbose:
4709
+ print("Coefficients:", regression_model.coef_)
4710
+ print("Coef mean", regression_model.coef_.mean(axis=0))
4711
+ print( regression_model.coef_.shape )
4712
+ print( perfimg.max() )
4713
+ gsrbold = ants.matrix_to_timeseries(simg, gmmat, regression_mask)
4714
+ outdict = {}
4715
+ outdict['meanBold'] = und
4716
+ outdict['brainmask'] = bmask
4717
+ rsfNuisance = pd.DataFrame( nuisance )
4718
+ rsfNuisance['FD']=corrmo['FD']
4719
+
4720
+
4721
+ nonbrainmask = ants.iMath( bmask, "MD",2) - bmask
4722
+ trimmask = ants.iMath( bmask, "ME",2)
4723
+ edgemask = ants.iMath( bmask, "ME",1) - trimmask
4724
+
4725
+ if verbose:
4726
+ print("perfusion dataframe begin")
4727
+ df_perf = antspyt1w.map_intensity_to_dataframe(
4728
+ 'dkt_cortex_cit_deep_brain',
4729
+ perfimg,
4730
+ dktseg)
4731
+ df_perf = antspyt1w.merge_hierarchical_csvs_to_wide_format(
4732
+ {'perf' : df_perf},
4733
+ col_names = ['Mean'] )
4734
+ if verbose:
4735
+ print("perfusion dataframe end")
4736
+ print( df_perf )
4737
+
4738
+ outdict['perfusion']=perfimg
4739
+ outdict['perfusion_gm_mean']=meangmval
4740
+ outdict['perf_dataframe']=df_perf
4741
+ outdict['motion_corrected'] = corrmo['motion_corrected']
4742
+ outdict['gmseg'] = gmseg
4743
+ outdict['gsrbold'] = gsrbold
4744
+ outdict['brain_mask'] = bmask
4745
+ outdict['nuisance'] = rsfNuisance
4746
+ outdict['tsnr'] = mytsnr
4747
+ outdict['ssnr'] = slice_snr( corrmo['motion_corrected'], csfAndWM, gmseg )
4748
+ outdict['dvars'] = dvars( corrmo['motion_corrected'], gmseg )
4749
+ outdict['high_motion_count'] = (rsfNuisance['FD'] > FD_threshold ).sum()
4750
+ outdict['high_motion_pct'] = (rsfNuisance['FD'] > FD_threshold ).sum() / rsfNuisance.shape[0]
4751
+ outdict['FD_max'] = rsfNuisance['FD'].max()
4752
+ outdict['FD_mean'] = rsfNuisance['FD'].mean()
4753
+ outdict['bold_evr'] = antspyt1w.patch_eigenvalue_ratio( und, 512, [16,16,16], evdepth = 0.9, mask = bmask )
4754
+ outdict['t1reg'] = t1reg
4755
+ return outdict
4756
+
4757
+
4406
4758
 
4407
4759
  def write_bvals_bvecs(bvals, bvecs, prefix ):
4408
4760
  ''' Write FSL FDT bvals and bvecs files
@@ -4474,6 +4826,7 @@ def mm(
4474
4826
  flair_image=None,
4475
4827
  nm_image_list=None,
4476
4828
  dw_image=[], bvals=[], bvecs=[],
4829
+ perfusion_image=None,
4477
4830
  srmodel=None,
4478
4831
  do_tractography = False,
4479
4832
  do_kk = False,
@@ -4510,6 +4863,8 @@ def mm(
4510
4863
 
4511
4864
  bvecs : list of bvecs file names
4512
4865
 
4866
+ perfusion_image : single perfusion image
4867
+
4513
4868
  srmodel : optional srmodel
4514
4869
 
4515
4870
  do_tractography : boolean
@@ -4578,7 +4933,8 @@ def mm(
4578
4933
  'FA_summ' : None,
4579
4934
  'MD_summ' : None,
4580
4935
  'tractography' : None,
4581
- 'tractography_connectivity' : None
4936
+ 'tractography_connectivity' : None,
4937
+ 'perf' : None,
4582
4938
  }
4583
4939
  normalization_dict = {
4584
4940
  'kk_norm': None,
@@ -4586,6 +4942,7 @@ def mm(
4586
4942
  'DTI_norm': None,
4587
4943
  'FA_norm' : None,
4588
4944
  'MD_norm' : None,
4945
+ 'perf_norm' : None,
4589
4946
  'alff_norm' : None,
4590
4947
  'falff_norm' : None,
4591
4948
  'CinguloopercularTaskControl_norm' : None,
@@ -4606,6 +4963,19 @@ def mm(
4606
4963
  print('kk')
4607
4964
  output_dict['kk'] = antspyt1w.kelly_kapowski_thickness( hier['brain_n4_dnz'],
4608
4965
  labels=hier['dkt_parc']['dkt_cortex'], iterations=45 )
4966
+ if perfusion_image is not None:
4967
+ boldTemplate=get_average_rsf(perfusion_image)
4968
+ if perfusion_image.shape[3] > 8: # FIXME - better heuristic?
4969
+ output_dict['perf'] = bold_perfusion(
4970
+ perfusion_image,
4971
+ boldTemplate,
4972
+ t1_image,
4973
+ hier['brain_n4_dnz'],
4974
+ t1atropos,
4975
+ hier['dkt_parc']['dkt_cortex'] + hier['cit168lab'],
4976
+ f=[0.0,math.inf],
4977
+ spa = (1.0, 1.0, 1.0, 0.0),
4978
+ nc = 6, verbose=verbose )
4609
4979
  ################################## do the rsf .....
4610
4980
  if len(rsf_image) > 0:
4611
4981
  rsf_image = [i for i in rsf_image if i is not None]
@@ -4786,7 +5156,7 @@ def mm(
4786
5156
  if verbose:
4787
5157
  print('normalization')
4788
5158
  # might reconsider this template space - cropped and/or higher res?
4789
- template = ants.resample_image( template, [1,1,1], use_voxels=False )
5159
+ # template = ants.resample_image( template, [1,1,1], use_voxels=False )
4790
5160
  # t1reg = ants.registration( template, hier['brain_n4_dnz'], "antsRegistrationSyNQuickRepro[s]")
4791
5161
  t1reg = do_normalization
4792
5162
  if do_kk:
@@ -4812,6 +5182,11 @@ def mm(
4812
5182
  normalization_dict[rsfkey] = ants.apply_transforms(
4813
5183
  group_template, rsfpro[netid],
4814
5184
  group_transform+rsfrig['fwdtransforms'] )
5185
+ if output_dict['perf'] is not None: # zizzer
5186
+ comptx = group_transform + output_dict['perf']['t1reg']['invtransforms']
5187
+ normalization_dict['perf_norm'] = ants.apply_transforms( group_template,
5188
+ output_dict['perf']['perfusion'], comptx,
5189
+ whichtoinvert=[False,False,True,False] )
4815
5190
  if nm_image_list is not None:
4816
5191
  nmpro = output_dict['NM']
4817
5192
  nmrig = nmpro['t1_to_NM_transform'] # this is an inverse tx
@@ -4872,6 +5247,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
4872
5247
  image_write_with_thumbnail( mm['NM'][mykey], tempfn, thumb=False )
4873
5248
 
4874
5249
  faderk = mdderk = fat1derk = mdt1derk = None
5250
+
4875
5251
  if mm['DTI'] is not None:
4876
5252
  mydti = mm['DTI']
4877
5253
  myop = output_prefix + separator
@@ -4984,6 +5360,26 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbo
4984
5360
  # mm_wide.to_csv( fdfn )
4985
5361
  else:
4986
5362
  mm_wide['dti_FD_mean'] = mm_wide['dti_FD_max'] = 'NA'
5363
+
5364
+ if mm['perf'] is not None:
5365
+ perfpro = mm['perf']
5366
+ mm_wide['gm_mean'] = perfpro['perfusion_gm_mean']
5367
+ mm_wide['tsnr_mean'] = perfpro['tsnr'].mean()
5368
+ mm_wide['dvars_mean'] = perfpro['dvars'].mean()
5369
+ mm_wide['ssnr_mean'] = perfpro['ssnr'].mean()
5370
+ mm_wide['high_motion_count'] = perfpro['high_motion_count']
5371
+ mm_wide['evr'] = perfpro['bold_evr']
5372
+ mm_wide['FD_mean'] = perfpro['FD_mean']
5373
+ mm_wide['FD_max'] = perfpro['FD_max']
5374
+ if 'perf_dataframe' in perfpro.keys():
5375
+ pderk = perfpro['perf_dataframe'].iloc[: , 1:]
5376
+ mm_wide = pd.concat( [ mm_wide, pderk ], axis=1 )
5377
+ else:
5378
+ print("FIXME - perfusion dataframe")
5379
+ mykey='perfusion'
5380
+ tempfn = output_prefix + separator + mykey + '.nii.gz'
5381
+ image_write_with_thumbnail( mm['perf'][mykey], tempfn )
5382
+
4987
5383
  mmwidefn = output_prefix + separator + 'mmwide.csv'
4988
5384
  mm_wide.to_csv( mmwidefn )
4989
5385
  if verbose:
@@ -5490,7 +5886,7 @@ def mm_nrg(
5490
5886
  write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep, verbose=True )
5491
5887
  for mykey in normPro.keys():
5492
5888
  if normPro[mykey] is not None:
5493
- if visualize and normPro[mykey].components == 1:
5889
+ if visualize and normPro[mykey].components == 1 and False:
5494
5890
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
5495
5891
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
5496
5892
  return
@@ -5639,6 +6035,8 @@ def mm_csv(
5639
6035
  imfns=['filename']
5640
6036
  elif locmod == 'T2Flair':
5641
6037
  imfns=['flairid']
6038
+ elif locmod == 'perf':
6039
+ imfns=['perfid']
5642
6040
  elif locmod == 'NM2DMT':
5643
6041
  imfns=[]
5644
6042
  for i in range(11):
@@ -5797,6 +6195,9 @@ def mm_csv(
5797
6195
  hier['brain_n4_dnz'],
5798
6196
  normalization_template_transform_type,
5799
6197
  outprefix = normout, verbose=False )
6198
+ myjac = ants.create_jacobian_determinant_image( template,
6199
+ greg['fwdtransforms'][0], do_log=True, geom=True )
6200
+ image_write_with_thumbnail( myjac, normout + "logjacobian.nii.gz", thumb=False )
5800
6201
  if verbose:
5801
6202
  print("end group template registration")
5802
6203
  else:
@@ -5974,6 +6375,22 @@ def mm_csv(
5974
6375
  axis=2, nslices=maxslice, ncol=7, crop=True, title='DefaultMode', filename=mymm+mysep+"boldDefaultMode.png" )
5975
6376
  ants.plot( tabPro['rsf']['meanBold'], tabPro['rsf']['FrontoparietalTaskControl'],
5976
6377
  axis=2, nslices=maxslice, ncol=7, crop=True, title='FrontoparietalTaskControl', filename=mymm+mysep+"boldFrontoparietalTaskControl.png" )
6378
+ if ( mymod == 'perf' ) and ishapelen == 4:
6379
+ dowrite=True
6380
+ tabPro, normPro = mm( t1, hier,
6381
+ perfusion_image=img,
6382
+ srmodel=None,
6383
+ do_tractography=False,
6384
+ do_kk=False,
6385
+ do_normalization=templateTx,
6386
+ group_template = normalization_template,
6387
+ group_transform = groupTx,
6388
+ test_run=test_run,
6389
+ verbose=True )
6390
+ if tabPro['perf'] is not None and visualize:
6391
+ maxslice = np.min( [21, tabPro['perf']['meanBold'].shape[2] ] )
6392
+ ants.plot( tabPro['perf']['perfusion'],
6393
+ axis=2, nslices=maxslice, ncol=7, crop=True, title='perfusion image', filename=mymm+mysep+"perfusion.png" )
5977
6394
  if ( mymod == 'DTI_LR' or mymod == 'DTI_RL' or mymod == 'DTI' ) and ishapelen == 4:
5978
6395
  bvalfn = re.sub( '.nii.gz', '.bval' , myimg )
5979
6396
  bvecfn = re.sub( '.nii.gz', '.bvec' , myimg )
@@ -6037,7 +6454,7 @@ def mm_csv(
6037
6454
  write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep )
6038
6455
  for mykey in normPro.keys():
6039
6456
  if normPro[mykey] is not None and normPro[mykey].components == 1:
6040
- if visualize:
6457
+ if visualize and False:
6041
6458
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
6042
6459
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
6043
6460
  return
@@ -6445,7 +6862,7 @@ progress=False, verbose=False ):
6445
6862
  """
6446
6863
  extend a study data frame with wide outputs
6447
6864
 
6448
- sdf : the input study dataframe
6865
+ sdf : the input study dataframe from antspymm QC output
6449
6866
 
6450
6867
  processing_dir: the directory location of the processed data
6451
6868
 
@@ -6468,8 +6885,8 @@ progress=False, verbose=False ):
6468
6885
  for k in range(len(musthavecols)):
6469
6886
  if not musthavecols[k] in sdf.keys():
6470
6887
  raise ValueError('sdf is missing column ' +musthavecols[k] + ' in merge_wides_to_study_dataframe' )
6471
- possible_iids = [ 'imageID', 'imageID', 'imageID', 'flairid', 'dtid1', 'dtid2', 'rsfid1', 'rsfid2', 'nmid1', 'nmid2', 'nmid3', 'nmid4', 'nmid5', 'nmid6', 'nmid7', 'nmid8', 'nmid9', 'nmid10' ]
6472
- modality_ids = [ 'T1wHierarchical', 'T1wHierarchicalSR', 'T1w', 'T2Flair', 'DTI', 'DTI', 'rsfMRI', 'rsfMRI', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT']
6888
+ possible_iids = [ 'imageID', 'imageID', 'imageID', 'flairid', 'dtid1', 'dtid2', 'rsfid1', 'rsfid2', 'nmid1', 'nmid2', 'nmid3', 'nmid4', 'nmid5', 'nmid6', 'nmid7', 'nmid8', 'nmid9', 'nmid10', 'perfid' ]
6889
+ modality_ids = [ 'T1wHierarchical', 'T1wHierarchicalSR', 'T1w', 'T2Flair', 'DTI', 'DTI', 'rsfMRI', 'rsfMRI', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'perf']
6473
6890
  alldf=pd.DataFrame()
6474
6891
  for myk in sdf.index:
6475
6892
  if progress > 0 and int(myk) % int(progress) == 0:
@@ -7640,3 +8057,501 @@ def novelty_detection_quantile(df_train, df_test):
7640
8057
  temp = (myqs[mykey][0] > df_train[mykey]).sum() / n
7641
8058
  myqs[mykey] = abs( temp - 0.5 ) / 0.5
7642
8059
  return myqs
8060
+
8061
+
8062
+ def brainmap_figure(statistical_df, data_dictionary_path, output_prefix, brain_image, overlay_cmap='bwr', nslices=21, ncol=7, edge_image_dilation = 0, black_bg=True, axes = [0,1,2], fixed_overlay_range=None, crop=True, verbose=False ):
8063
+ """
8064
+ Create figures based on statistical data and an underlying brain image.
8065
+
8066
+ Assumes both ~/.antspyt1w and ~/.antspymm data is available
8067
+
8068
+ Parameters:
8069
+ - statistical_df (pandas dataframe): with 2 columns named anat and value
8070
+ the anat column should have names that meet *partial matching* criterion
8071
+ with respect to regions that are measured in antspymm. value will be
8072
+ the value to be displayed. if two examples of a given region exist in
8073
+ statistical_df, then the largest absolute value will be taken for display.
8074
+ - data_dictionary_path (str): Path to the data dictionary CSV file.
8075
+ - output_prefix (str): Prefix for the output figure filenames.
8076
+ - brain_image (antsImage): the brain image on which results will overlay.
8077
+ - overlay_cmap (str): see matplotlib
8078
+ - nslices (int): number of slices to show
8079
+ - ncol (int): number of columns to show
8080
+ - edge_image_dilation (int): integer greater than or equal to zero
8081
+ - black_bg (bool): boolean
8082
+ - axes (list): integer list typically [0,1,2] sagittal coronal axial
8083
+ - fixed_overlay_range (list): scalar pair will try to keep a constant cbar and will truncate the overlay at these min/max values
8084
+ - crop (bool): crops the image to display by the extent of the overlay
8085
+ - verbose (bool): boolean
8086
+
8087
+ Returns:
8088
+ an image with values mapped to the associated regions
8089
+ """
8090
+
8091
+ # Read the statistical file
8092
+ zz = statistical_df
8093
+
8094
+ # Read the data dictionary from a CSV file
8095
+ mydict = pd.read_csv(data_dictionary_path)
8096
+ mydict = mydict[~mydict['Measurement'].str.contains("tractography-based connectivity", na=False)]
8097
+
8098
+ # Load image and process it
8099
+ edgeimg = ants.iMath(brain_image,"Normalize")
8100
+ if edge_image_dilation > 0:
8101
+ edgeimg = ants.iMath( edgeimg, "MD", edge_image_dilation)
8102
+
8103
+ # Define lists and data frames
8104
+ postfix = ['bf', 'deep_cit168lab', 'mtl', 'cerebellum', 'dkt_cortex','brainstem']
8105
+ atlas = ['BF', 'CIT168', 'MTL', 'TustisonCobra', 'desikan-killiany-tourville','brainstem']
8106
+ postdesc = ['nbm3CH13', 'CIT168_Reinf_Learn_v1_label_descriptions_pad', 'mtl_description', 'cerebellum', 'dkt','CIT168_T1w_700um_pad_adni_brainstem']
8107
+ statdf = pd.DataFrame({'img': postfix, 'atlas': atlas, 'csvdescript': postdesc})
8108
+ templateprefix = '~/.antspymm/PPMI_template0_'
8109
+ # Iterate through columns and create figures
8110
+ col2viz = 'value'
8111
+ if True:
8112
+ anattoshow = zz['anat'].unique()
8113
+ if verbose:
8114
+ print(col2viz)
8115
+ print(anattoshow)
8116
+ # Rest of your code for figure creation goes here...
8117
+ addem = edgeimg * 0
8118
+ for k in range(len(anattoshow)):
8119
+ if verbose:
8120
+ print(str(k) + " " + anattoshow[k] )
8121
+ mysub = zz[zz['anat'].str.contains(anattoshow[k])]
8122
+ vals2viz = mysub[col2viz].agg(['min', 'max'])
8123
+ vals2viz = vals2viz[abs(vals2viz).idxmax()]
8124
+ myext = None
8125
+ if 'dktcortex' in anattoshow[k]:
8126
+ myext = 'dkt_cortex'
8127
+ elif 'cit168' in anattoshow[k]:
8128
+ myext = 'deep_cit168lab'
8129
+ elif 'mtl' in anattoshow[k]:
8130
+ myext = 'mtl'
8131
+ elif 'cerebellum' in anattoshow[k]:
8132
+ myext = 'cerebellum'
8133
+ elif 'brainstem' in anattoshow[k]:
8134
+ myext = 'brainstem'
8135
+ elif any(item in anattoshow[k] for item in ['nbm', 'bf']):
8136
+ myext = 'bf'
8137
+ for j in postfix:
8138
+ if j == "dkt_cortex":
8139
+ j = 'dktcortex'
8140
+ if j == "deep_cit168lab":
8141
+ j = 'deep_cit168'
8142
+ anattoshow[k] = anattoshow[k].replace(j, "")
8143
+ if verbose:
8144
+ print( anattoshow[k] + " " + str( vals2viz ) )
8145
+ myatlas = atlas[postfix.index(myext)]
8146
+ correctdescript = postdesc[postfix.index(myext)]
8147
+ locfilename = templateprefix + myext + '.nii.gz'
8148
+ if verbose:
8149
+ print( locfilename )
8150
+ myatlas = ants.image_read(locfilename)
8151
+ atlasDescript = pd.read_csv(f"~/.antspyt1w/{correctdescript}.csv")
8152
+ atlasDescript['Description'] = atlasDescript['Description'].str.lower()
8153
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace(" ", "_")
8154
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("_left_", "_")
8155
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("_right_", "_")
8156
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("_left", "")
8157
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("_right", "")
8158
+ if myext == 'cerebellum':
8159
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("l_", "")
8160
+ atlasDescript['Description'] = atlasDescript['Description'].str.replace("r_", "")
8161
+ whichindex = atlasDescript.index[atlasDescript['Description'] == anattoshow[k]].values[0]
8162
+ else:
8163
+ whichindex = atlasDescript.index[atlasDescript['Description'].str.contains(anattoshow[k])]
8164
+
8165
+ if type(whichindex) is np.int64:
8166
+ labelnums = atlasDescript.loc[whichindex, 'Label']
8167
+ else:
8168
+ labelnums = list(atlasDescript.loc[whichindex, 'Label'])
8169
+ if not isinstance(labelnums, list):
8170
+ labelnums=[labelnums]
8171
+ addemiszero = ants.threshold_image(addem, 0, 0)
8172
+ temp = ants.image_read(locfilename)
8173
+ temp = ants.mask_image(temp, temp, level=labelnums, binarize=True)
8174
+ temp[temp == 1] = (vals2viz)
8175
+ temp[addemiszero == 0] = 0
8176
+ addem = addem + temp
8177
+
8178
+ if verbose:
8179
+ print('Done Adding')
8180
+ for axx in axes:
8181
+ figfn=output_prefix+f"fig{col2viz}ax{axx}_py.jpg"
8182
+ if crop:
8183
+ cmask = ants.threshold_image( addem,1e-5, 1e9 ).iMath("MD",3) + ants.threshold_image( addem,-1e9, -1e-5 ).iMath("MD",3)
8184
+ addemC = ants.crop_image( addem, cmask )
8185
+ edgeimgC = ants.crop_image( edgeimg, cmask )
8186
+ else:
8187
+ addemC = addem
8188
+ edgeimgC = edgeimg
8189
+ if fixed_overlay_range is not None:
8190
+ addemC[0:3,0:3,0:3]=fixed_overlay_range[0]
8191
+ addemC[4:7,4:7,4:7]=fixed_overlay_range[1]
8192
+ addemC[ addemC < fixed_overlay_range[0] ] = fixed_overlay_range[0]
8193
+ addemC[ addemC > fixed_overlay_range[1] ] = fixed_overlay_range[1]
8194
+ ants.plot(edgeimgC, addemC, axis=axx, nslices=nslices, ncol=ncol,
8195
+ overlay_cmap=overlay_cmap, resample=False,
8196
+ filename=figfn, cbar=axx==axes[0], crop=True, black_bg=black_bg )
8197
+ if verbose:
8198
+ print(f"{col2viz} done")
8199
+ if verbose:
8200
+ print("DONE brain map figures")
8201
+ return addem
8202
+
8203
+
8204
+ def aggregate_antspymm_results(input_csv, subject_col='subjectID', date_col='date', image_col='imageID', date_column='ses-1', base_path="./Processed/ANTsExpArt/", hiervariable='T1wHierarchical', valid_modalities=None, verbose=False ):
8205
+ """
8206
+ Aggregate ANTsPyMM results from the specified CSV file and save the aggregated results to a new CSV file.
8207
+
8208
+ Parameters:
8209
+ - input_csv (str): File path of the input CSV file containing ANTsPyMM QC results averaged and with outlier measurements.
8210
+ - subject_col (str): Name of the column to store subject IDs.
8211
+ - date_col (str): Name of the column to store date information.
8212
+ - image_col (str): Name of the column to store image IDs.
8213
+ - date_column (str): Name of the column representing the date information.
8214
+ - base_path (str): Base path for search paths. Defaults to "./Processed/ANTsExpArt/".
8215
+ - hiervariable (str) : the string variable denoting the Hierarchical output
8216
+ - valid_modalities (str array) : identifies for each modality; if None will be replaced by get_valid_modalities(long=True)
8217
+ - verbose : boolean
8218
+
8219
+ Note:
8220
+ This function is tested under limited circumstances. Use with caution.
8221
+
8222
+ Example usage:
8223
+ agg_df = aggregate_antspymm_results("qcdfaol.csv", subject_col='subjectID', date_col='date', image_col='imageID', date_column='ses-1', base_path="./Your/Custom/Path/")
8224
+
8225
+ Author:
8226
+ Avants and ChatGPT
8227
+ """
8228
+ import pandas as pd
8229
+ import numpy as np
8230
+ from glob import glob
8231
+
8232
+ def filter_df( indf, myprefix ):
8233
+ nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
8234
+ indf = indf.loc[:, nums]
8235
+ indf=indf.loc[:, indf.dtypes != 'object' ]
8236
+ indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
8237
+ indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
8238
+ indf = indf.add_prefix( myprefix )
8239
+ return( indf )
8240
+
8241
+ def myread_csv(x, cnms):
8242
+ """
8243
+ Reads a CSV file and returns a DataFrame excluding specified columns.
8244
+
8245
+ Parameters:
8246
+ - x (str): File path of the input CSV file describing the blind QC output
8247
+ - cnms (list): List of column names to exclude from the DataFrame.
8248
+
8249
+ Returns:
8250
+ pd.DataFrame: DataFrame with specified columns excluded.
8251
+ """
8252
+ df = pd.read_csv(x)
8253
+ return df.loc[:, ~df.columns.isin(cnms)]
8254
+
8255
+ import warnings
8256
+ # Warning message for untested function
8257
+ warnings.warn("Warning: This function is not well tested. Use with caution.")
8258
+
8259
+ if valid_modalities is None:
8260
+ valid_modalities = get_valid_modalities('long')
8261
+
8262
+ # Read the input CSV file
8263
+ df = pd.read_csv(input_csv)
8264
+
8265
+ # Filter rows where modality is 'T1w'
8266
+ df = df[df['modality'] == 'T1w']
8267
+ badnames = get_names_from_data_frame( ['Unnamed'], df )
8268
+ df=df.drop(badnames, axis=1)
8269
+
8270
+ # Add new columns for subject ID, date, and image ID
8271
+ df[subject_col] = np.nan
8272
+ df[date_col] = date_column
8273
+ df[image_col] = np.nan
8274
+ df = df.astype({subject_col: str, date_col: str, image_col: str })
8275
+
8276
+ # if verbose:
8277
+ # print( df.shape )
8278
+ # print( df.dtypes )
8279
+
8280
+ # prefilter df for data that exists
8281
+ keep = np.tile( False, df.shape[0] )
8282
+ for x in range(df.shape[0]):
8283
+ temp = df['fn'].iloc[x].split("_")
8284
+ # Generalized search paths
8285
+ path_template = f"{base_path}{temp[0]}/{date_column}/*/*/*"
8286
+ hierfn = sorted(glob( path_template + "-" + hiervariable + "-*wide.csv" ) )
8287
+ if len( hierfn ) > 0:
8288
+ keep[x]=True
8289
+
8290
+
8291
+ df=df[keep]
8292
+
8293
+ if verbose:
8294
+ print( "original input had shape " + str( df.shape[0] ) + " (T1 only) and we find " + str( (keep).sum() ) + " with hierarchical output defined by variable: " + hiervariable )
8295
+ print( df.shape )
8296
+
8297
+ myct = 0
8298
+ for x in range( df.shape[0]):
8299
+ if verbose:
8300
+ print(f"{x}...")
8301
+ locind = df.index[x]
8302
+ temp = df['fn'].iloc[x].split("_")
8303
+ if verbose:
8304
+ print( temp )
8305
+ df[subject_col].iloc[x]=temp[0]
8306
+ df[date_col].iloc[x]=date_column
8307
+ df[image_col].iloc[x]=temp[1]
8308
+
8309
+ # Generalized search paths
8310
+ path_template = f"{base_path}{temp[0]}/{date_column}/*/*/*"
8311
+ if verbose:
8312
+ print(path_template)
8313
+ hierfn = sorted(glob( path_template + "-" + hiervariable + "-*wide.csv" ) )
8314
+ if len( hierfn ) > 0:
8315
+ hdf=t1df=dtdf=rsdf=perfdf=nmdf=flairdf=None
8316
+ if verbose:
8317
+ print(hierfn)
8318
+ hdf = pd.read_csv(hierfn[0])
8319
+ badnames = get_names_from_data_frame( ['Unnamed'], hdf )
8320
+ hdf=hdf.drop(badnames, axis=1)
8321
+ nums = [isinstance(hdf[col].iloc[0], (int, float)) for col in hdf.columns]
8322
+ corenames = list(np.array(hdf.columns)[nums])
8323
+ hdf.loc[:, nums] = hdf.loc[:, nums].add_prefix("T1Hier_")
8324
+ myct = myct + 1
8325
+ dflist = [hdf]
8326
+
8327
+ for mymod in valid_modalities:
8328
+ t1wfn = sorted(glob( path_template+ "-" + mymod + "-*wide.csv" ) )
8329
+ if len( t1wfn ) > 0 :
8330
+ if verbose:
8331
+ print(t1wfn)
8332
+ t1df = myread_csv(t1wfn[0], corenames)
8333
+ t1df = filter_df( t1df, mymod+'_')
8334
+ dflist = dflist + [t1df]
8335
+
8336
+ hdf = pd.concat( dflist, axis=1)
8337
+ if verbose:
8338
+ print( df.loc[locind,'fn'] )
8339
+ if myct == 1:
8340
+ subdf = df.iloc[[x]]
8341
+ hdf.index = subdf.index.copy()
8342
+ df = pd.concat( [df,hdf], axis=1)
8343
+ else:
8344
+ commcols = list(set(hdf.columns).intersection(df.columns))
8345
+ df.loc[locind, commcols] = hdf.loc[0, commcols]
8346
+ badnames = get_names_from_data_frame( ['Unnamed'], df )
8347
+ df=df.drop(badnames, axis=1)
8348
+ return( df )
8349
+
8350
+ def aggregate_antspymm_results_sdf(
8351
+ study_df,
8352
+ project_col='projectID',
8353
+ subject_col='subjectID',
8354
+ date_col='date',
8355
+ image_col='imageID',
8356
+ base_path="./",
8357
+ hiervariable='T1wHierarchical',
8358
+ splitsep='-',
8359
+ idsep='-',
8360
+ wild_card_modality_id=False,
8361
+ verbose=False ):
8362
+ """
8363
+ Aggregate ANTsPyMM results from the specified study data frame and store the aggregated results in a new data frame. This assumes data is organized on disk
8364
+ as follows: rootdir/projectID/subjectID/date/outputid/imageid/ where
8365
+ outputid is modality-specific and created by ANTsPyMM processing.
8366
+
8367
+ Parameters:
8368
+ - study_df (pandas df): pandas data frame, output of generate_mm_dataframe.
8369
+ - project_col (str): Name of the column that stores the project ID
8370
+ - subject_col (str): Name of the column to store subject IDs.
8371
+ - date_col (str): Name of the column to store date information.
8372
+ - image_col (str): Name of the column to store image IDs.
8373
+ - base_path (str): Base path for searching for processing outputs of ANTsPyMM.
8374
+ - hiervariable (str) : the string variable denoting the Hierarchical output
8375
+ - splitsep (str): the separator used to split the filename
8376
+ - idsep (str): the separator used to partition subjectid date and imageid
8377
+ for example, if idsep is - then we have subjectid-date-imageid
8378
+ - wild_card_modality_id (bool): keep if False for safer execution
8379
+ - verbose : boolean
8380
+
8381
+ Note:
8382
+ This function is tested under limited circumstances. Use with caution.
8383
+
8384
+ Example usage:
8385
+ agg_df = aggregate_antspymm_results_sdf( studydf, subject_col='subjectID', date_col='date', image_col='imageID', base_path="./Your/Custom/Path/")
8386
+
8387
+ Author:
8388
+ Avants and ChatGPT
8389
+ """
8390
+ import pandas as pd
8391
+ import numpy as np
8392
+ from glob import glob
8393
+
8394
+ def filter_df( indf, myprefix ):
8395
+ nums = [isinstance(indf[col].iloc[0], (int, float)) for col in indf.columns]
8396
+ indf = indf.loc[:, nums]
8397
+ indf=indf.loc[:, indf.dtypes != 'object' ]
8398
+ indf = indf.loc[:, ~indf.columns.str.contains('Unnamed*', na=False, regex=True)]
8399
+ indf = pd.DataFrame(indf.mean(axis=0, skipna=True)).T
8400
+ indf = indf.add_prefix( myprefix )
8401
+ return( indf )
8402
+
8403
+ def myread_csv(x, cnms):
8404
+ """
8405
+ Reads a CSV file and returns a DataFrame excluding specified columns.
8406
+
8407
+ Parameters:
8408
+ - x (str): File path of the input CSV file describing the blind QC output
8409
+ - cnms (list): List of column names to exclude from the DataFrame.
8410
+
8411
+ Returns:
8412
+ pd.DataFrame: DataFrame with specified columns excluded.
8413
+ """
8414
+ df = pd.read_csv(x)
8415
+ return df.loc[:, ~df.columns.isin(cnms)]
8416
+
8417
+ import warnings
8418
+ # Warning message for untested function
8419
+ warnings.warn("Warning: This function is not well tested. Use with caution.")
8420
+
8421
+ # if valid_modalities is None:
8422
+ valid_modalities = get_valid_modalities('long')
8423
+ vmoddict = {}
8424
+ # Add key-value pairs
8425
+ vmoddict['imageID'] = 'T1w'
8426
+ vmoddict['flairid'] = 'T2Flair'
8427
+ vmoddict['perfid'] = 'perf'
8428
+ vmoddict['rsfid1'] = 'rsfMRI'
8429
+ vmoddict['dtid1'] = 'DTI'
8430
+ vmoddict['nmid1'] = 'NM2DMT'
8431
+
8432
+ # Filter rows where modality is 'T1w'
8433
+ df = study_df[study_df['modality'] == 'T1w']
8434
+ badnames = get_names_from_data_frame( ['Unnamed'], df )
8435
+ df=df.drop(badnames, axis=1)
8436
+ # prefilter df for data that exists
8437
+ keep = np.tile( False, df.shape[0] )
8438
+ for x in range(df.shape[0]):
8439
+ myfn = os.path.basename( df['filename'].iloc[x] )
8440
+ temp = myfn.split( splitsep )
8441
+ # Generalized search paths
8442
+ sid0 = temp[0]
8443
+ sid = str(df[subject_col].iloc[x])
8444
+ if sid0 != sid:
8445
+ warnings.warn("the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
8446
+ myproj = str(df[project_col].iloc[x])
8447
+ mydate = str(df[date_col].iloc[x])
8448
+ myid = str(df[image_col].iloc[x])
8449
+ path_template = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + hiervariable + '/' + str(myid) + "/"
8450
+ hierfn = sorted(glob( path_template + "*" + hiervariable + "*wide.csv" ) )
8451
+ if len( hierfn ) > 0:
8452
+ keep[x]=True
8453
+
8454
+ df=df[keep]
8455
+
8456
+ if not df.index.is_unique:
8457
+ warnings.warn("data frame does not have unique indices. we therefore reset the index to allow the function to continue on." )
8458
+ df = df.reset_index()
8459
+
8460
+
8461
+ if verbose:
8462
+ print( "original input had shape " + str( df.shape[0] ) + " (T1 only) and we find " + str( (keep).sum() ) + " with hierarchical output defined by variable: " + hiervariable )
8463
+ print( df.shape )
8464
+
8465
+ myct = 0
8466
+ for x in range( df.shape[0]):
8467
+ print("\n\n-------------------------------------------------")
8468
+ if verbose:
8469
+ print(f"{x}...")
8470
+ locind = df.index[x]
8471
+ myfn = os.path.basename( df['filename'].iloc[x] )
8472
+ sid = df[subject_col].iloc[x]
8473
+ if sid0 != sid:
8474
+ warnings.warn("the id derived from the filename " + sid + " does not match the id stored in the data frame " + sid )
8475
+ myproj = str(df[project_col].iloc[x])
8476
+ mydate = str(df[date_col].iloc[x])
8477
+ myid = str(df[image_col].iloc[x])
8478
+ if verbose:
8479
+ print( myfn )
8480
+ print( temp )
8481
+ print( "id " + sid )
8482
+ path_template = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + hiervariable + '/' + str(myid) + "/"
8483
+ searchhier = path_template + "*" + hiervariable + "*wide.csv"
8484
+ if verbose:
8485
+ print( searchhier )
8486
+ hierfn = sorted( glob( searchhier ) )
8487
+ if len( hierfn ) > 1:
8488
+ raise ValueError("there are " + str( len( hierfn ) ) + " number of hier fns with search path " + searchhier )
8489
+ if len( hierfn ) == 1:
8490
+ hdf=t1df=dtdf=rsdf=perfdf=nmdf=flairdf=None
8491
+ if verbose:
8492
+ print(hierfn)
8493
+ hdf = pd.read_csv(hierfn[0])
8494
+ badnames = get_names_from_data_frame( ['Unnamed'], hdf )
8495
+ hdf=hdf.drop(badnames, axis=1)
8496
+ nums = [isinstance(hdf[col].iloc[0], (int, float)) for col in hdf.columns]
8497
+ corenames = list(np.array(hdf.columns)[nums])
8498
+ hdf.loc[:, nums] = hdf.loc[:, nums].add_prefix("T1Hier_")
8499
+ myct = myct + 1
8500
+ hdf = hdf.add_prefix( "T1Hier_" )
8501
+ dflist = [hdf]
8502
+
8503
+ for mymod in vmoddict.keys():
8504
+ print("\n\n************************* " + mymod + " *************************")
8505
+ modalityclass = vmoddict[ mymod ]
8506
+ if wild_card_modality_id:
8507
+ mymodid = '*'
8508
+ else:
8509
+ mymodid = str( df[mymod].iloc[x] )
8510
+ if mymodid.lower() != "nan" and mymodid.lower() != "na":
8511
+ mymodid = os.path.basename( mymodid )
8512
+ mymodid = os.path.splitext( mymodid )[0]
8513
+ mymodid = os.path.splitext( mymodid )[0]
8514
+ temp = mymodid.split( idsep )
8515
+ mymodid = temp[ len( temp )-1 ]
8516
+ else:
8517
+ print("missing")
8518
+ continue
8519
+ if verbose:
8520
+ print( "modality id is " + mymodid + " for modality " + modalityclass )
8521
+ modalityclasssearch = modalityclass
8522
+ if modalityclass in ['rsfMRI','DTI']:
8523
+ modalityclasssearch=modalityclass+"*"
8524
+ path_template_m = base_path + "/" + myproj + "/" + sid + "/" + mydate + '/' + modalityclasssearch + '/' + mymodid + "/"
8525
+ modsearch = path_template_m + "*" + modalityclasssearch + "*wide.csv"
8526
+ if verbose:
8527
+ print( modsearch )
8528
+ t1wfn = sorted( glob( modsearch ) )
8529
+ if len( t1wfn ) > 1:
8530
+ raise ValueError("there are " + str( len( t1wfn ) ) + " number of wide fns with search path " + modsearch )
8531
+ if len( t1wfn ) == 1:
8532
+ if verbose:
8533
+ print(t1wfn)
8534
+ t1df = myread_csv(t1wfn[0], corenames)
8535
+ t1df = filter_df( t1df, modalityclass+'_')
8536
+ dflist = dflist + [t1df]
8537
+ else:
8538
+ if verbose:
8539
+ print( " cannot find " + modsearch )
8540
+
8541
+ hdf = pd.concat( dflist, axis=1)
8542
+ if verbose:
8543
+ print( "count: " + str( myct ) )
8544
+ if myct == 1:
8545
+ subdf = df.iloc[[x]]
8546
+ hdf.index = subdf.index.copy()
8547
+ print( hdf.index )
8548
+ print( df.index )
8549
+ df = pd.concat( [df,hdf], axis=1)
8550
+ else:
8551
+ commcols = list(set(hdf.columns).intersection(df.columns))
8552
+ df.loc[locind, commcols] = hdf.loc[0, commcols]
8553
+ badnames = get_names_from_data_frame( ['Unnamed'], df )
8554
+ df=df.drop(badnames, axis=1)
8555
+ return( df )
8556
+
8557
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: antspymm
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: multi-channel/time-series medical image processing with antspyx
5
5
  Home-page: https://github.com/stnava/ANTsPyMM
6
6
  Author: Avants, Gosselin, Tustison, Reardon
@@ -18,10 +18,13 @@ Requires-Dist: dipy
18
18
  Requires-Dist: nibabel
19
19
  Requires-Dist: scipy
20
20
  Requires-Dist: siq
21
- Requires-Dist: sklearn
21
+ Requires-Dist: scikit-learn
22
22
 
23
23
  # ANTsPyMM
24
24
 
25
+
26
+ [mapping](https://imgur.com/a/XzpXI3i)
27
+
25
28
  ## processing utilities for timeseries/multichannel images - mostly neuroimaging
26
29
 
27
30
  the outputs of these processes can be used for data inspection/cleaning/triage
@@ -243,6 +246,13 @@ studycsv = antspymm.generate_mm_dataframe(
243
246
  rsf_filenames=[fns[2]])
244
247
  studycsv2 = studycsv.dropna(axis=1)
245
248
  mmrun = antspymm.mm_csv( studycsv2, mysep='_' )
249
+
250
+ # aggregate the data after you've run on many subjects
251
+ # studycsv_all would be the vstacked studycsv2 data frames
252
+ zz=antspymm.aggregate_antspymm_results_sdf( studycsv_all,
253
+ subject_col='subjectID', date_col='date', image_col='imageID', base_path=bd,
254
+ splitsep='_', idsep='-', wild_card_modality_id=True, verbose=True)
255
+
246
256
  ```
247
257
 
248
258
  ## NRG example
@@ -426,6 +436,15 @@ for f in folders:
426
436
  pdoc -o ./docs antspymm --html
427
437
  ```
428
438
 
439
+ ## ssl error
440
+
441
+ if you get an odd certificate error when calling `force_download`, try:
442
+
443
+ ```python
444
+ import ssl
445
+ ssl._create_default_https_context = ssl._create_unverified_context
446
+ ``
447
+
429
448
  ## to publish a release
430
449
 
431
450
  ```
@@ -0,0 +1,7 @@
1
+ antspymm/__init__.py,sha256=wck10IhO1ZFxNqz4bTIHFP7cqYosRKBI8bKIsZzRwfs,3166
2
+ antspymm/mm.py,sha256=sYXNst5xri6vFDzvuqDB6avuwkKoq3GTEsvm0RLUuPU,347923
3
+ antspymm-1.1.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
+ antspymm-1.1.3.dist-info/METADATA,sha256=bFp7u3Bd85Sq4Nc_TvvzxfYDhyU9Uxj4T9yzDcDK26I,14153
5
+ antspymm-1.1.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ antspymm-1.1.3.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
+ antspymm-1.1.3.dist-info/RECORD,,
@@ -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=BTNN_iisXoz2on4gVcAQWFB_qJYDM-yqZOJU1xho9sw,2931
2
- antspymm/mm.py,sha256=SX1eYoKPmZ6QId0GZBIiztywWJAjNX2lUy9uihyuFG4,308416
3
- antspymm-1.1.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-1.1.1.dist-info/METADATA,sha256=wxrVbMj17gBEbDV9Nuh2Df8S-XPKTH35qS5iZcvPKic,13598
5
- antspymm-1.1.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
6
- antspymm-1.1.1.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-1.1.1.dist-info/RECORD,,